Modify key if it already exists - perl

I'm writing a piece of code that creates a HoAs and loops through for each key. The snippet below shows a basic example of the problem I'm having.
#!/usr/bin/perl
use strict;
use warnings;
my #array1 = qw (1 1 3 4 5); # Note that '1' appears twice
my #array2 = qw (a b c d e);
my #array3 = qw (6 7 8 9 10);
my #array4 = qw (f g h i j);
my %hash;
push #{$hash{$array1[$_]}}, [ $array2[$_], $array3[$_], $array4[$_] ] for 0 .. $#array1;
for my $key (sort keys %hash) {
my ($array2, $array3, $array4) = #{$hash{$key}[-1]};
print "[$key] $array2\t$array3\t$array4\n";
}
Output:
[1] b 7 g
[3] c 8 h
[4] d 9 i
[5] e 10 j
For the data I'm actually using (as opposed to this example) I have been using a key that I've just realised isn't unique, so, as above I end up overriding non-uniqe keys. I'm mainly using these values as keys in order to sort by them later.
My question is either:
A) I can perform the above task for each key unless (exists $hash{$array1}) in which case I can modify it
or
B) Is there a way to sort by those values, in which case I could use another, non-redundant key.
Thanks!

so, as above I end up overriding non-uniqe keys
You aren't. Let's print out the whole contents of that hash:
for my $key (sort { $a <=> $b } keys %hash) { # sort numerically!
for my $array (#{ $hash{$key} }) { # loop over all instead of $hash{$key}[-1] only
say "[$key] " . join "\t", #$array;
}
}
Output:
[1] a 6 f
[1] b 7 g
[3] c 8 h
[4] d 9 i
[5] e 10 j
You would be overriding the values if you were building the hash like
$hash{$array1[$_]} = [ $array2[$_], $array3[$_], $array4[$_] ] for 0 .. $#array1;
(And printing it as)
for my $key ( ... ) {
say "[$key] " . join "\t", #{ $hash{$key} };
}
That is, assigning instead of pushing.
If you want to keep the first value assigned to each key, use the //= operator (assign if undef).

Related

Hash of hashes from arrays in Perl

I am using two arrays (one that holds keys and one that holds values) to build a hash in Perl. I need to count and delete the repeated key/value pairs so that I have a unique hash. Then I have to build a hash of hashes of the form: ((key,value), count).
I am using this line to build the hash from the arrays.
#hash{#keys} = #values;
This will keep repeated key/value pairs in the hash which I do not want. Any help in creating the hash of hashes is appreciated. Thanks.
Let's say you arrays look like this:
my #keys = qw/a b c a a/;
my #values = qw/1 2 3 4 1/;
It's as easy as a simple for loop:
for ($i = 0; $i < scalar #keys; $i++) {
$hash{$keys[$i]}{$values[$i]} += 1;
}
You can then iterate over the inner values (the count) with a nested loop:
foreach my $key (keys %hash) {
while (my ($value, $count) = each %{ $hash{$key} } ) {
print "$key : $value = $count\n";
}
}
Which would produce:
c : 3 = 1
a : 4 = 1
a : 1 = 2
b : 2 = 1
You can sort the keys if you want to:
foreach my $key (sort keys %hash) {
# same as before
}
To get
a : 4 = 1
a : 1 = 2
b : 2 = 1
c : 3 = 1
You can use a hash of hashes, where each key hash a subhash of the form value => count.
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
my #keys = qw( k1 k1 k1 k2 k2 k3 k4 k4 k5);
my #values = qw( v1 v2 v2 v3 v4 v5 v6 v6 v7 );
my %hash;
for my $idx (0 .. $#keys) {
$hash{ $keys[$idx] }{ $values[$idx] }++;
}
print Dumper \%hash;

Printing groups of key/value pairs in hash

How can I print a hash in Perl, such that 3 key value pairs are printed on each line?
print %hash;
This will print key value pairs each in a line.
To display the hash, so "that 3[n] key value pairs are printed on each line", you can use a counter ($n) and % (modulo op) to determine when to print a "\n". Demo:
use Modern::Perl;
my %h = ();
for (0..7) {
$h{$_} = chr(65 + $_);
}
print %h, "\n";
my $cols = +$ARGV[0] || 5;
my $n = -$cols;
for my $key (keys %h) {
print $key, ' => ', $h{$key}, 0 == ++$n % $cols ? "\n" : "\t\t";
}
print $n % $cols ? "\n------" : "------";
output:
perl -w 31444449.pl 1
6G4E1B3D0A7H2C5F
6 => G
4 => E
1 => B
3 => D
0 => A
7 => H
2 => C
5 => F
------
perl -w 31444449.pl
6G4E1B3D0A7H2C5F
6 => G 4 => E 1 => B 3 => D 0 => A
7 => H 2 => C 5 => F
------
perl -w 31444449.pl 3
6G4E1B3D0A7H2C5F
6 => G 4 => E 1 => B
3 => D 0 => A 7 => H
2 => C 5 => F
------
Borodin's solutions, however, is simpler.
See mpapec answer for a much improved version.
A very simple way to do this is to copy all the keys and values to an array, and then print six (three pairs) of those at a time
use strict;
use warnings;
my %h = map { $_ => 1 } 'A' .. 'H';
my #kv = %h;
while ( my #row = splice #kv, 0, 6 ) {
print "#row\n";
}
output
B 1 C 1 A 1
D 1 E 1 G 1
F 1 H 1
You can use natatime from List::MoreUtils:
use List::MoreUtils qw/natatime/;
my $it = natatime 6, %ENV;
while (my #vals = $it->()) {
print "#vals\n";
}
List::MoreUtils isn't in core modules, you need to install it.
Thanks All. I tried this and it worked.
my #keylist=sort keys %hash;
my $counter=0;
foreach(#keylist){
#printing the key value pairs
printf "%-15s :%3d ",$_,$hash{$_};
$counter++;
if($counter==3){
$counter=0;
print "\n";
}
}
print "\n";
If you really just want to print hash and check the values for debugging or for analysing then use
use Data::Dumper;
print Dumper(\%hash);
This print hash keys and values at any n number of levels

