Spliting values and reversing a hash

Discussion in 'Perl Misc' started by mlwollman@gmail.com, Oct 4, 2006.

  1. Guest

    Hello all,

    I'm very, very new to Perl, and I have a question I can't quite find
    the answer in the O'Reilly Perl books (Learning, Programming, and
    Cookbook). Any assistance would be helpful.

    I have a hash with key value pairs like:
    1 => Chocolate,Vanilla,Rocky Road
    2 => Strawberry,Vanilla, Pistachio
    3 => Cookie Dough,Chocolate
    4 => Strawberry,Pistachio

    And I need to transform it to:
    Chocolate => 1,3
    Vanilla => 1,2
    Rocky Road => 1
    Strawberry => 2,4
    Pistachio =>2,4
    Cookie Dough => 3

    What's a good, simple way to do that?

    I thought about using while each and changing the value to an array and
    foreach array element make a new has with the array element as a key
    and the old key join() any existing values, I think. I'm pretty
    confused now.

    Thank You,
    Matt
     
    , Oct 4, 2006
    #1
    1. Advertising

  2. Bob Walton Guest

    wrote:
    ....
    > I have a hash with key value pairs like:
    > 1 => Chocolate,Vanilla,Rocky Road
    > 2 => Strawberry,Vanilla, Pistachio
    > 3 => Cookie Dough,Chocolate
    > 4 => Strawberry,Pistachio
    >
    > And I need to transform it to:
    > Chocolate => 1,3
    > Vanilla => 1,2
    > Rocky Road => 1
    > Strawberry => 2,4
    > Pistachio =>2,4
    > Cookie Dough => 3
    >
    > What's a good, simple way to do that?
    >
    > I thought about using while each and changing the value to an array and
    > foreach array element make a new has with the array element as a key
    > and the old key join() any existing values, I think. I'm pretty
    > confused now.

    ....
    > Matt
    >


    Here is one way, which is pretty much what you described, except I
    elected to use strings rather than arrays since I think you said you
    wanted strings:

    use warnings;
    use strict;
    use Data::Dumper;
    my %h; #original hash
    $h{1}='Chocolate,Vanilla,Rocky Road';
    $h{2}='Strawberry,Vanilla,Pistachio';
    $h{3}='Cookie Dough,Chocolate';
    $h{4}='Strawberry,Pistachio';
    my %h1; #flavors hash
    for(sort keys %h){
    my @v=split /,/,$h{$_}; #separate flavors
    for my $v(@v){
    $h1{$v}.="$_,"; #add number to flavor
    }
    }
    for(keys %h1){
    $h1{$_}=~s/,$//; #get rid of trailing commas
    }
    print Dumper(\%h1);

    --
    Bob Walton
    Email: http://bwalton.com/cgi-bin/emailbob.pl
     
    Bob Walton, Oct 4, 2006
    #2
    1. Advertising

  3. wrote:
    >
    > I'm very, very new to Perl, and I have a question I can't quite find
    > the answer in the O'Reilly Perl books (Learning, Programming, and
    > Cookbook). Any assistance would be helpful.
    >
    > I have a hash with key value pairs like:
    > 1 => Chocolate,Vanilla,Rocky Road
    > 2 => Strawberry,Vanilla, Pistachio
    > 3 => Cookie Dough,Chocolate
    > 4 => Strawberry,Pistachio
    >
    > And I need to transform it to:
    > Chocolate => 1,3
    > Vanilla => 1,2
    > Rocky Road => 1
    > Strawberry => 2,4
    > Pistachio =>2,4
    > Cookie Dough => 3
    >
    > What's a good, simple way to do that?


    $ perl -le'
    use Data::Dumper;

    my %hash = (
    1 => [ "Chocolate", "Vanilla", "Rocky Road" ],
    2 => [ "Strawberry", "Vanilla", "Pistachio" ],
    3 => [ "Cookie Dough", "Chocolate" ],
    4 => [ "Strawberry", "Pistachio" ],
    );

    print Dumper \%hash;

    for my $key ( keys %hash ) {
    for my $flavour ( @{ delete $hash{ $key } } ) {
    push @{ $hash{ $flavour } }, $key;
    }
    }

    print Dumper \%hash;
    '
    $VAR1 = {
    '4' => [
    'Strawberry',
    'Pistachio'
    ],
    '1' => [
    'Chocolate',
    'Vanilla',
    'Rocky Road'
    ],
    '3' => [
    'Cookie Dough',
    'Chocolate'
    ],
    '2' => [
    'Strawberry',
    'Vanilla',
    'Pistachio'
    ]
    };

    $VAR1 = {
    'Chocolate' => [
    '1',
    '3'
    ],
    'Cookie Dough' => [
    '3'
    ],
    'Strawberry' => [
    '4',
    '2'
    ],
    'Rocky Road' => [
    '1'
    ],
    'Pistachio' => [
    '4',
    '2'
    ],
    'Vanilla' => [
    '1',
    '2'
    ]
    };



    John
    --
    Perl isn't a toolbox, but a small machine shop where you can special-order
    certain sorts of tools at low cost and in short order. -- Larry Wall
     
    John W. Krahn, Oct 4, 2006
    #3
  4. Guest

    Thank You, Bob. That worked perfectly! Much appreciated.
    Matt

    Bob Walton wrote:
    > wrote:
    > ...
    > > I have a hash with key value pairs like:
    > > 1 => Chocolate,Vanilla,Rocky Road
    > > 2 => Strawberry,Vanilla, Pistachio
    > > 3 => Cookie Dough,Chocolate
    > > 4 => Strawberry,Pistachio
    > >
    > > And I need to transform it to:
    > > Chocolate => 1,3
    > > Vanilla => 1,2
    > > Rocky Road => 1
    > > Strawberry => 2,4
    > > Pistachio =>2,4
    > > Cookie Dough => 3
    > >
    > > What's a good, simple way to do that?
    > >
    > > I thought about using while each and changing the value to an array and
    > > foreach array element make a new has with the array element as a key
    > > and the old key join() any existing values, I think. I'm pretty
    > > confused now.

    > ...
    > > Matt
    > >

    >
    > Here is one way, which is pretty much what you described, except I
    > elected to use strings rather than arrays since I think you said you
    > wanted strings:
    >
    > use warnings;
    > use strict;
    > use Data::Dumper;
    > my %h; #original hash
    > $h{1}='Chocolate,Vanilla,Rocky Road';
    > $h{2}='Strawberry,Vanilla,Pistachio';
    > $h{3}='Cookie Dough,Chocolate';
    > $h{4}='Strawberry,Pistachio';
    > my %h1; #flavors hash
    > for(sort keys %h){
    > my @v=split /,/,$h{$_}; #separate flavors
    > for my $v(@v){
    > $h1{$v}.="$_,"; #add number to flavor
    > }
    > }
    > for(keys %h1){
    > $h1{$_}=~s/,$//; #get rid of trailing commas
    > }
    > print Dumper(\%h1);
    >
    > --
    > Bob Walton
    > Email: http://bwalton.com/cgi-bin/emailbob.pl
     
    , Oct 4, 2006
    #4
  5. wrote:
    > John W. Krahn wrote:
    >>
    >>for my $key ( keys %hash ) {
    >> for my $flavour ( @{ delete $hash{ $key } } ) {
    >> push @{ $hash{ $flavour } }, $key;
    >> }
    >> }

    >
    > If I recall correctly, it isn't a good idea to delete
    > and add keys to a hash while iterating over it.


    "for my $key ( keys %hash )" creates a list of the hash keys in memory and any
    modifications to %hash inside the loop won't affect the contents of that list.

    perldoc -f delete


    John
    --
    Perl isn't a toolbox, but a small machine shop where you can special-order
    certain sorts of tools at low cost and in short order. -- Larry Wall
     
    John W. Krahn, Oct 4, 2006
    #5
  6. Bob Walton wrote:
    >
    > Here is one way, which is pretty much what you described, except I
    > elected to use strings rather than arrays since I think you said you
    > wanted strings:
    >
    > use warnings;
    > use strict;
    > use Data::Dumper;
    > my %h; #original hash
    > $h{1}='Chocolate,Vanilla,Rocky Road';
    > $h{2}='Strawberry,Vanilla,Pistachio';
    > $h{3}='Cookie Dough,Chocolate';
    > $h{4}='Strawberry,Pistachio';
    > my %h1; #flavors hash
    > for(sort keys %h){
    > my @v=split /,/,$h{$_}; #separate flavors
    > for my $v(@v){
    > $h1{$v}.="$_,"; #add number to flavor
    > }
    > }
    > for(keys %h1){
    > $h1{$_}=~s/,$//; #get rid of trailing commas
    > }
    > print Dumper(\%h1);


    Another way to do it:

    $ perl -le'
    use Data::Dumper;
    my %hash = (
    1 => "Chocolate,Vanilla,Rocky Road",
    2 => "Strawberry,Vanilla, Pistachio",
    3 => "Cookie Dough,Chocolate",
    4 => "Strawberry,Pistachio",
    );
    print Dumper \%hash;
    for my $key ( keys %hash ) {
    for my $flavour ( split /\s*,\s*/, delete $hash{ $key } ) {
    push @{ $hash{ $flavour } }, $key;
    }
    }
    $_ = join ",", @$_ for values %hash;
    print Dumper \%hash;
    '
    $VAR1 = {
    '4' => 'Strawberry,Pistachio',
    '1' => 'Chocolate,Vanilla,Rocky Road',
    '3' => 'Cookie Dough,Chocolate',
    '2' => 'Strawberry,Vanilla, Pistachio'
    };

    $VAR1 = {
    'Chocolate' => '1,3',
    'Cookie Dough' => '3',
    'Strawberry' => '4,2',
    'Rocky Road' => '1',
    'Pistachio' => '4,2',
    'Vanilla' => '1,2'
    };



    John
    --
    Perl isn't a toolbox, but a small machine shop where you can special-order
    certain sorts of tools at low cost and in short order. -- Larry Wall
     
    John W. Krahn, Oct 4, 2006
    #6
  7. Keith Keller Guest

    On 2006-10-04, John W. Krahn <> wrote:
    > wrote:
    >> John W. Krahn wrote:
    >>>
    >>>for my $key ( keys %hash ) {
    >>> for my $flavour ( @{ delete $hash{ $key } } ) {
    >>> push @{ $hash{ $flavour } }, $key;
    >>> }
    >>> }

    >>
    >> If I recall correctly, it isn't a good idea to delete
    >> and add keys to a hash while iterating over it.

    >
    > "for my $key ( keys %hash )" creates a list of the hash keys in memory and any
    > modifications to %hash inside the loop won't affect the contents of that list.
    >
    > perldoc -f delete


    While the actual for loop is documented in perldoc -f delete, the
    creating a new list in memory aspect is more explicitly documented in
    perldoc -f keys.

    --keith

    --
    -francisco.ca.us
    (try just my userid to email me)
    AOLSFAQ=http://www.therockgarden.ca/aolsfaq.txt
    see X- headers for PGP signature information
     
    Keith Keller, Oct 4, 2006
    #7
  8. Guest

    John W. Krahn wrote:
    > wrote:


    (snipped)

    > >
    > > If I recall correctly, it isn't a good idea to delete
    > > and add keys to a hash while iterating over it.

    >
    > "for my $key ( keys %hash )" creates a list of the hash keys in memory and any
    > modifications to %hash inside the loop won't affect the contents of that list.
    >
    > perldoc -f delete



    Sorry, my recollection was indeed faulty.
    I was thinking of the 'each' function used
    to iterate over a hash.

    --
    Regards,
    Steven
     
    , Oct 4, 2006
    #8
  9. Mirco Wahab Guest

    Thus spoke Michele Dondi (on 2006-10-04 11:40):
    > It's up to you to modify accordingly to your actual
    > needs.
    >
    > my %rev;
    > for my $k (keys %hash) {
    > push @{ $rev{$_} }, $k for split /,/, $hash{$k};
    > }
    >


    Hi Michele, to re-stringify the numbers array *is* just
    major part of the fun ;-)

    BTW: somebody *had* eventually to come up with it ...

    my %h=(1 => 'Chocolate,Vanilla,Rocky Road',
    2 => 'Strawberry,Vanilla, Pistachio',
    3 => 'Cookie Dough,Chocolate',
    4 => 'Strawberry,Pistachio');

    my %r;

    (($_=join '',%h)=~s/\s//g),s/(\d)([^\d]+)/ $r{$_}.=','x!!defined($r{$_}).$1for(split',',$2)/eg;

    ;-))

    Regards

    M.
     
    Mirco Wahab, Oct 4, 2006
    #9
  10. Mirco Wahab Guest

    Thus spoke Michele Dondi (on 2006-10-04 12:49):
    > On Wed, 04 Oct 2006 12:23:15 +0200, Mirco Wahab
    > <> wrote:
    >
    >>> push @{ $rev{$_} }, $k for split /,/, $hash{$k};

    >> re-stringify the numbers array

    >
    > It's not that hard to do so in the first place:
    >
    > $rev{$_} .= defined $rev{$_} ? ",$k" : $k
    > for split /,/, $hash{$k};


    Aehmm, you didn't pull the
    leading \s+ from keys, eg. ' Pistachio'.

    Why did you choose the explicit form of:

    $rev{$_} .= ','x defined $rev{$_} . $k

    I guess it's not always ok to expect that
    'defined' gets evaluated to 0/1 !?

    Regards

    Mirco


    BTW. some kind of a perlgolf version
    anybody - for learning purpose? (the
    hash has to be %h):

    @_=%h;%h=(),($_="@_")
    =~s/\s//g,s/(\d+)
    ([^\d]+)/$h{$_}
    .=','x defined
    ($h{$_}).$1
    for(split
    ',',$2)
    /egx
    ;
     
    Mirco Wahab, Oct 4, 2006
    #10
  11. -berlin.de Guest

    Mirco Wahab <> wrote in comp.lang.perl.misc:
    > Thus spoke Michele Dondi (on 2006-10-04 12:49):
    > > On Wed, 04 Oct 2006 12:23:15 +0200, Mirco Wahab
    > > <> wrote:
    > >
    > >>> push @{ $rev{$_} }, $k for split /,/, $hash{$k};
    > >> re-stringify the numbers array

    > >
    > > It's not that hard to do so in the first place:
    > >
    > > $rev{$_} .= defined $rev{$_} ? ",$k" : $k
    > > for split /,/, $hash{$k};

    >
    > Aehmm, you didn't pull the
    > leading \s+ from keys, eg. ' Pistachio'.
    >
    > Why did you choose the explicit form of:


    Did you mean "Why did you *not* choose..."?

    > $rev{$_} .= ','x defined $rev{$_} . $k
    >
    > I guess it's not always ok to expect that
    > 'defined' gets evaluated to 0/1 !?


    In Perl a boolean false is a dual-valued scalar. Used as a number it
    is 0, used as a string it is "". A boolean true is indistinguishable
    from 1. With this kind of boolean the construct works as intended.

    The problem is that

    - the behavior seems to be nowhere documented and
    - there are exceptions: the short-circuiting boolean operators || and
    && can return anything.

    Anno
     
    -berlin.de, Oct 4, 2006
    #11
  12. Dr.Ruud Guest

    mlwollman schreef:

    > I have a hash with key value pairs like:
    > 1 => Chocolate,Vanilla,Rocky Road
    > 2 => Strawberry,Vanilla, Pistachio
    > 3 => Cookie Dough,Chocolate
    > 4 => Strawberry,Pistachio
    >
    > And I need to transform it to:
    > Chocolate => 1,3
    > Vanilla => 1,2
    > Rocky Road => 1
    > Strawberry => 2,4
    > Pistachio =>2,4
    > Cookie Dough => 3
    >
    > What's a good, simple way to do that?
    >
    > I thought about using while each and changing the value to an array
    > and foreach array element make a new has with the array element as a
    > key and the old key join() any existing values, I think. I'm pretty
    > confused now.


    perl -MData::Dumper -wle '
    my %in = (
    1 => ["Chocolate", "Vanilla", "Rocky Road"],
    2 => ["Strawberry", "Vanilla", "Pistachio"],
    3 => ["Cookie Dough", "Chocolate"],
    4 => ["Strawberry", "Pistachio"],
    );
    my %out;
    for my $k (keys %in) {
    for (@{$in{$k}}) {
    $out{$_} .= $out{$_} ? ",$k" : $k;
    }
    }
    print Dumper \%in, \%out;
    '

    --
    Affijn, Ruud

    "Gewoon is een tijger."
     
    Dr.Ruud, Oct 4, 2006
    #12
  13. Mirco Wahab Guest

    Thus spoke Michele Dondi (on 2006-10-04 16:01):
    > On Wed, 04 Oct 2006 13:53:56 +0200, Mirco Wahab
    >>Why did you choose the explicit form of:

    > Because I didn't choose it! Jokes apart...


    Right, .. of course ... ;-)

    >> $rev{$_} .= ','x defined $rev{$_} . $k

    > ...because I didn't thought of it. And I'm not golfing, in which case
    > the chances of picking it up would have been probably higher.


    Yeah, I was so into it this afternoon ;-)

    I made even a version (all strings), where
    the input strings draw light onto the
    algorithm, its shadow containing the
    control output ...

    (... but really, that's enough for now ...)

    Regards


    M.

    ( maybe someone has a funnier idea )

    ------ [cut here] 8< -----------

    use strict;
    use warnings;
    use Data::Dumper;

    my %h = (
    4 => 'Strawberry,Pistachio',
    3 => 'Cookie Dough,Chocolate',
    1 => 'Chocolate,Vanilla,Rocky Road',
    2 => 'Strawberry,Vanilla, Pistachio'); # <--light source ends here

    @_=%h;%h=(),($_="@_")
    =~s/\s//g,s/(\d+)
    ([^\d]+)/$h{$_}
    .=','x defined
    ($h{$_}).$1
    for(split
    ',',$2)
    /egx
    # <-- shadow starts here
    ;print
    Dumper
    \%h;%h = @_;
    print Dumper \%h;
     
    Mirco Wahab, Oct 4, 2006
    #13
  14. Ted Zlatanov Guest

    On 3 Oct 2006, wrote:

    > I have a hash with key value pairs like:
    > 1 => Chocolate,Vanilla,Rocky Road
    > 2 => Strawberry,Vanilla, Pistachio
    > 3 => Cookie Dough,Chocolate
    > 4 => Strawberry,Pistachio
    >
    > And I need to transform it to:
    > Chocolate => 1,3
    > Vanilla => 1,2
    > Rocky Road => 1
    > Strawberry => 2,4
    > Pistachio =>2,4
    > Cookie Dough => 3


    Try Tie::Hash::TwoWay (I wrote it) on CPAN, which implements two-way
    hash lookups between keys and values. It may simplify this job for
    you if you have to do it repeatedly.

    Ted
     
    Ted Zlatanov, Oct 4, 2006
    #14
  15. John W. Krahn wrote:
    > wrote:
    >> John W. Krahn wrote:
    >>> for my $key ( keys %hash ) {
    >>> for my $flavour ( @{ delete $hash{ $key } } ) {
    >>> push @{ $hash{ $flavour } }, $key;
    >>> }
    >>> }

    >> If I recall correctly, it isn't a good idea to delete
    >> and add keys to a hash while iterating over it.

    >
    > "for my $key ( keys %hash )" creates a list of the hash keys in memory and any
    > modifications to %hash inside the loop won't affect the contents of that list.
    >
    > perldoc -f delete

    ^^^^^^
    ITYM perldoc -f keys:

    The returned values are copies of the original keys
    in the hash, so modifying them will not affect the
    original hash. Compare "values".


    --
    Charles DeRykus
     
    Charles DeRykus, Oct 4, 2006
    #15
  16. -berlin.de Guest

    Charles DeRykus <> wrote in comp.lang.perl.misc:
    > John W. Krahn wrote:
    > > wrote:
    > >> John W. Krahn wrote:
    > >>> for my $key ( keys %hash ) {
    > >>> for my $flavour ( @{ delete $hash{ $key } } ) {
    > >>> push @{ $hash{ $flavour } }, $key;
    > >>> }
    > >>> }
    > >> If I recall correctly, it isn't a good idea to delete
    > >> and add keys to a hash while iterating over it.

    > >
    > > "for my $key ( keys %hash )" creates a list of the hash keys in memory and any
    > > modifications to %hash inside the loop won't affect the contents of that list.
    > >
    > > perldoc -f delete

    > ^^^^^^
    > ITYM perldoc -f keys:
    >
    > The returned values are copies of the original keys
    > in the hash, so modifying them will not affect the
    > original hash. Compare "values".


    If I read that right, it is about the keys not being lvalues the
    way values are. That is one thing, changing the hash while looping
    over its keys is another.

    I believe it is safe to change the hash in

    for my $key ( keys %hash ) {
    # changes to %hash here
    }

    but the behavior of

    for my $i ( 1 .. 100_000_000 ) {

    can make a programmer wonder. If the loop over keys %hash were
    transformed to use an implicit iterator like the second one
    changing the hash might be unsafe.

    Anno
     
    -berlin.de, Oct 4, 2006
    #16
  17. Dr.Ruud Guest

    Michele Dondi schreef:
    > Dr.Ruud:


    >> perl -MData::Dumper -wle '
    >> my %in = (
    >> 1 => ["Chocolate", "Vanilla", "Rocky Road"],
    >> 2 => ["Strawberry", "Vanilla", "Pistachio"],
    >> 3 => ["Cookie Dough", "Chocolate"],
    >> 4 => ["Strawberry", "Pistachio"],
    >> );
    >> my %out;
    >> for my $k (keys %in) {
    >> for (@{$in{$k}}) {
    >> $out{$_} .= $out{$_} ? ",$k" : $k;
    >> }
    >> }
    >> print Dumper \%in, \%out;
    >> '

    >
    > Funnily enough you chose exactly the other way around what I did, i.e.
    > arrayrefs for the input and strings for the output.


    I created the code last night, but hesitated to post it because I just
    didn't like it. When I saw the other reactions, I felt that it was
    different enough, so I posted it after all.

    --
    Affijn, Ruud

    "Gewoon is een tijger."
     
    Dr.Ruud, Oct 4, 2006
    #17
  18. -berlin.de wrote:
    > Charles DeRykus <> wrote in comp.lang.perl.misc:
    >> John W. Krahn wrote:
    >>> wrote:
    >>>> John W. Krahn wrote:
    >>>>> for my $key ( keys %hash ) {
    >>>>> for my $flavour ( @{ delete $hash{ $key } } ) {
    >>>>> push @{ $hash{ $flavour } }, $key;
    >>>>> }
    >>>>> }
    >>>> If I recall correctly, it isn't a good idea to delete
    >>>> and add keys to a hash while iterating over it.
    >>> "for my $key ( keys %hash )" creates a list of the hash keys in memory and any
    >>> modifications to %hash inside the loop won't affect the contents of that list.
    >>>
    >>> perldoc -f delete

    >> ^^^^^^
    >> ITYM perldoc -f keys:
    >>
    >> The returned values are copies of the original keys
    >> in the hash, so modifying them will not affect the
    >> original hash. Compare "values".

    >
    > If I read that right, it is about the keys not being lvalues the
    > way values are. That is one thing, changing the hash while looping
    > over its keys is another.
    >
    > I believe it is safe to change the hash in
    >
    > for my $key ( keys %hash ) {
    > # changes to %hash here
    > }
    >
    > but the behavior of
    >
    > for my $i ( 1 .. 100_000_000 ) {
    >
    > can make a programmer wonder. If the loop over keys %hash were
    > transformed to use an implicit iterator like the second one
    > changing the hash might be unsafe.
    >


    That sounds right. I notice that 5.8.x (in contrast to earlier
    versions) now clarifies that the most recently returned `each'
    item can be safely deleted.


    --
    Charles DeRykus
     
    Charles DeRykus, Oct 5, 2006
    #18
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Replies:
    2
    Views:
    820
    Kevin Spencer
    Oct 27, 2003
  2. rp
    Replies:
    1
    Views:
    590
    red floyd
    Nov 10, 2011
  3. Sean Hussey
    Replies:
    9
    Views:
    141
    Robert Klemme
    Nov 10, 2006
  4. Stuart Clarke

    String spliting and inclusion

    Stuart Clarke, Jul 21, 2009, in forum: Ruby
    Replies:
    16
    Views:
    251
    Harry Kakueki
    Jul 23, 2009
  5. Ne Scripter

    Spliting hash using map!

    Ne Scripter, Oct 12, 2009, in forum: Ruby
    Replies:
    5
    Views:
    120
    7stud --
    Oct 12, 2009
Loading...

Share This Page