1

Problem

I have a hash/array structure, some of the hash keys are not in the same case though.

I would like to know if there is a way to handle this case without manually checking the keys of every hash.

In the example below i would like all ID/iD/id/Id fields to be printed.


Example code

use warnings;
use strict;


my $Hash = {
   Server  =>   [
                        {
                                Id=>123
                        },
                        {
                                iD=>456
                        },
                        {
                                ID=>789
                        }
                ]

};

for (@{$Hash->{Server}}){
        print "$_->{ID}\n"
        #This is the problematic part
}

Other

perl version: v5.10.0

This data is recieved from elsewhere and must remain the same case, the example above is minimal and i cannot just simply change them all to the same case.

Any more info needed let me know.

Community
  • 1
  • 1
123
  • 10,778
  • 2
  • 22
  • 45

3 Answers3

5

Well, it depends a little bit on your source of information. This looks like you've parsed something, so there may be a better solution.

However, with what we've got here, I'd do it like this:

for my $entry (@{$Hash->{Server}}){
   #grep, find first match. Dupes discarded. 
   my ( $key ) = grep { /^id$/i } keys %$entry; 
   print "$key => ",$entry -> {$key},"\n";
}

This works by using grep with an i regex for case insensitive on keys, and grabbing whatever comes out first. So if you have multiple matches for /id/i then it'll be random which one you get. (sort could help with that though)

Given you're working with XML though, I'd probably backtrack a bit, throw out XML::Simple and do it like this instead:

#!/usr/bin/perl
use strict;
use warnings;

use XML::Twig;

my $twig = XML::Twig -> new ( twig_handlers => { '_all_' => sub { $_ -> lc_attnames }} );
   $twig -> parse ( \*DATA );

print "XML looks like:\n";
$twig -> set_pretty_print ( 'indented_a'); 
$twig -> print;

print "Output:\n";

foreach my $server ( $twig -> get_xpath('//Server') ) { 
    print $server -> att('id'),"\n";
}

__DATA__
<XML>
   <Server ID="123" />
   <Server Id="456" />
   <Server id="789" />
</XML>

Or you can just:

foreach my $server ( $twig -> get_xpath('//Server') ) {
    $server -> lc_attnames;
    print $server -> att('id'),"\n";
}

in lieu of doing it in the twig handlers. The first answer will 'fix' all of your XML to having lower case attributes, which might not be what you want. But then, it might be useful for other scenarios, which is why I've given two examples.

Community
  • 1
  • 1
Sobrique
  • 52,974
  • 7
  • 60
  • 101
  • 1
    I wonder which solution is cheaper. Your grepping all keys or my looking at all possible ones. – simbabque Nov 29 '16 at 11:40
  • Hard to say really. I suspect it depends a lot on the number of keys in the first place - inspecting just one for a match is trivial, but if there's a bunch of redundant (non 'id') ones then grep has to check them all. – Sobrique Nov 29 '16 at 11:43
  • In terms of time complexity, I believe `XML::Twig` solution is cheaper because it doesn't involve any nested loop. Other solutions require nested loops that makes the complexity `O(n*n)`. – Arunesh Singh Nov 29 '16 at 12:08
  • 1
    Was only really looking for the first part of the answer to handle the hash keys but the XML::Twig example is a welcome addition :) – 123 Nov 29 '16 at 13:27
  • Honestly - if you've not gone too far down the implementation road, switching away from `XML::Simple` will help you massively in the long run. `XML::Twig` can even do a drop in replacement with the `simplify` method if you really must. – Sobrique Nov 29 '16 at 13:49
1

There is no built-in way to do that. What you could do is use List::Util's first to at least get less checks, and then still try until one fits for each of the keys.

use strict;
use warnings;
use feature 'say';
use List::Util 'first';

my $Hash = {
    Server => [
        {
            Id => 123
        },
        {
            iD => 456
        },
        {
            ID => 789
        }
    ]
};

foreach my $thing ( @{ $Hash->{Server} } ) {

    #                     this returns the first match in the list, like grep   
    #   so we need to use it here to return the actual value
    say $thing->{ first { $thing->{$_} } qw/id ID iD Id/ };
}

If there are a lot of other keys in the data structure, this is cheaper than looking at all the keys, because you at max look up all possible id keys plus one, and at best two.

If you want the list of possible keys to auto-generate and the uppercase and lowercase letters can be arbitrarily mixed, take a look at this answer.

Community
  • 1
  • 1
simbabque
  • 53,749
  • 8
  • 73
  • 136
-1

I would suggest you to use regex to ignore case of keys using i flag.

for my $item ( @ { $Hash->{Server} }) {
    for(keys %{$item}) {
        print $item -> {$_},"\n" if /^ID$/i;
    }
}
Arunesh Singh
  • 3,489
  • 18
  • 26