3

As a beginner I have what I think is a rather complicated problem I am hoping someone could help with.

I have the following text file (tab delminated)...

FILE1.txt

Dog     Big     
Dog     Medium     
Dog     Small     
Rabbit     Huge     
Rabbit     Tiny     
Rabbit     Middle    
Donkey     Massive    
Donkey     Little   
Donkey     Gigantic

I need to read FILE1.txt into a hash reference to get something like the following... (using Data::Dumper)

$VAR1 = {
        'Dog' => {
                 'Big',
                 'Medium',
                 'Small'
                 },
        'Rabbit  => {
                    'Huge',
                    'Tiny',
                    'Middle'
                    },
        'Donkey  => {
                    'Massive',
                    'Little',
                    'Gigantic'
                    },                               
        };

The problem I am having:

I then need to loop through each branch of the hash reference one at a time, I will use the value from the hash reference to check if this matches my keyword, if so it will then return it's corresponding key.... for example...

What I need it to do:

my $keyword == "Little";

Dog->Big 
if 'Big' matches my keyword then return $found = Dog
else go to the next branch
Rabbit->Huge
if 'Huge' matches my keyword then return $found = Rabbit
else go to the next branch
Donkey->Massive
if 'Massive' matches my keyword then return $found = Donkey
else go to the next branch (which is Dog again, but the second element this time)
Dog->Medium
if 'Medium' matches my keyword then return $found = Dog
else go to the next branch
Rabbit->Tiny
if 'Tiny' matches my keyword then return $found = Rabbit
else go the the next branch
Donkey->Little
if 'Little' matches my keyword then return $found = Donkey

..... and so on until the keyword is found or we reach the end of the hash reference

This is the kind of thing I am trying to achieve but don't know how to go about doing this, or whether a hash reference is the best way to do this, or if it can even be done with a hash/hash reference?

your help with this is much appreciated, thanks

yonetpkbji
  • 1,019
  • 2
  • 21
  • 35
  • If you are going to do the lookup by the adjectives (Little, Huge, Tiny, etc), why not build your hash with them as keys instead? – TLP Apr 09 '13 at 16:17
  • @TLP - but it is the Dog, Rabbit or Donkey part thats associated with the adjective that I actually need at the end of it, thanks – yonetpkbji Apr 09 '13 at 16:22
  • Yes. I am aware of that. You *do* know that you can store the value "Dog" under the key "Little" just as easily as you can store the value "Little" under the key "Dog", right? – TLP Apr 09 '13 at 16:49
  • 1
    1) Your "Data::Dumper" hash example is incorrect. You're showing a list under each key, not a hash. 2) How about building 2 hashes. One that goes from animal->[list of adjectives] and one that goes from adjective->animal. – imran Apr 09 '13 at 17:00
  • So the requirement is to do a breadth-first search of a Perl data structure? – mob Apr 09 '13 at 17:06
  • 1
    @mob: no, the requirement is to do all the first elements for each top level key, then all the second elements. the sub-hashes need to be ordered, it seems, so should be arrays, not hashes. – ysth Apr 09 '13 at 17:13
  • @TLP - sorry yes, I understand what you mean now, would that be a better way to do it then?, thanks – yonetpkbji Apr 09 '13 at 17:52
  • @perl-user It would make it easier to look up which animal goes with which adjective. You can do both, if you want, use two hashes. – TLP Apr 09 '13 at 17:55
  • I agree with ysh, the second level should be arrays – Vorsprung Apr 09 '13 at 19:17
  • @TLP - How would the method using two hashes work? thanks – yonetpkbji Apr 09 '13 at 22:53

2 Answers2

1

To critique my own answer: the structuring of the part that does the search could be better. And maybe it is pointless even using an ordered hash as the search is through a linear list. Maybe it should be an array of arrays

   use strict;
    use warnings;
    use Tie::IxHash;
    #open file
    open(my $fh,"ani.txt") ||die $!;

    #make an ordered hash
    tie my %sizes, 'Tie::IxHash';


    #read file into hash of arrays
    while(<$fh>) {
       (my $animal,my $size)=split(/\s+/);
       if (!exists($sizes{$animal})) {
           $sizes{$animal} = [$size];
       } else { 
           push @{$sizes{$animal}},$size;
       }
    }

    my $keyword="Little";
    my $running=1;
    my $depth=0;
    while( $running ) {
      $running = 0;
      for my $search (keys %sizes) {
          next if ($depth > @{$sizes{$search}});
          $running = 1;
          if ($keyword eq $sizes{$search}[$depth]) {
              print "FOUND!!!!!! $search $depth";
              exit(0);
          }
      }
      $depth++;
    }

Here is another version of a solution to the stated problem. To solve the actual problem given there is no need to store anything except the first "size" key for each animal in a hash

This hash can then be trivally used to look up the animal

use strict;
use warnings;
open(my $fh,"ani.txt") ||die $!;

my %animals;

#read file into hash
while(<$fh>) {
   (my $animal,my $size)=split(/\s+/);
   #only add the animal the first time the size is found
   if (!exists($animals{$size})) {
       $animals{$size} = $animal;
   } 
}

my $keyword="Little";
print "animal is ", $animals{$keyword};
Vorsprung
  • 32,923
  • 5
  • 39
  • 63
1

Choosing proper data structure is often key step to the solution, but first of all you should define what you are trying achieve. What is overall goal? For example I have this data file and in mine application/program I need frequently ask for this information. It is crucial to ask proper question because for example if you don't need ask frequently for keyword it doesn't make sense creating hash at all.

 perl -anE'say $F[0] if $F[1] eq "Little"' FILE1.txt

Yes it is that simple. Look in perlrun manpage for switches and what they mean and how to do same thing in bigger application.

If you need frequently ask for this question you should arrange your data in way which helps you and not in way you have to battle with.

use strict;
use warnings;
use feature qw(say);
use autodie;

open my $f, '<', 'FILE1.txt';
my %h;
while(<$f>) {
    chomp;
    my ($animal, $keyword) = split' ';
    $h{$keyword} = $animal unless exists $h{$keyword};
}

close $f;

for my $keyword (qw(Little Awkward Small Tiny)) {
    say $h{$keyword} ? "$keyword $h{$keyword}" : "keyword $keyword not found";
}

But if you still insist you want to traverse hash you can do it but you has been warned.

open my $f, '<', 'FILE1.txt';
my %h;
while (<$f>) {
    chomp;
    my ( $animal, $keyword ) = split ' ';
    push @{ $h{$animal} }, $keyword;
}

close $f;

KEYWORD:
for my $keyword (qw(Little Awkward Small Tiny)) {
    for my $animal (keys %h) {
        for my $k (@{$h{$animal}}) {
            if($k eq $keyword) {
                say "$keyword $animal";
                next KEYWORD;
            }
        }
    }
    say "keyword $keyword not found";
}
Hynek -Pichi- Vychodil
  • 26,174
  • 5
  • 52
  • 73