Perl sorting arrayref - arrays

So I have this Perl code:
$array->[0][0] = "cc";
$array->[0][1] = "3";
$array->[1][0] = "aaaa";
$array->[1][1] = "2";
$array->[2][0] = "bb";
$array->[2][1] = "1";
And I need it sorted in alphabetical order (second column) so that $array->[0][0] is "aaaa" and $array->[0][1] is "2"
I must have been asleep during Programming 101 in the 90's. I've spent hours trawling code and tutorials on the net and just can't get it. Can someone provide me with some sample code please. thanks!

Just sort the dereferenced array by the first element:
$array = [ sort { $a->[0] cmp $b->[0] } #$array ];
or
#$array = sort { $a->[0] cmp $b->[0] } #$array;
Returns:
[ [ 'aaaa', '2' ],
[ 'bb', '1' ],
[ 'cc', '3' ] ]

If you can reach into CPAN, use the sort_by function provided by List::UtilsBy (or via List::AllUtils)
use List::AllUtils 'sort_by';
$array = [ sort_by { $_->[0] } #$array ];
... or alternatively using Sort::Key
use Sort::Key 'keysort';
$array = [ keysort { $_->[0] } #$array ];
Both achieve the same thing, but you should really try to get a modern version of List::AllUtils as it will save you from reinventing a lot of wheels.

Related

perl call variable name using another variable(array)

my #CLASS_TYPES = ("INTRA", "BB", "CAT");
my #INTRA_NEIGH = ("1.1.1.1/32","2.2.2.2/32");
my #BB_NEIGH = ("3.3.3.3/32","4.4.4.4/32" );
foreach my $class (#CLASS_TYPES) {
my $csv = #.$class._NEIGH;
print($csv);
when I print $csv I am expecting to print the array values how do I achieve that
That's not the right approach. See Why it's stupid to `use a variable as a variable name' and A More Direct Explanation of the Problem.
The very fact that #CAT_NEIGH doesn't exist illustrates part of the problem.
Solution:
my #CLASS_TYPES = ("INTRA", "BB", "CAT");
my %NEIGH = (
INTRA => [ "1.1.1.1/32", "2.2.2.2/32" ],
BB => [ "3.3.3.3/32", "4.4.4.4/32" ],
);
for my $class (#CLASS_TYPES) {
next if !$NEIGH{$class};
print "$_\n" for #{ $NEIGH{$class} };
}
or just
my %NEIGH = (
INTRA => [ "1.1.1.1/32", "2.2.2.2/32" ],
BB => [ "3.3.3.3/32", "4.4.4.4/32" ],
);
for my $class (keys(%NEIGH)) {
print "$_\n" for #{ $NEIGH{$class} };
}

Clean way to access a nested data structure

I have a segment of code that, although it works, does not look like a clean way to do things.
I build the structure using:
foreach my $n (#node_list)
{
chomp ($n);
foreach my $c (#cpes)
{
my #returned; #Interfaces to CPEs with MED settings
my #creturned; #General Customer Interfaces
my ($cust) = $c =~ /([a-zA-Z]+)[_-][a-zA-Z0-9]+/s;
print "\n\t\tCustomer is $cust\n";
chomp($c);
$c = uc $c;
my ($search) = $c;
(#returned) = `cat /curr/$n | grep "$search"`;
if (#returned)
{
my $cust_match = 'interface \"' . $cust;
(#creturned) = `cat /curr/$n | egrep -i "$cust_match" | grep -v "$search"`;
}
if (#creturned) #Have we found other CPEs on the same router
{
my ($nf) = $n =~ /([a-zA-Z0-9-]+).cfg/s;
my (#interfaces) = map { /([A-Z0-9_]+)/s } #creturned;
#interfaces = uniq(#interfaces);
unshift (#interfaces, $c);
push (#new_out, {$nf => {$cust => [#interfaces]}});
}
}
This will return:
$VAR1 = [
{
'router-xx-xx' => {
'50000' => [
[
'THXXXXVF_NLXXXX40_1121_2',
'10x.xx.x.50'
],
[
'THXXXPVF_NLXXXX66_1121_1',
'10x.xx.x.70'
],
[
'THXXXXVF_NLXXXX67_1121_2',
'10x.xx.x.78'
],
}
},
Each router can have a number of VPRNs and each VPRN can contain multiple interfaces. In the example above I've shown one router with one VPRN.
However, when it comes to accessing elements in the above, I've written the following convoluted (but working) code:
foreach my $candidate (#nodes)
{
my %node = %{ $candidate };
foreach my $n (keys %node)
{
print "\nRouter is $n\n";
foreach my $cust (keys %{ $node{$n} })
{
print "Customer on $n is \n" . Dumper $cust;
my #intlist = #{$node{$n}{$cust}};
my $med_cpe = $intlist[0]; #the CPE that was used to find node
{truncated}
}
}
}
}
You don't explain exactly what you find "convoluted" about the traversal code, but you have made it unnecessarily complex by duplicating data into #intlist and %node. The excessive and inconsistent indentation also makes it ungainly
I would write something closer to this
for my $node ( #nodes ) {
for my $n ( keys %$node ) {
print "\nRouter is $n\n";
for my $cust ( keys %{ $node->{$n} } ) {
print "Customer on $n is \n" . Dumper \$cust;
my $med_cpe = $node->{$n}{$cust}[0];
}
}
}
If you don't need the values of $node and $n except to access $med_cpe then you don't need a nested data structure at all: a simple array is fine. On the face of it, an array like this will do what you need
[
[
'router-xx-xx',
'50000',
'THXXXXVF_NLXXXX40_1121_2',
'10x.xx.x.50',
],
[
'router-xx-xx',
'50000',
'THXXXPVF_NLXXXX66_1121_1',
'10x.xx.x.70',
],
...
]

Convert an array of strings into a array of arrays of strings

My goal is to convert this
my #array=("red", "blue", "green", ["purple", "orange"]);
into this
my #array = ( ["red"], ["blue"], ["green"], ["purple", "orange"]);
Current test code:
my #array = ("red", "blue", "green", ["purple", "orange"] );
foreach $item ( #array ) {
#if this was Python, it would be as simple as:
# if is not instance(item, array):
# # item wasn't a list
# item = [item]
if(ref($item) ne 'ARRAY'){
#It's an array reference...
#you can read it with $item->[1]
#or dereference it uisng #newarray = #{$item}
#print "We've got an array!!\n";
print $item, "\n";
# keep a copy of our string
$temp = $item;
# re-use the variable but convert to an empty list
#item = ();
# add the temp-copy as first list item
#item[0] = $temp;
# print each list item (should be just one item)
print "$_\n" for $item;
}else{
#not an array in any way...
print "ALREADY an array!!\n";
}
}
# EXPECTED my #array=(["red"], ["blue"], ["green"], ["purple", "orange"]);
print #array , "\n";
foreach $item (#array){
if(ref($item) ne 'ARRAY'){
#
#say for $item;
print "didn't convert properly to array\n";
}
}
The comment about python maps pretty directly to perl.
my #array = ("red", "blue", "green", ["purple", "orange"] );
foreach $item ( #array ) {
#if this was Python, it would be as simple as:
# if is not instance(item, array):
# # item wasn't a list
# item = [item]
if (ref $item ne 'ARRAY') {
$item = [ $item ];
}
}
though using map as in Borodin's answer would be more natural.
I'm wondering why you want to do this, but it's
#array = map { ref ? $_ : [ $_ ] } #array
And please don't call arrays #array; that's what the # is for.
Your comment is ridiculous
#if this was Python, it would be as simple as:
# if is not instance(item, array):
# # item wasn't a list
# item = [item]
If you were familiar with Perl then you wouldn't need to ask the question. You must be aware that there is no one-to-one translation from Python to Perl. Python is much less expressive than either Perl or C, but I can't imagine you demanding a simple conversion to C.
Please get over your bigotry.
If you push the values to a new array, you don't need to do more than evaluate whether or not $item is an arrayref:
#! perl
use strict;
use warnings;
use Data::Dumper;
my #array=("red", "blue", "green", ["purple", "orange"]);
my #new_array;
foreach my $item (#array) {
if ( ref($item) eq 'ARRAY' ) {
push #new_array, $item;
}
else {
push #new_array, [$item];
}
}
print Dumper \#new_array;
Output from Dumper:
$VAR1 = [
[
'red'
],
[
'blue'
],
[
'green'
],
[
'purple',
'orange'
]
];
After a long day of learning more Perl than I ever thought/wanted to learn... here's what I think is a workable solution:
#! perl
use strict;
use warnings;
use Data::Dumper;
my %the_dict = (duts =>
{dut_a => {UDF => 'hamnet'},
dut_b => {UDF => [ '1', '2', '3', ]},
dut_c => {UDF => [ 'Z' ], }});
print Dumper \%the_dict;
foreach my $dut (keys %{$the_dict{duts}}) {
# convert the dut's UDF value to an array if it wasn't already
if ( 'ARRAY' ne ref $the_dict{duts}->{$dut}{UDF} ) {
$the_dict{duts}->{$dut}{UDF} = [ $the_dict{duts}->{$dut}{UDF} ];
}
# now append a new value to the array
push(#{$the_dict{duts}{$dut}{UDF}}, 'works');
}
print Dumper \%the_dict;
when run we see these print-outs:
$VAR1 = {
'duts' => {
'dut_a' => {
'UDF' => 'hamnet'
},
'dut_c' => {
'UDF' => [
'Z'
]
},
'dut_b' => {
'UDF' => [
'1',
'2',
'3'
]
}
}
};
$VAR1 = {
'duts' => {
'dut_a' => {
'UDF' => [
'hamnet',
'works'
]
},
'dut_c' => {
'UDF' => [
'Z',
'works'
]
},
'dut_b' => {
'UDF' => [
'1',
'2',
'3',
'works'
]
}
}
};

Passing an array to a subroutine encloses it in another array?

I noticed that when I pass an array to my subroutine it seems like it gets encapsulated by another array (so two levels, while the initial is only one).
I know that using references to arrays is better, but I'm wondering in this specific case why it is not working as expected.
Code example:
#!/usr/local/bin/perl
use Data::Dumper;
sub testSub {
my (#arr) = (#_);
print Dumper \#arr;
}
my #testArray = ();
push #testArray, {
'key1' => 'value1',
'key2' => 'value2',
'urls' => [ 'www.example.com' ]
};
print Dumper #testArray;
foreach my $item ( #testArray ) {
my #urls = testSub( $item->{'urls'} );
}
output
$VAR1 = {
'urls' => [
'www.example.com'
],
'key1' => 'value1',
'key2' => 'value2'
};
$VAR1 = [
[
'www.example.com'
]
];
my #urls = testSub( $item->{'urls'}, 'abc' );
Result of Dumper in subrotine:
$VAR1 = [
[
'www.example.com'
],
'abc'
];
Array passed by reference. Since at the time of compilation perl did not know what will be in the scalar $item->{'urls'}.
my #urls = testSub( #{ $item->{'urls'} }, 'abc' );
Result of Dumper in subrotine:
$VAR1 = [
'www.example.com',
'abc'
];
Now the compiler expects an array and turns it into a list.
You are passing $item->{'urls'} to your subroutine
Your Data::Dumper output clearly shows that the hash element looks like this
'urls' => [ 'www.example.com' ]
When you call testSub, you are making an assignment that is equivalent to
my #arr = ( [ 'www.example.com' ] );
Your statement print Dumper \#arr passes an array reference to Dumper, so it displays
[ [ 'www.example.com' ] ]
It would help your confusion if you were consistent in calling Dumper. print Dumper #testArray passes the contents of #testArray as individual parameters (although in this case the array has only a single element) while print Dumper \#arr passes an array reference as a single parameter, and is the better choice
If you want testSub to receive a list of URLs, you must expand the array $item->{urls} into a list with #{ ... }:
my #urls = testSub( #{ $item->{'urls'} } );

grep multiple pattern in perl array at a time

Below is the code which actually finds a pattern in a perl array.
my $isAvailable = grep { $_->[0] eq '12345' } {$filteredTableEntriesMap{$REPORT_PART1}} ;
But i would like to search for two patterns in two indexes at a time
my $isWiuAvailable = grep { $_->[0] eq '12345' } #{$filteredTableEntriesMap{$REPORT_PART1}} ;
my $isBsAvailable = grep { $_->[1] eq '6789' } #{$filteredTableEntriesMap{$REPORT_PART1}} ;
This is how the map is represented
$VAR1 = {
'REPORT PART2' => [],
'REPORT PART1' => [
[
'12345',
'6789',
],
[
'343435',
'315',
],
[
'00103',
'000315',
],
]
And i would want to match an array which has these two entries in index 1 and index 2
Thanks
You can combine the two conditions into one expression.
my #found = grep { $_->[0] eq '12345' && $_->[1] eq '6789' }
#{$filteredTableEntriesMap{$REPORT_PART1}};
The stuff inside the {} for grep is basically a subroutine. You can do as much as you want in there as long as you return a true value if you want to keep $_ in your #found result.

Resources