Sort HOA by key first, then value

I've got a HoA that I'm setting up as follows (test example):
#!/usr/bin/perl -w
use strict;
my #array1 = qw (1 1 1 4 5); # Note '1' appears several times
my #array2 = qw (a b c d e);
my #array3 = qw (8 6 7 9 10);
my #array4 = qw (f g h i j);
my %hash;
push #{$hash{$array1[$_]}}, [ $array2[$_], $array3[$_], $array4[$_] ] for 0 .. $#array1;
for my $key (sort keys %hash) {
for my $array (# { $hash{$key} } ) {
my ($array2, $array3, $array4) = #$array;
print "[$key] $array2\t$array3\t$array4\n";
}
}
Output:
[1] a 8 f
[1] b 6 g
[1] c 7 h
[4] d 9 i
[5] e 10 j
What I want to do is be able to sort by the key first (as above) but in the instances where the key is the same, sort on a different array contained in the hash - e.g. numerically by the values in #array3, to give the desired output:
*
[1] b 6 g
[1] c 7 h
[1] a 8 f
[4] d 9 i
[5] e 10 j
Replace this line
for my $array (# { $hash{$key} } ) {
by
for my $array ( sort { $a->[1] <=> $b->[1] } #{ $hash{$key} } ) {
All the arrayrefs in #{ $hash{$key} } have the element you want to sort on at index 1. This sort block orders them in numerically ascending order by the second field.

Join keys and values in perl

I have a hash with the following key/value pair
4 => model1
2 => model2
I want the following string created from the above hash
4 X model1 , 2 X model2
I tried the following
my %hash,
foreach my $keys (keys %hash) {
my $string = $string . join(' X ',$keys,$hash{$keys});
}
print $string;
What I get is
4 X model12Xmodel2
How can I accomplish the desired result 4 X model1 , 2 X model2?
You could do:
my %hash = (4 => "model1", 2 => "model2");
my $str = join(", ", map { "$_ X $hash{$_}" } keys %hash);
print $str;
Output:
4 X model1, 2 X model2
How it works:
map { expr } list evaluates expr for every item in list, and returns the list that contains all the results of these evaluations. Here, "$_ X $hash{$_}" is evaluated for each key of the hash, so the result is a list of key X value strings. The join takes care of putting the commas in between each of these strings.
Note that your hash is a bit unusual if you're storing (item,quantity) pairs. It would usually be the other way around:
my %hash = ("model1" => 4, "model2" => 2);
my $str = join(", ", map { "$hash{$_} X $_" } keys %hash);
because with your scheme, you can't store the same quantity for two different items in your hash.
You can also modify your loop as follows:
my #tokens =();
foreach my $key (keys %hash) {
push #tokens, $string . join(' X ',$key, $hash{$key});
}
print join(', ' , #tokens);

Is there simple syntax for declaring multiple keys with one value in perl?

Is there a simple way to declare a hash with multiple keys which all point to the same value in perl?
Here is something similar to what I'm looking for (I don't actually know if this works or not):
my $hash = {
a, b, c => $valA,
d, e, f => $valB
};
such that....
print $hash->{a}; #prints $valA
print $hash->{b}; #prints $valA
print $hash->{c}; #prints $valA
print $hash->{d}; #prints $valB
print $hash->{e}; #prints $valB
print $hash->{f}; #prints $valB
You can write this:
my %hash;
$hash{$_} = $valA for qw(a b c);
$hash{$_} = $valB for qw(d e f);
No, there is no simple syntax for this. (Actually, => is documented to be an alias for , whose only formal effect is that it allows a bareword to the left of it even in strict mode).
The best you could do without defining your own subs might be something like
#hash{qw(a b c)} = ($valA) x 3 ;
#hash(qw(d e f)} = ($valB) x 3 ;
I like to use a hash slice on one side and the list replication operator on the other. I use the scalar value of the keys array to figure out how many values to replicate:
#hash{ #keys } = ($value) x #keys;
There is no built in syntax, but you can always write your own:
my $value = sub {map {$_ => $_[1]} #{$_[0]}};
my $hash = {
[qw(a b c)]->$value('valA'),
[qw(d e f)]->$value('valB'),
};
say join ', ' => map "$_: $$hash{$_}", sort keys %$hash;
# a: valA, b: valA, c: valA, d: valB, e: valB, f: valB
If you are going to be doing this a lot, you might want to look at Hash::Util's hv_store function, which allows you to load multiple keys with exactly the same memory location.
You can use hash slice assignment:
my $hash = {};
#$hash{a,b,c} = ($valA) x 3;
#$hash{d,e,f} = ($valB) x 3;
Assignment can be done with statements too, such as with map. Here, map will expand into two lists.
my $hash = {
( map { $_ => $valA } ('a' .. 'c') ),
( map { $_ => $valB } ('d' .. 'f') ),
};
Yeah, as Henning Makholm pointed out, there is no direct shortcut, since => is an alias for ,. The closest thing to a shortcut I can think of is:
foreach('a','b','c')
{
$hash->{$_}=$valA;
}
foreach('d','e','f')
{
$hash->{$_}=$valB;
}