I have two hash of arrays. I want to compare whether the keys in both hash of arrays contain the same values.
#!/usr/bin/perl
use warnings; use strict;
my %h1 = (
w => ['3','1','2'],
e => ['6','2','4'],
r => ['8', '1'],
);
my %h2 = (
w => ['1','2','3'],
e => ['4','2','6'],
r => ['4','1'],
);
foreach ( sort {$a <=> $b} (keys %h2) ){
if (join(",", sort #{$h1{$_}})
eq join(",", sort #{$h1{$_}})) {
print join(",", sort #{$h1{$_}})."\n";
print join(",", sort #{$h2{$_}})."\n\n";
} else{
print "no match\n"
}
}
if ("1,8" eq "1,4"){
print "true\n";
} else{
print "false\n";
}
The output is supposed to should be:
2,4,6
2,4,6
1,2,3
1,2,3
no match
false
but for some reason my if-statement isn't working. thanks
Smart match is an interesting solution; available from 5.010 onward:
if ([sort #{$h1{$_}}] ~~ [sort #{$h2{$_}}]) { ... }
The smart match on array references returns true when the corresponding elements of each array smartmatch themselves. For strings, smart matching tests for string equality.
This may be better than joining the members of an array, as smart matching works for arbitrary data*. On the other hand, smart matching is quite complex and has hidden gotchas
*on arbitrary data: If you can guarantee all your strings only contain numbers, then everything is allright. However, then you could just have used numbers instead:
%h1 = (w => [3, 1, 2], ...);
# sort defaults to alphabetic sorting. This is undesirable here
if ([sort {$a <=> $b} #{$h1{$_}}] ~~ [sort {$a <=> $b} #{$h2{$_}}]) { ... }
If your data may contain arbitrary strings, especially strings containing commata, then your comparision isn't safe — consider the arrays
["1foo,2bar", "3baz"], ["1foo", "2bar,3baz"] # would compare equal per your method
if (join(",", sort #{$h1{$_}})
eq join(",", sort #{$h1{$_}})) {
Should be :
if (join(",", sort #{$h1{$_}})
eq join(",", sort #{$h2{$_}})) {
Note the $h2. You're comparing one hash to itself.
Try this:It compares two hashes line by line exactly.
if ( join(",", sort #{ $h1{$_}})
eq join(",", sort #{ $h2{$_}}) ) #Compares two lines exactly
Related
I am trying to sort by value in a HoA wherein key => [ a, b, c]
I want to sort alphabetically and have tried and read with no success. I think its the commas, but please help! Below is a short snippet. The raw data is exactly how it appears in the data dumper print vs. the CLI. I have to use some sort of delimiter otherwise the cli output is tedious! Thank you!
use strict;
use warnings;
my ( $lsvm_a,$lsvm_b,%hashA,%hashB );
my $vscincludes = qr/(^0x\w+)\,\w+\,\w+.*/; #/
open (LSMAP_A, "-|", "/usr/ios/cli/ioscli lsmap -vadapter vhost7 -field clientid vtd backing -fmt ," ) or die $!;
while ($lsvm_a = (<LSMAP_A>)) {
chomp($lsvm_a);
next unless $lsvm_a =~ /$vscincludes/;
#{$hashA{$1}} = (split ',', $lsvm_a);
}
open (LSMAP_B, "-|", "/usr/sbin/clcmd -m xxxxxx /usr/ios/cli/ioscli lsmap -vadapter vhost29 -field clientid vtd backing -fmt ," ) or die $!;
while ($lsvm_b = (<LSMAP_B>)) {
chomp($lsvm_b);
next unless $lsvm_b =~ /$vscincludes/;
push #{$hashA{$1}}, (split ',', $lsvm_b);
}
print "\n\nA:";
for my $key ( sort { $hashA{$a} cmp $hashA{$b} } keys %hashA ) {
print "$key => '", join(", ", #{$hashA{$key}}), "'\n";
}
##
print "===\nB:";
foreach my $key ( sort { (#{$hashB{$a}}) cmp (#{$hashB{$b}}) } keys %hashB ) {
print "$key ==> #{$hashB{$key}}\n";
}
print "\n\n__DATA_DUMPER__\n\n";
use Data::Dumper; print Dumper \%hashA; print Dumper \%hashB;
Output
A:
0x00000008 => '0x00000008, atgdb003f_avg01, hdisk10, atgdb003f_ovg01, hdisk96, atgdb003f_pvg01, hdisk68, atgdb003f_rvg01, hdisk8, vtscsi0, atgdb003f_data.5bcd027df10f27bf9a880ce7bc1dd924'
===
B:
0x00000008 => '0x00000008, atgdb003f_avg01, hdisk10, atgdb003f_data, atgdb003f_data.5bcd027df10f27bf9a880ce7bc1dd924, atgdb003f_ovg01, hdisk96, atgdb003f_pvg01, hdisk68, atgdb003f_rvg01, hdisk8'
__DATA_DUMPER__
$VAR1 = {
'0x00000008' => [
'0x00000008',
'atgdb003f_avg01',
'hdisk10',
'atgdb003f_ovg01',
'hdisk96',
'atgdb003f_pvg01',
'hdisk68',
'atgdb003f_rvg01',
'hdisk8',
'vtscsi0',
'atgdb003f_data.5bcd027df10f27bf9a880ce7bc1dd924'
]
};
$VAR1 = {
'0x00000008' => [
'0x00000008',
'atgdb003f_avg01',
'hdisk10',
'atgdb003f_data',
'atgdb003f_data.5bcd027df10f27bf9a880ce7bc1dd924',
'atgdb003f_ovg01',
'hdisk96',
'atgdb003f_pvg01',
'hdisk68',
'atgdb003f_rvg01',
'hdisk8'
]
};
### CLI out ###
###0x00000008,atgdb003f_avg01,hdisk10,atgdb003f_ovg01,hdisk96,atgdb003f_pvg01,hdisk68,atgdb003f_rvg01,hdisk8,vtscsi0,atgdb003f_data.5bcd027df10f27bf9a880ce7bc1dd924
###0x00000008,atgdb003f_avg01,hdisk10,atgdb003f_data,atgdb003f_data.5bcd027df10f27bf9a880ce7bc1dd924,atgdb003f_ovg01,hdisk96,atgdb003f_pvg01,hdisk68,atgdb003f_rvg01,hdisk8
Update The arrayrefs (hash values) have multiple elements after all, and need be sorted. Then
for my $key (keys %h) { #{$h{$key}} = sort #{$h{$key}} }
or, more efficiently† (and in the statement modifier form, with less noise but perhaps less clear)
$h{$_} = [ sort #{$h{$_}} ] for keys %h;
The sort by default uses lexicographical sort, as wanted.
Keys are desired to be sorted numerically, but note that while we can rewrite the arrays to make them sorted it is not so with hashes, which are inherently unordered. We can print sorted of course
foreach my $k (sort { $a <=> $b } keys %h) { ... }
This will warn if keys aren't numbers.
† By 56% – 60% in my benchmarks on three different machines, with both v5.16 and v5.30.0
Original post
I take it that you need to sort a hash which has an arrayref for a value, whereby that arrayref has a single element. Then sort on that, first, element
foreach my $key ( sort { $hashB{$a}->[0] cmp $hashB{$b}->[0] } keys %hashB ) {
print "$key ==> #{$hashB{$key}}\n";
}
See the cmp operator under Equality operators in perlop. It takes scalars, which are stringwise compared (so the attempted sorting with an array from the question is wrong since cmp would get lengths of those arrays to sort by!)
In my understanding your hash to sort is like
$VAR1 = {
'0x00000008' => [ 'atgdb003f_avg01,hdisk10,atgdb003f_ovg01,...' ],
...
}
where each value is an arrayref with exactly one element.
Perl Sort function unable to arrange array elements in my expected incremental manner
#array_sort = sort { $a <=> $b } #array
#array = ("BE_10", "BE_110", "BE_111", "BE_23", "BE_34", "BE_220", "BE_335");
#array_sort = sort { $a <=> $b } #array;
print "array_sort = #array_sort\n";
Expected result:
array_sort = BE_10 BE_23 BE_34 BE_110 BE_111 BE_220 BE_335
Actual result:
array_sort = BE_10 BE_110 BE_111 BE_23 BE_34 BE_220 BE_335
Always use use strict; use warnings;. It would have found your problem, which is that all your strings have the numerical value of zero. Since all strings are numerically identical, the sort function you provided always returns zero. Because of this, and because Perl used a stable sort, the order of the strings remained unchanged.
You wish to perform a "natural sort", and there are modules such as Sort::Key::Natural that will do that.
use Sort::Key::Natural qw( natsort );
my #sorted = natsort #unsorted;
Sounds like a good case for a Schwartzian transform.
If the prefix is always going to be the same and it's just the numbers after the underscore that differ:
my #array = ("BE_10", "BE_110", "BE_111", "BE_23", "BE_34", "BE_220", "BE_335");
my #array_sort = map { $_->[0] }
sort { $a->[1] <=> $b->[1] }
map { [ $_, (split /_/, $_)[1] ] } #array;
print "array_sort = #array_sort\n";
And if it might be different:
my #array = ("BE_10", "BE_110", "BE_111", "BE_23", "CE_34", "BE_220", "CE_335");
my #array_sort = map { $_->[0] }
sort { $a->[1] cmp $b->[1] || $a->[2] <=> $b->[2] }
map { [ $_, split(/_/, $_) ] } #array;
print "array_sort = #array_sort\n";
Basic idea is that you decompose the original array into a list of array refs holding the original element and the transformed bit(s) you want to sort on, do the sort, and then extract the original elements in the new sorted order.
My data structure is
my %hash = (
firstkey => {
secondkey => {
2 => ['9','2'],
1 => ['3','4'],
3 => ['8','2']
}
}
);
print Dumper \%hash;
I want to sort the hash by the thirdly key. i.e. 1, 2 and 3 in this case
and then compare the second element (index[1]) in the array. If they are the same, and then print it out.
Expected Sorted Hash:
my %hash = (
firstkey => {
secondkey => {
1 => ['3','4'],
2 => ['9','2'],
3 => ['8','2']
}
}
);
print Dumper \%hash;
After sort the hash, we compare the index[1] of the 1st array[3,4] with the 2nd array[9,2].
4 is not equal to 2, so we are not going to print anything.
Then, we compare the index[1] of the 2nd array[9,2] with the 3rd array[4,2].
2 is equal to 2, then we are going to print all the content of it
firstkey, secondkey, 3, [8,2]
we only need to compare the adjacent array.
I read a lot of solutions about sorting the hash, but I couldn't find one solution that really reorders it Is it any way to reorder the hash by the key and construct a hash with the new order in Perl?
Or we can only sort the hash by using the for loop and compare it in the for loop?
One cannot have a "sorted hash" – they are intrinsically unordered data structures (see keys). The randomizaton of the initial seed and hash traversal are even enhanced for security purposes.
But we can sort the list of hash keys as needed. Then we have an ordered list to iterate over and thus can process the hash in a "sorted manner."
The keys to sort by here are at a deeper level, so iterate over the upper (two) levels to get to them. Then it's a straightforward sort and test
use warnings;
use strict;
use feature 'say';
my %hash = (
firstkey1 => {
secondkey1 => {
2 => [9, 2], 1 => [3, 4], 3 => [8, 2]
}
}
);
foreach my $k1 (keys %hash)
{
foreach my $k2 (keys %{$hash{$k1}})
{
# Relieve syntax below
my $hr = $hash{$k1}{$k2};
my #sr_k3 = sort { $a <=> $b } keys %{$hr};
foreach my $i (1..$#sr_k3)
{
if ( $hr->{$sr_k3[$i]}[1] == $hr->{$sr_k3[$i-1]}[1] )
{
say "$k1, $k2, $sr_k3[$i], ",
'[', join(',', #{$hr->{$sr_k3[$i]}}), ']';
}
}
#say "#{$hash{$k1}{$k2}{$_}}" for keys %{$hash{$k1}{$k2}};
}
}
A few notes
Sorted keys are iterated over starting with the second one due to the comparison criterion
Hashref is copied at the second level only for convenience, to relieve the messy syntax
When complex data structures get too unwieldy it may be time to use a class instead
This works for any number of keys in both levels (only one key is shown for each level).
As has been said, you won't be able to dictate the order of the hash. Here's a way to map it to something you can sort and do the comparison you need.
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
my %hash = ( firstkey => {
secondkey => {
2 => [9,2],
1 => [3,4],
3 => [8,2],
}
}
);
#obtain an array of two-element array refs, whose contents are the keys
#for the "secondkey" hash and the corresponding value, respectively
my #arr = map { [ $_, $hash{firstkey}->{secondkey}->{$_} ] }
keys %{$hash{firstkey}->{secondkey}};
#sort on the aforementioned key
my #sorted = sort { $a->[0] <=> $b->[0] } #arr;
#obtain an array of array refs, whose contents are a pair of adjacent
#elements from the #sorted array
my #ordered_pairs = map { [ $sorted[$_], $sorted[$_+1] ] }
(0 .. (scalar #sorted - 2));
#compare the elements in question, and do something if there's a match
for (#ordered_pairs) {
if ($_->[0][1][1] == $_->[1][1][1]) {
print Dumper $_->[1];
}
}
I understand that the default sort in Perl is an ASCII sort, not numerical. But how can I numerically sort strings that have numbers?
For example, I have a hash of arrays, like so:
myhash{ANN20021015_0101_XML_71.9} = ("anta", "hunna", "huma");
myhash{ANN20021115_0049_XML_14.1} = ("lqd", "qAl", "arrajul");
myhash{ANN20021115_0049_XML_14.2} = ("anna", "alwalada");
I just need the keys to be sorted...but the sorting is numerical within strings. I can't do a string sort, because I end up with "10" following "1", but I can't do a numerical sort either!
First of all your code isn't valid Perl and may not do what you think it does. Always
use strict;
use warnings;
at the head of your program to resolve any simple mistakes. The code should look like
$myhash{'ANN20021015_0101_XML_71.9'} = ["anta", "hunna", "huma"];
$myhash{'ANN20021115_0049_XML_14.1'} = ["lqd", "qAl", "arrajul"];
$myhash{'ANN20021115_0049_XML_14.2'} = ["anna", "alwalada"];
To sort on something other than the entire value, you can transform $a and $b within the sort block, and sort the result numerically <=> instead of stringwise <=>. This code does what you need
my #sorted = sort {
my ($aa) = $a =~ /.*_(.+)/;
my ($bb) = $b =~ /.*_(.+)/;
$aa <=> $bb;
} keys %myhash;
But if you have a large amount of data it may be profitable to use the Schwartzian Transform which will avoid extracting the numeric part of your strings every time they are compared
my #sorted = map { $_->[0] }
sort { $a->[1] cmp $b->[1] }
map { /.*_(.+)/ and [$_, $1] }
keys %myhash;
You need to do a custom sort: cut your strings into parts you know are literals/numbers and compare those as needed.
From your example it looks like you want literal.digits, but you can change the regular expression so that it fits you.
my $cut = qr/(.*?\.)(\d+)(.*)/;
sort {
my #a = $a =~ $cut; my #b = $b =~ $cut;
$a[0] cmp $b[0] || $a[1] <=> $b[1] || $a[2] cmp $b[2]
} keys %myhash;
See also Borodin's answer.
print "#_\n";
4109 4121 6823 12967 12971 14003 20186
How do I sort it in Perl?
Using #sorted = sort(#_); gives me an alphabetical ordering:
13041 13045 14003 20186 4109 4121 6823
How do I get a numerical ordering? Does Perl have built-in functions for merge-sort, insertion-sort, etc.?
You can pass a custom comparison function to Perl's sort routine. Just use:
#sorted = sort { $a <=> $b } #unsorted;
The sort function accepts a custom comparison function as its first argument, in the form of a code block. The {...} part is just this code block (see http://perldoc.perl.org/functions/sort.html ).
sort will call this custom comparison function whenever it needs to compare two elements from the array to be sorted. sort always passes in the two values to compare as $a, $b, and the comparison function has to return the result of the comparison. In this case it just uses the operator for numeric comparison (see http://perldoc.perl.org/perlop.html#Equality-Operators ), which was probably created just for this purpose :-).
Solution shamelessly stolen from "Perl Cookbook", Chapter 04 Sub-chapter 15 (buy the book - it's worth it!)
Supply a comparison function to sort():
# sort numerically ascending
my #articles = sort {$a <=> $b} #files;
# sort numerically descending
my #articles = sort {$b <=> $a} #files;
The default sort function is cmp, string comparison, which would sort (1, 2, 10) into (1, 10, 2) . <=> , used above, is the numerical comparison operator.
Perl's sort by default sorts alphabetically in ASCII order. To sort numerically you can use:
#sorted = sort { $a <=> $b } #_;
This is a Perl FAQ. From the command line:
perldoc -q sort
perlfaq4: How do I sort an array by (anything)?
#l = (4109, 4121, 6823, 12967, 12971, 14003, 20186, 1, 3, 4);
#l = sort { $a <=> $b } #l;
print "#l\n"; # 1 3 4 4109 4121 6823 12967 12971 14003 20186
You have to supply your own sorting subroutine { $a <=> $b }
You can predefine a function which should be used to compe values in your array.
perldoc -f sort gives you an example:
# Sort using explicit subroutine name
sub byage {
$age{$a} <=> $age{$b}; # Presuming numeric
}
#sortedclass = sort byage #class;
The <=> operator is used to sort numerically.
#sorted = sort {$a <=> $b} #unsorted;
You find here (and in a lot of other places) that the way to sort a numeric array is:
#sorted_array = sort { $a <=> $b } #unsorted_array;
Now you try it, and you get an error: "Can't use "my $a" in sort comparison"! (This is because you have already declared '$a', using 'strict.pm'). But then, you can't use non-declared variables either since they will be rejected as undefined! So, you might feel trapped in an impasse.
Neither perldoc.perl.org, nor most other places mention that '$a' and '$b' are reserved (tokens) for this use! (This of course when one uses 'strict', which one should. And which is quite crazy, because 'a' and 'b' are among the most common short variables used in programming, and logically so!)