I need help regarding perl hash of array - perl

I have a hash of array and two variables -1 list and 1-scalar value
How do I do this?
I want two things out of this. First is a list for every key.
Second is I need $b to have the value of last array element of every key
%abc=(
0=>[1,2,3],
1=>[1,5]
);
#a;
$b;
for key 0 i need #a to have [1,2] and for key 1 i need #a to have [1].
for 0 key i need $b to have value 3 and for key 1 i need $b to have value 5

I understand you want #a to hold all values but last, and $b to just hold the last value. How about:
use feature 'say';
my %abc = (0 => [1,2,3], 1 => [1,5]);
for my $key (keys %abc) {
my #a = #{$abc{$key}};
my $b = pop #a;
say "#a / $b"
}

I'd almost certainly use something like the other solution here. But here's a different solution that a) uses values() instead of keys() and b) uses reverse() to simplify(?!) the assignments. Don't do this :-)
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
my %abc = (0 => [1,2,3], 1 => [1,5]);
for (values %abc) {
my ($b, #a) = reverse #$_;
#a = reverse #a;
say "#a / $b";
}

Related

perl: Use map and foreach at once?

I was wondering if it is possible to make a hash assigning its keys and values at once. Or in general use map and for at one line:
#!/usr/bin/perl
%h = map {$_, $i} qw[a b c] for $i (1..3)
But unfortunatelly not => Number found where operator expected, meant number in the parenthesis. So my question is why am I not able to make double loop by this way? And how otherwise would someone assign hash keys to values (and I dont concern something like $h = {a=>1,b=>2,c=>3} but rather assigning %h = (#keys = #values) ... in other words, how to assign hash by:
2 arrays only (#keys,#values), no scalars
At once (at one line - without block)
Is it even possible in perl?
Populating a hash is simply a matter of assigning a list with alternating keys and values, so you just have to construct the list using the two arrays in an alternating fashion.
use strict;
use warnings;
my #keys = qw(a b c);
my #values = 1..3;
my %h = map { ($keys[$_], $values[$_]) } 0..$#keys;
List::UtilsBy provides a useful abstraction for this in zip_by.
use List::UtilsBy 'zip_by';
my %h = zip_by { #_ } \#keys, \#values;
But actually it's even easier to use slice assignment. Though you technically can't do this in the same statement as the declaration, it's by far the neatest option:
my %h;
#h{#keys} = #values;
Use List::MoreUtils 'zip' or add your own since that module is not a core module:
sub zip(\##){map{($_[0][$_-1],$_[$_])}1..#{$_[0]}}
my %h = zip #keys, #values;
Well, the question is not very clear on 'why?' -- same can be achieved with following code
use strict;
use warnings;
use Data::Dumper;
my $debug = 1;
my %h;
#h{qw(a b c)} = (1..3);
print Dumper(\%h) if $debug;

Multiply all values in hash together in Perl?

I would like to multiply all the values of a hash together without having to call the specific elements. E.g. I DON'T want to do $hash{'kv1'} * $hash{'kv2'} * $hash{'kv3'} because I won't know the number of elements in the hash to do this.
I'm guessing there is a simple and efficient function to do this, maybe using each or something, but I can't find any examples to follow. Any ideas?
Start at 1 (since 1 multiplied by anything is what you multiple it by), then loop over the values of the hash and multiple the current total value by the value from the hash.
#!/usr/bin/env perl
use v5.12;
my %hash = ( a => 1, b => 2, c => 3, d => 4 );
my $value = 1;
$value = $value * $_ foreach values %hash;
say $value;
This is what List::Util's reduce is for:
use List::Util 'reduce';
my %hash = (foo => 3, bar => 7, baz => 2);
say reduce { our $a * our $b } values %hash;
Output:
42
I would iterate over keys %hash like this:
my $prod = 1;
$prod *= $hash{$_} for keys %hash;
will this not do?
my $m = 1;
for (values %my_hash) {
$m *= $_;
}

How to copy keys from hash to array without duplicates?

If I have an array and hash like these
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
my #a = qw/a b c d e/;
my %h = (a => 1, b => 1, f => 1, g => 1);
and I would like to end up with #a containing all of the keys from %h, and no element in the array must appear more than once.
How can that be done, as exists doesn't work on arrays?
If you have Perl 5.10 and later, you can use smart matching (~~):
for my $key (keys %h) {
push #a, $key unless $key ~~ #a;
}
Otherwise, List::Util's first can help:
for my $key (keys %h) {
push #a, $key unless first { $_ eq $key } #a;
}
You could make use of List::MoreUtils's uniq function:
use List::MoreUtils qw( uniq );
#a = uniq #a, keys %h;
Convert the values you want into hash keys, then extract them
my %foo = map { $_ => 1 } #a, keys %h;
print sort keys %foo;
How about this (admittedly destructive to `%h):
delete #h{ #a }; # delete all keys of h already in #a
push #a, keys %h; # push remaining keys onto #a
Thus #a retains the order it had and simply appends the non-duplicate keys in %h.
A word about the destructiveness: The example above illustrates some concepts of what can be done when you can afford to be destructive. And delete is certainly no more destructive than passing out of the scope of a lexical variable.
The issue can be addressed by simply copying it to another hash before narrowing the hash to those keys not found in #a.
my %h2 = %h;
delete #h2{ #a };
push #a, keys %h2;

How can I get a only part of a hash in Perl?

Is there a way to get a sub-hash? Do I need to use a hash slice?
For example:
%hash = ( a => 1, b => 2, c => 3 );
I want only
%hash = ( a => 1, b => 2 );
Hash slices return the values associated with a list of keys. To get a hash slice you change the sigil to # and provide a list of keys (in this case "a" and "b"):
my #items = #hash{"a", "b"};
Often you can use a quote word operator to produce the list:
my #items = #hash{qw/a b/};
You can also assign to a hash slice, so if you want a new hash that contains a subset of another hash you can say
my %new_hash;
#new_hash{qw/a b/} = #hash{qw/a b/};
Many people will use a map instead of hash slices:
my %new_hash = map { $_ => $hash{$_} } qw/a b/;
Starting with Perl 5.20.0, you can get the keys and the values in one step if you use the % sigil instead of the # sigil:
my %new_hash = %hash{qw/a b/};
You'd probably want to assemble a list of keys you want:
my #keys = qw(a b);
And then use a loop to make the hash:
my %hash_slice;
for(#keys) {
$hash_slice{$_} = %hash{$_};
}
Or:
my %hash_slice = map { $_ => $hash{$_} } #keys;
(My preference is the second one, but whichever one you like is best.)
Yet another way:
my #keys = qw(a b);
my %hash = (a => 1, b => 2, c => 3);
my %hash_copy;
#hash_copy{#keys} = #hash{#keys};
Too much functional programming leads me to think of zip first.
With List::MoreUtils installed,
use List::MoreUtils qw(zip);
%hash = qw(a 1 b 2 c 3);
#keys = qw(a b);
#values = #hash{#keys};
%hash = zip #keys, #values;
Unfortunately, the prototype of List::MoreUtils's zip inhibits
zip #keys, #hash{#keys};
If you really want to avoid the intermediate variable, you could
zip #keys, #{[#hash{#keys}]};
Or just write your own zip without the problematic prototype. (This doesn't need List::MoreUtils at all.)
sub zip {
my $max = -1;
$max < $#$_and $max = $#$_ for #_;
map { my $ix = $_; map $_->[$ix], #_; } 0..$max;
}
%hash = zip \#keys, [#hash{#keys}];
If you're going to be mutating in-place,
%hash = qw(a 1 b 2 c 3);
%keep = map +($_ => 1), qw(a b);
$keep{$a} or delete $hash{$a} while ($a, $b) = each %hash;
avoids the extra copying that the map and zip solutions incur. (Yes, mutating the hash while you're iterating over it is safe... as long as the mutation is only deleting the most recently iterated pair.)
FWIW, I use Moose::Autobox here:
my $hash = { a => 1, b => 2, c => 3, d => 4 };
$hash->hslice([qw/a b/]) # { a => 1, b => 2 };
In real life, I use this to extract "username" and "password" from a form submission, and pass that to Catalyst's $c->authenticate (which expects, in my case, a hashref containing the username and password, but nothing else).
New in perl 5.20 is hash slices returning keys as well as values by using % like on the last line here:
my %population = ('Norway',5000000,'Sweden',9600000,'Denmark',5500000);
my #slice_values = #population{'Norway','Sweden'}; # all perls can do this
my %slice_hash = %population{'Norway','Sweden'}; # perl >= 5.20 can do this!
A hash is an unordered container, but the term slice only really makes sense in terms of an ordered container. Maybe look into using an array. Otherwise, you may just have to remove all of the elements that you don't want to produce your 'sub-hash'.

How do I remove duplicate items from an array in Perl?

I have an array in Perl:
my #my_array = ("one","two","three","two","three");
How do I remove the duplicates from the array?
You can do something like this as demonstrated in perlfaq4:
sub uniq {
my %seen;
grep !$seen{$_}++, #_;
}
my #array = qw(one two three two three);
my #filtered = uniq(#array);
print "#filtered\n";
Outputs:
one two three
If you want to use a module, try the uniq function from List::MoreUtils
The Perl documentation comes with a nice collection of FAQs. Your question is frequently asked:
% perldoc -q duplicate
The answer, copy and pasted from the output of the command above, appears below:
Found in /usr/local/lib/perl5/5.10.0/pods/perlfaq4.pod
How can I remove duplicate elements from a list or array?
(contributed by brian d foy)
Use a hash. When you think the words "unique" or "duplicated", think
"hash keys".
If you don't care about the order of the elements, you could just create the hash then extract the keys. It's not important how you create that hash: just that you use "keys" to get the unique elements.
my %hash = map { $_, 1 } #array;
# or a hash slice: #hash{ #array } = ();
# or a foreach: $hash{$_} = 1 foreach ( #array );
my #unique = keys %hash;
If you want to use a module, try the "uniq" function from
"List::MoreUtils". In list context it returns the unique elements, preserving their order in the list. In scalar context, it returns the number of unique elements.
use List::MoreUtils qw(uniq);
my #unique = uniq( 1, 2, 3, 4, 4, 5, 6, 5, 7 ); # 1,2,3,4,5,6,7
my $unique = uniq( 1, 2, 3, 4, 4, 5, 6, 5, 7 ); # 7
You can also go through each element and skip the ones you've seen
before. Use a hash to keep track. The first time the loop sees an
element, that element has no key in %Seen. The "next" statement creates
the key and immediately uses its value, which is "undef", so the loop
continues to the "push" and increments the value for that key. The next
time the loop sees that same element, its key exists in the hash and
the value for that key is true (since it's not 0 or "undef"), so the
next skips that iteration and the loop goes to the next element.
my #unique = ();
my %seen = ();
foreach my $elem ( #array )
{
next if $seen{ $elem }++;
push #unique, $elem;
}
You can write this more briefly using a grep, which does the same thing.
my %seen = ();
my #unique = grep { ! $seen{ $_ }++ } #array;
Install List::MoreUtils from CPAN
Then in your code:
use strict;
use warnings;
use List::MoreUtils qw(uniq);
my #dup_list = qw(1 1 1 2 3 4 4);
my #uniq_list = uniq(#dup_list);
My usual way of doing this is:
my %unique = ();
foreach my $item (#myarray)
{
$unique{$item} ++;
}
my #myuniquearray = keys %unique;
If you use a hash and add the items to the hash. You also have the bonus of knowing how many times each item appears in the list.
Can be done with a simple Perl one-liner.
my #in=qw(1 3 4 6 2 4 3 2 6 3 2 3 4 4 3 2 5 5 32 3); #Sample data
my #out=keys %{{ map{$_=>1}#in}}; # Perform PFM
print join ' ', sort{$a<=>$b} #out;# Print data back out sorted and in order.
The PFM block does this:
Data in #in is fed into map. map builds an anonymous hash. keys are extracted from the hash and feed into #out
Method 1: Use a hash
Logic: A hash can have only unique keys, so iterate over array, assign any value to each element of array, keeping element as key of that hash. Return keys of the hash, its your unique array.
my #unique = keys {map {$_ => 1} #array};
Method 2: Extension of method 1 for reusability
Better to make a subroutine if we are supposed to use this functionality multiple times in our code.
sub get_unique {
my %seen;
grep !$seen{$_}++, #_;
}
my #unique = get_unique(#array);
Method 3: Use module List::MoreUtils
use List::MoreUtils qw(uniq);
my #unique = uniq(#array);
The variable #array is the list with duplicate elements
%seen=();
#unique = grep { ! $seen{$_} ++ } #array;
That last one was pretty good. I'd just tweak it a bit:
my #arr;
my #uniqarr;
foreach my $var ( #arr ){
if ( ! grep( /$var/, #uniqarr ) ){
push( #uniqarr, $var );
}
}
I think this is probably the most readable way to do it.
Previous answers pretty much summarize the possible ways of accomplishing this task.
However, I suggest a modification for those who don't care about counting the duplicates, but do care about order.
my #record = qw( yeah I mean uh right right uh yeah so well right I maybe );
my %record;
print grep !$record{$_} && ++$record{$_}, #record;
Note that the previously suggested grep !$seen{$_}++ ... increments $seen{$_} before negating, so the increment occurs regardless of whether it has already been %seen or not. The above, however, short-circuits when $record{$_} is true, leaving what's been heard once 'off the %record'.
You could also go for this ridiculousness, which takes advantage of autovivification and existence of hash keys:
...
grep !(exists $record{$_} || undef $record{$_}), #record;
That, however, might lead to some confusion.
And if you care about neither order or duplicate count, you could for another hack using hash slices and the trick I just mentioned:
...
undef #record{#record};
keys %record; # your record, now probably scrambled but at least deduped
Try this, seems the uniq function needs a sorted list to work properly.
use strict;
# Helper function to remove duplicates in a list.
sub uniq {
my %seen;
grep !$seen{$_}++, #_;
}
my #teststrings = ("one", "two", "three", "one");
my #filtered = uniq #teststrings;
print "uniq: #filtered\n";
my #sorted = sort #teststrings;
print "sort: #sorted\n";
my #sortedfiltered = uniq sort #teststrings;
print "uniq sort : #sortedfiltered\n";
Using concept of unique hash keys :
my #array = ("a","b","c","b","a","d","c","a","d");
my %hash = map { $_ => 1 } #array;
my #unique = keys %hash;
print "#unique","\n";
Output:
a c b d