Sorting by length in Perl - perl

Anyone have any idea how to sort number by length?
Ex : (11,111,122,12,2,13,21,15,211,22,213,2004)
I wanted the sorted array to be:
11
12
13
15
111
122
2
21
22
213
2004

The desired output seems to indicate you don't just want to sort by the number of digits, but rather first sort by the first digit and then by the length.
The desired output you show omits 211, so I just put it where it belonged according to my understanding.
#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;
my #source = (11, 111, 122, 12, 2, 13, 21, 15, 211, 22, 213, 2004);
my #desired = (11, 12, 13, 15, 111, 122, 2, 21, 22, 211, 213, 2004);
my #sorted =sort {
substr($a, 0, 1) <=> substr($b, 0, 1) ||
length($a) <=> length($b) ||
$a <=> $b # thanks #ikegami
} #source;
is_deeply(\#sorted, \#desired, 'Sorted and desired are the same');

my #sorted =
sort { substr($a,0,1) <=> substr($b,0,1) || $a <=> $b }
#unsorted;
gives the order you requested. Or maybe you want
my #sorted =
sort { substr($a,0,1) <=> substr($b,0,1)
|| length($a) <=> length($b)
|| $a <=> $b }
#unsorted;
If 211 wasn't missing from the output you provided, I could would tell you which one you want.

Consider a so-called Schwartzian transform, which avoids recomputing the sort keys by temporarily associating them with the input items:
my #sorted =
map { $_->[0] }
sort { $a->[1] cmp $b->[1] or $a->[0] <=> $b->[0] }
map { [ $_ => sprintf "%.1s%08x", $_, length ] }
#source;

This is provided by List::UtilsBy::sort_by
use List::UtilsBy qw( sort_by );
my #sorted = sort_by { sprintf "%.1s%08x", $_, length } #source;
It's much the same as the Schwartzian Transform solutions others have suggested, but wrapped in a neat abstraction.

Related

Find minium value that is maximum times in Array Perl

I have a requirement to find out the minimum value that is occurring maximum times in the array .I have store those values in other array .
my #arr=(1,2,3,4,1,3,4,1);
1 is the minimum value that is occurring maximum times.
If there are two or more elements occurring same number of times, smaller is preferred:
my #arr=(1,2,3,4,1,3,4,1);
my %seen;
$seen{$_}++ for #arr;
my ($min_val) = sort { $seen{$b} <=> $seen{$a} || $a <=> $b } keys %seen;
print "$min_val\n";
You can use a hash to count the occurrences of each number. The most frequent numbers can be found as having the frequence equal to the max of the frequences, the minimum among them can be found by min, both min and max come from List::Util.
#!/usr/bin/perl
use warnings;
use strict;
use List::Util qw(min max);
my #arr = (1, 2, 3, 4, 1, 3, 4, 1);
my %occurrences;
$occurrences{$_}++ for #arr;
my $max_freq = max(values %occurrences);
print min(grep $max_freq == $occurrences{$_}, keys %occurrences);
Use this it will work perfect for you
my #arr=(1, 2, 3, 4, 1, 3, 4, 1);
my %count;
foreach (#arr){
$count{$_}++;
}
my ($min_by_value) = sort { $a <=> $b} keys %count;
my ($max_by_count) = sort { $count{$b} <=> $count{$a} } keys %count;
my $max =
($count{$min_by_value} >= $count{$max_by_count}) ? $min_by_value : $max_by_count;
print "minimum value max times = $max\n";

Sorting a two-dimensional array in Perl based on value of one item, with exceptions

Currently, I am working with a two dimensional array with information on soccer goals scored. Each item in the first array is an array with different information regarding the goal. The second item in this array is the minute scored (1-90) because I want them in sequential order so I can determine what they made the scoreline. I'm using that 'minutes' value to sort with this:
#allinfogoals = sort { $a->[1] <=> $b->[1] } #allinfogoals;
This works great, until I run into goals that were scored in extra time. The minutes for these are displayed like "90+2" or "45+3". Now, I can just add them together, but that could make the order incorrect. In this case, a goal scored right before half time could be stored as having been scored AFTER a goal that was scored shortly after the beginning of the second half.
So, I'm finding the minutes that have this 90+x format and splitting them at the '+'. I'm storing the first value where I regularly store minutes in the array, but I added another at the end of the array (12th item) and I'm putting that second part (mins into extra time) there. That is 0 when it is a regular goal.
How can I modify the sorting above to compensate this and have it maintain the proper order?
It sounds like you want to sort on one key first, and if that key is the same, then you want to sort on a second key.
E.g. you want 45+2 to be sorted between 45 and 46.
You can do this by simply using:
#ls = sort { $a->[0] <=> $b->[0] || $a->[1] <=> $b->[1] } #ls
Only if the first key is the same, the second key is consulted.
Here is a complete example:
my #allinfogoals=(
[ 46, 0 ],
[ 45, 2 ],
[ 45, 0 ],
[ 33, 0 ],
[ 91, 0 ],
[ 90, 2 ],
);
#allinfogoals=sort { $a->[0] <=> $b->[0] || $a->[1] <=> $b->[1] } #allinfogoals;
use Data::Dump; dd \#allinfogoals;
And the output is:
[[33, 0], [45, 0], [45, 2], [46, 0], [90, 2], [91, 0]]
Here is a "brute force" solution, in that there's not much finesse in it, but does the job. It does not work with a two-dimensional data structure unless adapted, but on the other hand, I don't know what your data structure looks like.
use strict;
use warnings;
use feature 'say';
my #data = qw(22 45+3 45 46 90 90+3);
my #sorted = map $_->[2], # turn back to org string
sort {
$a->[0] <=> $b->[0] || # default sort by period number
$a->[1] <=> $b->[1] # or by minute
} map mysort($_), #data; # map all minutes to 3-element array
say for #sorted;
sub mysort {
my $time = shift;
if ($time =~ /45\+(\d+)/) {
return [1, 45+$1, $time];
} elsif ($time =~ /90\+(\d+)/) {
return [2, 90+$1, $time];
} else {
my $period = ($time <= 45 ? 1 : 2);
return [$period, $time, $time]
}
}
This uses a Schwartzian transform to turn each minute entry into a three element array, consisting of period number, minute within that period and the original string. The output of this script is:
22
45
45+3
46
90
90+3
In this particular case, as you have described it, you could also (as someone answered but deleted again, it seems) convert your overtime minutes to tenths and order numerically:
my #allinfogoals=qw(46 45+2 45 33 91 90+2);
#allinfogoals=map { s/[.]/+/; $_ } sort { $a <=> $b } map { s/[+]/./; $_ } #allinfogoals;
use Data::Dump; dd \#allinfogoals;
... and then convert back. Output:
[33, 45, "45+2", 46, "90+2", 91]

