How to copy keys from hash to array without duplicates? - perl

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;

Related

I need help regarding perl hash of array

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";
}

Printing specific number of key-value pairs in a hash

I have a hash which stores the count of key-value pairs, from an array of strings taken from an input document then sorts them and prints them.
%count = ();
foreach $string (#strings) {
$count{$string}++;
}
foreach my $key (sort {$count{$b} <=> $count{$a} } keys %count) {
print $key, ": ", $count{$key} ;
}
so I am wondering is there a way to only print a certain number of key-value pairs in the hash instead of all of them ? i.e print top 5 based the value?
edit: would a for loop solve this?
%count = ();
foreach $string (#strings) {
$count{$string}++;
}
my $n=0; # variable to keep count of processed keys
foreach my $key (sort {$count{$b} <=> $count{$a} } keys %count) {
# count processed keys (++$n)
# and terminate the loop after processing 5 keys
last if ++$n>5;
print $key, ": ", $count{$key} ;
}
Can take a slice of the list returned by sort
use strict;
use warnings;
use feature 'say';
....
my %count;
foreach my $string (#strings) {
++$count{$string}
}
say "$_: $count{$_}"
for ( sort { $count{$b} <=> $count{$a} } keys %count )[0..4];
(This expects that the hash indeed has five keys; if it can happen that that is not the case you'd get hit by warnings so add a test in that case, for instance $_ and say "..." for ...)
The code in the question is clearly not using strict; I recommend to always use it.
The %count = () makes sense if the hash has been populated before and now need be emptied. If you are creating it then just declare (and without = (), which does nothing).
Note, thanks to Grinnz: very recent List::Util 1.50 adds head (and tail) functions

Perl how to compare hash value against a list and returns keys matching

Consider this case:
I have a list:
#Value2CompareList
And I have a hash table:
%Hash2Check
Now I want to achieve the following:
if($Hash2Check{$keys} eq $ElementFromArray) {return matching keys}
How do I do it in a faster way without Loop?
Thanks folks!
You can use grep inside a grep:
#! /usr/bin/perl
use warnings;
use strict;
use feature qw{ say };
my #Value2CompareList = qw( a b c d e f );
my %Hash2Check = (
A => 'a',
B => 'b',
C => 'c');
say for grep {
my $k = $_;
grep $Hash2Check{$k} eq $_, #Value2CompareList
} keys %Hash2Check;
It's complex because the data structure is not suitable for what you need. An inverted hash would be much better:
my %Inverted = reverse %Hash2Check;
say for grep defined, #Inverted{#Value2CompareList};
This only works if the values are unique. If not, you need to create a hash of arrays:
my %Inverted;
while (my ($k, $v) = each %Hash2Check) {
push #{ $Inverted{$v} }, $k;
}
say for map #$_, grep defined, #Inverted{#Value2CompareList};
You cannot do it without a loop. You are iterating a list, and that's a loop.
But you can pretend a little, and use grep:
my #matches = grep { exists $Hash2Check{$_} } #Value2CompareList;
But be under no illusions - grep is still iterating the list. Each iteration, it sets $_ to the current element, and checks if it exists in the hash.

accessing "pair hash" references used for column specification

I'm building a small library that transforms log data into a CSV-like file that can be imported by spreadsheet software. For the output, I'm interested in an option to display human-friendly captions for the table columns if needed. This should be an option so that the tool can also be used with minimal effort. I came up with an array for column specification that contains plain scalars for keys or hash references with a pair of key and value. I'm accessing these via keys and values which looks a bit strange to me.
Is there a simpler way to access key and value of a hash that contains just a pair?
(I tried to simplify the code as much as possible.)
#!/usr/bin/perl -w
use strict;
use warnings;
# some sample data
my #rows = (
{x => 1, y => 2},
{y => 5, z => 6},
{x => 7, z => 9},
);
sub print_table {
my #columns = #_; # columns of interest with optional header replacement
my #keys; # for accessing the data values
my #captions; # for display on column headers
for (#columns) {
if (ref($_) eq 'HASH') {
push #keys, keys %$_;
push #captions, values %$_;
} else {
push #keys, $_;
push #captions, $_;
}
}
print join ("\t", #captions), "\n";
for my $row (#rows) {
print join ("\t", (map {$row->{$_} // ''} #keys)), "\n";
}
}
print_table({x=>'u'}, 'y');
All you need:
my ($k, $v) = %hash;
So
my ($k, $v) = %$_;
push #keys, $k;
push #captions, $v;
or
push #keys, ( %$_ )[0];
push #captions, ( %$_ )[1];
Use each.
From perldoc:
When called on a hash in list context, returns a 2-element list
consisting of the key and value for the next element of a hash.
But don't forget to reset the iterator using keys(%hash) afterwards or subsequent each will fail.
my ($k, $v) = each(%$_);
keys(%$_);

In Perl, how can I print the key corresponding to the maximum value in a hash?

How can I print only the first key and element of my hash?
I have already a sorted hash, but I want to print only the first key and respective value
thanks,
Thanks to all of you at the end I push the keys and the values to two different #array and print element 0 of each array and it works :)
Hashes have unordered keys. So, there is no such key as a first key in a hash.
However, if you need the key that sorts first (for maximum key value):
my %hash = (
'foo' => 'bar',
'qux' => 'baz',
);
my ($key) = sort { $b cmp $a } keys %hash;
print "$key => $hash{$key}"; # Outputs: qux => baz
Remember to use <=> instead of cmp for numerical sorting.
In perl hashes there is no ordering for keys. Use sort function to get the keys in the order that you want or you can push the keys into an array as you create the hash and your first key will be in zero th index in the array
You can use the below code, i am assuming hash name is my_hash and keys and values are numbers. If you have strings, you can use cmp instead of <=>. Refer to the sort documentation for more details
Get the max key
foreach (sort {$b <=> $a} keys %my_hash) {
print "Keys is $_\n";
print "Value is $my_hash{$_}\n";
last;
}
Get the key corresponding to the max value
foreach (sort {$my_hash{$b} <=> $my_hash{$a}} keys %my_hash) {
print "Keys is $_\n";
print "Value is $my_hash{$_}\n";
last;
}
foreach my $key (sort keys(%hash)) {
print "$key" . "$hash{$key}" . "\n";
last;
}
For large hashes, if you do not need the sorted keys for any other reason, it might be better to avoid sorting.
#!/usr/bin/env perl
use strict; use warnings;
my %hash = map { $_ => rand(10_000) } 'aa' .. 'zz';
my ($argmax, $max) = each %hash;
keys %hash; # reset iterator
while (my ($k, $v) = each %hash) {
if ($v >= $max) {
$max = $v;
$argmax = $k;
}
}
print "$argmax => $max\n";
If you are intent on sorting, you only need the key with the maximum value, not the entire arrays of keys and values:
#!/usr/bin/env perl
use strict; use warnings;
my %hash = map { $_ => rand(10_000) } 'aa' .. 'zz';
my ($argmax) = sort { $hash{$b} <=> $hash{$a} } keys %hash;
print "$argmax => $hash{$argmax}\n";
Just as Alan wrote - hashes don't have specific order, but you can sort hash keys:
foreach my $key (sort keys(%hash)) {
print $key . ': ' . $hash{$key} . "\n";
}
or, as you wish, get first element from keys array:
my #keys = keys(%hash);
print $keys[0];