Spliting values and reversing a hash

M

mlwollman

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
 
B

Bob Walton

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);
 
J

John W. Krahn

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
 
J

John W. Krahn

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
 
J

John W. Krahn

Bob said:
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
 
K

Keith Keller

"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
 
A

attn.steven.kuo

John said:
(e-mail address removed) wrote:
(snipped)


"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.
 
M

Mirco Wahab

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.
 
M

Mirco Wahab

Thus spoke Michele Dondi (on 2006-10-04 12:49):
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
;
 
A

anno4000

Mirco Wahab said:
Thus spoke Michele Dondi (on 2006-10-04 12:49):

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
 
D

Dr.Ruud

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;
'
 
M

Mirco Wahab

Thus spoke Michele Dondi (on 2006-10-04 16:01):
Because I didn't choose it! Jokes apart...

Right, .. of course ... ;-)
...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;
 
T

Ted Zlatanov

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
 
C

Charles DeRykus

John said:
"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".
 
A

anno4000

Charles DeRykus said:
^^^^^^
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
 
D

Dr.Ruud

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.
 
C

Charles DeRykus

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.
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,900
Latest member
Nell636132

Latest Threads

Top