sorting by mmyy (month and year)

I'm looking for a logical (not additional module) to sort by such format. I have a list of strings which looks like:
asdadasBBBsfasdasdas-0112
asdanfnfnfnfnf222ads-1210
etc.
I cant just sort by the numbers, because, for instance: 812 > 113 (812 = August 2012, 113 = January 2013, so its incorrect)
any good strategy??
thanks,
A schwartzian transform would be a huge waste here. This similar construct whose name I can never remember would be way better.
my #sorted =
map substr($_, 4),
sort
map substr($_, -2) . substr($_, -4, 2) . $_,
#unsorted;
Using the match operator instead of substr:
my #sorted =
map substr($_, 4),
sort
map { /(..)(..)\z/s; $2.$1.$_ }
#unsorted;
How about Schwartzian transform:
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dump qw(dump);
my #list = (
'asdadasBBBsfasdasdas-0112',
'asdanfnfnfnfnf222ads-1210',
'asdanfnfnfnfnf222ads-1211',
'asdanfnfnfnfnf222ads-1010',
'asdanfnfnfnfnf222ads-1011',
);
my #sorted =
map { $_->[0] }
sort { $a->[1] <=> $b->[1] or $a->[2] <=> $b->[2] }
map { /-(\d\d)(\d\d)$/; [$_, $2, $1] } #list;
dump #sorted;
output:
(
"asdanfnfnfnfnf222ads-1010",
"asdanfnfnfnfnf222ads-1210",
"asdanfnfnfnfnf222ads-1011",
"asdanfnfnfnfnf222ads-1211",
"asdadasBBBsfasdasdas-0112",
)
Use a sorting function that looks at the year first, and then the date:
sub mmyy_sorter {
my $a_yy = substr($a, -2);
my $b_yy = substr($b, -2);
my $a_mm = substr($a, -4, 2);
my $b_mm = substr($b, -4, 2);
return ($a_yy cmp $b_yy) || ($a_mm cmp $b_mm);
}
my #sorted = sort mmyy_sorter #myarray;
NB: this is technically not as efficient as it could be as it has to re-calculate the month and year subfields for every comparison, not just once for each item in the array.
It would also be possible to take advantage of Perl's automatic type conversion and use the <=> operator in place of cmp, since all of the values actually represent numbers.
What about remake it to months? For example:
812 = 12 * 12 + 8
113 = 13 * 12 + 1
You can turn years into months and it will be good. For selecting numbers you can use regex.
Thanks to #M42 for the sample data.
use strict;
use warnings;
use feature 'say';
my #list = (
'asdadasBBBsfasdasdas-0112',
'asdanfnfnfnfnf222ads-1210',
'asdanfnfnfnfnf222ads-1211',
'asdanfnfnfnfnf222ads-1010',
'asdanfnfnfnfnf222ads-1011',
);
my #sorted = sort {
my ($aa, $bb) = map { /(..)(..)\z/ and $2.$1 } $a, $b;
$aa <=> $bb;
} #list;
say for #sorted;
output
asdanfnfnfnfnf222ads-1010
asdanfnfnfnfnf222ads-1210
asdanfnfnfnfnf222ads-1011
asdanfnfnfnfnf222ads-1211
asdadasBBBsfasdasdas-0112

