ChuckularOne has asked for the wisdom of the Perl Monks concerning the following question:

Here is my problem:
I have a hash (it's 4 dimensional, but we'll only use a 2 dimensional hash for the question! :-) containing key value pairs.
%food_color = ("Apple" => "red", "Banana" => "yellow");
When I'm updating a value, I need to be case insensitive on the key side, so that:
%food_color = ("APPLE" => "blue");
replaces "Apple" => "red"

I have looked high and low, 3 O'Rielly PERL books and every online reference I could find. Nothing even addresses the idea. Has anyone had to do this? Is it possible? Or did I just waste an hour looking for a solution?

Your humble servant,
-Chuck

Replies are listed 'Best First'.
RE: Case insensitivity in a hash... Futile effort?
by vroom (His Eminence) on Apr 18, 2000 at 23:20 UTC
    If you've got the Perl Cookbook look at Recipe 13.15. They show a lot of cool ways to use tie including the case insensitive hash, and an appending hash.

    vroom | Tim Vroom | vroom@cs.hope.edu
Re: Case insensitivity in a hash... Futile effort?
by BBQ (Curate) on Apr 18, 2000 at 23:19 UTC
    I guess you've already considered looping? What about this:
    foreach $key (keys %food_color) { if (uc($key) eq uc($new_key)) { $food_color{$key} = $new_value; } }
    Of course I haven't tested this, but I don't see why it wouldn't work. With uc() you're not really ignoring case, but uppercasing everything just to make sure they match. (kinda like an SQL approach). Le'me know if that works!

    Cheers!

    #!/home/bbq/bin/perl
    # Trust no1!
      Hmm. Well, TMTOWTDI, but I really wouldn't use this if my hash were even slightly large; why use a hash at all if you're going to loop through each key every time you need to do an insert? You'd be better off using a sorted array that you could do a binary search on, or something such.

      This is particularly bad if the hash is multi-dimensional. If I wanted to do the equivalent of

      $hash{foo}{bar}{baz} = 2;
      using your looping method, I'd have to loop over *three* arrays (for the three different keys).

      I'm not saying you're wrong--I'm just saying that the tie method is, in most cases, a much better and more general solution.

        I was going to say I wasn't looping through the hash, but then I went back and looked at the code because I thought... "How could it be working?" And I am. The hash won't be too terribly large for this application maybe 200 records.
        I guess it's time to learn to use tie.

        Your humble servant,
        -Chuck
      Thank you
      Thank you
      Thank you
      Thank you
      Thank you
      Thank you
      Thank you
      Thank you

      This was enough to take care of my issue.

      Your humble servant,
      -Chuck
        Chuck,

        1. You're more than welcome (x8)! :o)
        2. Btrott has a VERY good point! If you're dealing with a big hash you could get slugish in NO time.

        But then again, (I shall say this perl style)
        if (you're running a big hash) { it's probably because you're pulling something off from a text file; if (that's true) { then your text file shouldn't be too big anyway; } else { its going to get slugish from the filesystem access in the first place!; } } else { you should be considering a comercial database product, under which you wouldn't need to do case detection on your hash; }

        Was that reasonably readable? In any case, I'm glad I could be of assistance!

        Cheers again!
Re: Case insensitivity in a hash... Futile effort?
by btrott (Parson) on Apr 18, 2000 at 23:08 UTC
    Well, first of all, redefining the hash *would* get rid of Apple => red, but that's probably not what you meant. :)

    In a thread a while back, vroom wrote a nice solution using tie: Re: case sensitivity in hashes.

    You'll have to modify it a bit, because you want multi- dimensional hashes; so I think you'd probably just have to tie all of the hashes of hashes of hashes, etc. So, for example, say you're using vroom's Tie::CaseInsensitive:

    use strict; my %HASH; use Tie::CaseInsensitive; tie %HASH, 'Tie::CaseInsensitive'; $HASH{BoB} = 1; $HASH{bob} = 2; $HASH{BOb} = 3; tie %{$HASH{bill}}, 'Tie::CaseInsensitive'; $HASH{bill}{jones} = 3; $HASH{BILL}{JONES} = 2; use Data::Dumper; print Dumper \%HASH;
    You should get:
    $VAR1 = { 'bill' => { 'jones' => 2 }, 'bob' => 3 };
    which looks quite right.
      So to tie a hash four dimensional hash, I would:
      $level1 = "LEVEL1"; $level2 = "LEVEL2"; $level3 = "LEVEL3"; $setting1 = "level1"; $setting2 = "level2"; $setting3 = "level3"; use Tie::CaseInsensitive; tie %{$HASH{$level1}{$level2}{level3}}, 'Tie::CaseInsensitive'; $HASH{$level1}{$level2}{level3} = 3; $HASH{$setting1}{$setting2}{setting3} = "Three"; use Data::Dumper; print Dumper \%HASH;
      I tried to run this code and got the following error:
      Can't locate Tie/CaseInsensitive.pm in @INC at ./hashTieTest.pl line 12.
      I'm guessing this means that I need to add a .pm .
      but which one? Is there a tie.pm? I'll go check CPAN. -Chuck
        Tie::CaseInsensitive can be gotten here: Re: case sensitivity in hashes.

        Just copy and paste it into your editor, save it, then put it somewhere in your @INC as Tie/CaseInsensitive.pm.

        And w/ a multi-dimensional hash, you need to tie each level of the hash. Does that make sense? So you tie the hash %HASH, then you tie $HASH{level1}, and so on down.

RE: Case insensitivity in a hash... Futile effort?
by japhy (Canon) on Apr 19, 2000 at 06:23 UTC
    Here's a Tie module you can use to recursively implement the Tie::CaseInsensitive module on hash values that are hash references (so you don't need to keep tie()ing).
    package Tie::RecursiveCI; use strict; use Tie::CaseInsensitive; use vars qw( @ISA ); @ISA = qw( Tie::CaseInsensitive ); sub STORE { my ($self, $key, $value) = @_; $self->{lc $key} = $value; if (UNIVERSAL::isa($value,'HASH')) { tie %{ $self->{lc $key} }, 'Tie::RecursiveCI'; } return $self->{lc $key}; }
    Here's a sample program using it:
    #!/usr/bin/perl use Tie::RecursiveCI; tie %hash, 'Tie::RecursiveCI'; $hash{foo} = 10; $hash{Foo} = 20; $hash{bar}{blat} = 30; $hash{BAR}{blAT} = 40; for (sort keys %hash) { print "$_ => $hash{$_}\n"; } for (sort keys %{ $hash{bAr} }) { print "$_ => $hash{bar}{$_}\n"; }
    And here's the output:
    bar => HASH(0xd67b0) foo => 20 blat => 40
Re: Case insensitivity in a hash... Futile effort?
by IndyZ (Friar) on Apr 19, 2000 at 06:28 UTC
    Of course, by far the easiest option is to lc() your keys before you insert them, and before you perform lookups in the hash...

    Brian
      Heh, then look at this :)
      %hash = map +(++$i % 2 ? lc($_) : $_), (foo => 'bar', This => 'that', THESE => 'those');
      Nifty-ish, I think.
        WHOA!

        Care to explain? I'm not even an acolyte yet! As a matter of fact, I've gotta go to perlfunc and see what this map guy does... :o)

        Cheers!