Printing multi keyed Hash in Perl

I have a perl hash that I am indexing like this:
my %hash;
$hash{'number'}{'even'} = [24, 44, 38, 36];
$hash{'number'}{'odd'} = [23, 43, 37, 35];
When I try to print key names like this:
foreach my $key (keys %hash{'number'})
{
print "Key: $key\n";
}
I get the following error:
Type of arg 1 to keys must be hash (not hash slice) at test.pl
However when I pass the array ref to a function and print it there, it prints the values:
test(\%hash);
sub test
{
my ($hash) = #_;
foreach my $key (keys %{$hash->{'number'}})
{
print "Key: $key\n"; #outputs: even odd
}
}
Can someone please let me know what is going wrong here? Also if I have multi-keyed hash which I have in this case where hash is indexed by both 'number' and 'even' or 'odd' if I do something like this:
foreach my $key (keys %hash)
{
print "First Key: $key\n"; #Outputs number
}
Then will I always get 'number' as the output right and I can never get 'even', 'odd' as outputs, correct? This is just to know good coding practice :)
This is the full code:
sub test
{
my ($hash) = #_;
foreach my $key (keys %{$hash->{'number'}})
{
print "Key: $key\n";
}
}
my %hash;
$hash{'number'}{'even'} = [24, 44, 38, 36];
$hash{'number'}{'odd'} = [23, 43, 37, 35];
test(\%hash);
foreach my $key (keys %hash)
{
print "First Key: $key\n";
}
foreach my $key (keys %hash{'number'})
{
print "Key: $key\n";
}
Thanks,
Newbie
my %hash;
$hash{'number'}{'even'} = [24, 44, 38, 36];
$hash{'number'}{'odd'} = [23, 43, 37, 35];
%hash is a hash whose keys are strings ('number'), and whose values are hash references.
foreach my $key (keys %hash{'number'})
{
print "Key: $key\n";
}
To refer to a value that's part of %hash, you want to write $hash{'number'}, not %hash{'number'}.
But $hash{'number'} is a hash reference, not a hash. To refer to the hash that it refers to, you can write this:
%{$hash{'number'}}
Putting it all together this:
my %hash;
$hash{'number'}{'even'} = [24, 44, 38, 36];
$hash{'number'}{'odd'} = [23, 43, 37, 35];
foreach my $key (keys %{$hash{'number'}}) {
print "Key: $key\n";
}
will produce this output:
Key: even
Key: odd
(possibly not in that order).
You can do something like this:
#!/usr/bin/perl -w
use strict;
use warnings;
my %hash;
$hash{'number'}{'even'} = [24, 44, 38, 36];
$hash{'number'}{'odd'} = [23, 43, 37, 35];
foreach my $i(keys %hash){
print $i;
foreach my $j(keys %{$hash{$i}}){
print "\t".$j."\t";
print join(" ",#{$hash{'number'}{$j}})."\n";
}
}

Sort function in Perl

Consider:
use warnings;
my #a = (1, 11, 3, 5, 21, 9, 10);
my #b = sort #a;
print "#b";
Output: 1 10 11 21 3 5 9
Codepad link: http://codepad.org/Fvhcf3eP
I guess the sort function is not taking the array's elements as an integer. That is why the output is not:
1 3 5 9 10 11 21
Is it?
How can I get the above result as output?
The default implementation of Perl's sort function is to sort values as strings. To perform numerical sorting:
my #a = sort {$a <=> $b} #b;
The linked page shows other examples of how to sort case-insensitively, in reverse order (descending), and so on.
You can create explicit subroutines to prevent duplication:
sub byord { $a <=> $b };
...
#a = sort byord #b;
This is functionally equivalent to the first example using an anonymous subroutine.
You are correct. So just tell Perl to treat it as an integer like below.
File foop.pl
use warnings;
my #a = (1, 11, 3, 5, 21, 9, 10);
my #b = sort {$a <=> $b} #a;
print "#b";
Run
perl foop.pl
1 3 5 9 10 11 21
Provide a custom comparison function (comparing numerically):
sort {$a <=> $b} #array;
Here is a numerical sort:
#sorted = sort { $a <=> $b } #not_sorted
#b = sort { $a <=> $b } #a;
Is numerical
Use the spaceship operator: sort { $a <=> $b } #a
Guessing is the wrong approach. If you don't understand sort, look it up: sort
my #b = sort{$a <=> $b} #a;