Why is my sort function not working - arrays

I have a file which I have read into an array, with multiple columns and I want to sort numerically by the second column. I've looked up countless similar questions and tried to directly incorporate the answers given.
here is the basic code I am using:
use strict;
use warnings;
use diagnostics;
my #arrayed = (
"\ndog", "10", "barks",
"\ncat", "20", "meows",
"\nfish", "5", "plop",
"\nant", "30", "walk",
);
print "#arrayed";
print "\n";
my #sortedarray = sort { $a->[1] <=> $b->[1] } #arrayed;
print "#sortedarray";
exit;
This gives me an error cant use string ("dog") as an array reference while strict is turned on. I tried a few other examples with other files, arrays but always get this message so I assume there must be something intrinsically wrong with my code.
could anybody more experienced shed a little light on what I'm doing wrong please, and allow me to sort by the numbered column while still maintaining the row structure.

You have a flat array, but you want an array-of-arrays:
use strict;
use warnings;
use diagnostics;
use Data::Dumper;
my #arrayed = (
["dog", "10", "barks"],
["cat", "20", "meows"],
["fish", "5", "plop"],
["ant", "30", "walk"],
);
print Dumper(\#arrayed);
my #sortedarray = sort { $a->[1] <=> $b->[1] } #arrayed;
print Dumper(\#sortedarray);
__END__
$VAR1 = [
[
'dog',
'10',
'barks'
],
[
'cat',
'20',
'meows'
],
[
'fish',
'5',
'plop'
],
[
'ant',
'30',
'walk'
]
];
$VAR1 = [
[
'fish',
5,
'plop'
],
[
'dog',
10,
'barks'
],
[
'cat',
20,
'meows'
],
[
'ant',
30,
'walk'
]
];

Your assignment does not create a multi-dimensional array:
my #arrayed = (
"\ndog", "10", "barks",
"\ncat", "20", "meows",
"\nfish", "5", "plop",
"\nant", "30", "walk",
);
You would need to use array references inside those parentheses:
my #arrayed = (
[ "\ndog", "10", "barks" ],
[ "\ncat", "20", "meows" ],
[ "\nfish", "5", "plop" ],
[ "\nant", "30", "walk" ]
);
The brackets [ ... ] create anonymous array references, which can then be stored in the array.
One of the most important things to know when debugging is what your data looks like. Doing something like what you did
print "#arrayed";
Is not very useful, since it will only show a list of the elements separated by space. Also, if you had done this with a multi-dimensional array, you would get output like this:
ARRAY(0x7fd658) ARRAY(0x7fd7f0)
Which is what array references look like when stringified. Instead, you should use the Data::Dumper module:
use Data::Dumper;
print Dumper \#arrayed;
Notice that you are printing a reference to the array. The output would be a data structure looking like what toolic has shown in his answer:
$VAR1 = [
[ ...
Note that the brackets, again, denote array references.

Related

Change property name when converting Powershell object to JSON using ConvertTo-Json?

I have a dataset consisting of a two-dimensional array (flexData[5][2]). I have this defined in my Powershell script as follows:
class flexData {
[DateTime]$dateTime
[string]$firmwareVersion
[string[][]]$flexData
}
$flexObj = [flexData]#{dateTime = $(Get-Date); firmwareVersion = 'u031C'; flexData = #(#(0, 1), #(320, 17), #(45, 36), #(0, 0))}
The problem with this is that the output object that ConvertTo-Json spits out is hard to read:
{
"dateTime": "2021-10-11T13:58:25.0937842+02:00",
"firmwareVersion": "u031C",
"flexData": [
[
"0",
"1"
],
[
"320",
"17"
],
[
"45",
"36"
],
[
"0",
"0"
]
]
}
Is there a way to instead of using a single key name and two-dimensional arrays, to instead convert this to flexData0, flexData1 ... flexData4 and keep my actual data as single-dimensional arrays? I could obviously do this by manually defining my class as:
class flexData {
[DateTime]$dateTime
[string]$firmwareVersion
[string[]]$flexData0
[string[]]$flexData1
[string[]]$flexData2
[string[]]$flexData3
[string[]]$flexData4
}
But is there a smarter way of doing this? Especially since I would also like to make a third-dimension of my array to store multiple iterations of flexData?
You could add a constructor to your flexData class that creates an object from the top-level array instead:
class flexData {
[DateTime]$dateTime
[string]$firmwareVersion
[psobject]$flexData
flexData([DateTime]$dateTime, [string]$firmwareVersion, [string[][]]$flexData){
$this.dateTime = $dateTime
$this.firmwareVersion = $firmwareVersion
# Create object from nested array
$dataProperties = [ordered]#{}
for($i = 0; $i -lt $flexData.Length; $i++){
$dataProperties["$i"] = $flexData[$i]
}
$this.flexData = [pscustomobject]$dataProperties
}
}
Now, the individual outer array items will be listed as properties named 0 through (N-1):
PS ~> $data = [flexData]::new($(Get-Date), 'u031C', #(#(0, 1), #(320, 17), #(45, 36), #(0, 0)))
PS ~> $data |ConvertTo-Json
{
"dateTime": "2021-10-11T14:21:48.4026882+02:00",
"firmwareVersion": "u031C",
"flexData": {
"0": [
"0",
"1"
],
"1": [
"320",
"17"
],
"2": [
"45",
"36"
],
"3": [
"0",
"0"
]
}
}

How do I parse an array in perl that contains subarrays?

Let me preface by saying I'm a total novice at perl.
I need to modify rules on a mail system. I can access the rules as an array and I believe the array contains subarrays. I need to modify one particular element and preserve the rest. My problem is I'm confused as to what the array type really is and how to consistently access the elements.
There may be more than one set of rules, but I'm only interested in processing rules with a priority of '1', which is $Rule[0]. Within $Rule[3] I need to parse the addresses.
use strict;
use Data::Dumper qw(Dumper);
my $Rules=$cli->GetAccountMailRules($account);
print Dumper \#$Rules;
foreach my $Rule (#$Rules) {
if($Rule->[0]=~/1/) {
my $pri=$Rule->[0];
my $type=$Rule->[1];
my $auto=$Rule->[2];
my $actions=$Rule->[3];
my $action1;
my $auto1;
my $auto2;
my #newRule;
my $oldDest;
print "\n";
print "Priority:\n";
print Dumper \$pri;
print "\n";
print "Rule Type:\n";
print Dumper \$type;
print "\n";
print "Forward Auto?:\n";
print Dumper \$auto;
print "\n";
print "Actions:\n";
print Dumper \$actions;
print "\n";
foreach my $ax (#$actions) {
$action1=$ax->[0];
$oldDest=$ax->[1];
}
my #addresses=split /[;,]|\\e/, $oldDest;
my #dests = grep(/corp.com|corp1.com|corp2.com|corp3.com/, #addresses);
my $newDest = join(",", #dests);
if (#$auto) {
foreach my $au (#$auto) {
$auto1=$au->[0];
$auto2=$au->[1];
}
#newRule=(
[ $pri, $type,
[[$auto1,$auto2]],
[[$action1,$newDest]]
]
);
} else {
#newRule=(
[ $pri, $type,
[],
[[$action1,$newDest]]
]
);
}
}
}
}
Output thusly:
# perl removeRules.pl
$VAR1 = [
[
'1',
'#Redirect',
[
[
'Human Generated',
'---'
]
],
[
[
'Mirror to',
'test10#corp.com\\etest10#gmail.com\\etest10#corp1.com'
]
]
]
];
Priority:
$VAR1 = \'1';
Rule Type:
$VAR1 = \'#Redirect';
Forward Auto?:
$VAR1 = \[
[
'Human Generated',
'---'
]
];
Actions:
$VAR1 = \[
[
'Mirror to',
'test10#corp.com\\etest10#gmail.com\\etest10#corp1.com'
]
];
The problem I'm running into is there is an option within $actions to discard emails after forwarding, which introduces new elements (or subarray?) into $actions:
# perl removeRules.pl
$VAR1 = [
[
'1',
'#Redirect',
[
[
'Human Generated',
'---'
]
],
[
[
'Mirror to',
'test10#corp.com\\etest10#gmail.com\\etest10#corp1.com'
],
[ <---- Begin new elements
'Discard',
'---'
] <---- End new elements
]
]
];
Priority:
$VAR1 = \'1';
Rule Type:
$VAR1 = \'#Redirect';
Forward Auto?:
$VAR1 = \[
[
'Human Generated',
'---'
]
];
Actions:
$VAR1 = \[
[
'Mirror to',
'test10#corp.com\\etest10#gmail.com\\etest10#corp1.com'
],
[
'Discard',
'---'
]
];
I tried testing to see if they can be referenced as additional elements in $actions but it throws off the index.
my $action2;
my $action3;
print "Actions:\n";
print Dumper \$actions;
print "\n";
foreach my $ax (#$actions) {
$action1=$ax->[0];
$oldDest=$ax->[1];
$action2=$ax->[2];
$action3=$ax->[3];
}
print " action1 $action1\n";
print " oldDest $oldDest\n";
print " action2 $action2\n";
print " action3 $action3\n";
Output:
Actions:
$VAR1 = \[
[
'Mirror to',
'test10#corp.com\\etest10#gmail.com\\etest10#corp1.com'
],
[
'Discard',
'---'
]
];
action1 Discard
oldDest ---
Use of uninitialized value $action2 in concatenation (.) or string at removeRules.pl line 107, <GEN0> line 4.
action2
Use of uninitialized value $action3 in concatenation (.) or string at removeRules.pl line 108, <GEN0> line 4.
action3
Thank you in advance.
Using this:
[
[
'Mirror to',
'test10#corp.com\\etest10#gmail.com\\etest10#corp1.com'
],
[
'Discard',
'---'
]
]
This is a reference to an array (the outer [..]) that has two items. Each item is again a reference to an array.
First item (position 0 of outer array reference) is
[
'Mirror to',
'test10#corp.com\\etest10#gmail.com\\etest10#corp1.com'
],
and second (position 1) is:
[
'Discard',
'---'
]
If $ractions is this outer array, then the above two items are respectively under $ractions->[0] and $ractions->[1].
Since they are both an array reference again you can access their items using the same construct, or using a Perl property, you can remove the second array.
In short:
'Mirror to' can be accessed by $ractions->[0]->[0] or shorter $ractions->[0][0]
'test10#corp.com\etest10#gmail.com\etest10#corp1.com' can be accessed by $ractions->[0]->[1]
'Discard' can be accessed by $ractions->[1]->[0]
'---' can be accessed by $ractions->[1]->[1]
Be aware however that $VAR1 = \[ shows that you have a reference over a reference. So you will need an extra step of derefencing:
DB<1> use Data::Dumper;
DB<2> #a=(1,2)
DB<3> print Data::Dumper::Dumper(#a);
$VAR1 = 1;
$VAR2 = 2;
DB<4> print Data::Dumper::Dumper(\#a);
$VAR1 = [
1,
2
];
DB<5> print Data::Dumper::Dumper(\\#a);
$VAR1 = \[
1,
2
];
PS: do not use corp.com or anything like that when you need to obfuscate domain names. See guidance in RFC2606 or TL;DR: use example.com

access / print nth element of sub array, for every array

I have a multidimensional array:
#multarray = ( [ "one", "two", "three" ],
[ 4, 5, 6, ],
[ "alpha", "beta", "gamma" ]
);
I can access #multarray[0]
[
[0] [
[0] "one"
[1] "two"
[2] "three"
]
]
or even #multarray[0][0]
"one"
But how to I access say the 1st sub element of every sub array? something akin to multarray[*][0] so produce:
"one"
4
"alpha"
Thanks!
You can use map and dereference each array:
use warnings;
use strict;
use Data::Dumper;
my #multarray = (
[ "one", "two", "three" ],
[ 4, 5, 6, ],
[ "alpha", "beta", "gamma" ]
);
my #subs = map { $_->[0] } #multarray;
print Dumper(\#subs);
__END__
$VAR1 = [
'one',
4,
'alpha'
];
See also: perldsc
Using a for() loop, you can loop over the outer array, and use any of the inner elements. In this example, I've set $elem_num to 0, which is the first element. For each loop over the outer array, we take each element (which is an array reference), then, using the $elem_num variable, we print out the contents of the inner array's first element:
my $elem_num = 0;
for my $elem (#multarray){
print "$elem->[$elem_num]\n";
}

How to Iterate the contents of multidimentional array in parallel using Perl

I have an array containing number of array. I am trying to get the element from each array parallely. Can some body please help me.
#shuffle= (
[ "george", "jane", "elroy" ],
[ "homer", "marge", "bart" ],
[ "fred", "barney" ]
);
I tried this, but it iterating the inner arrays sequentially.
my #fh;
#create an array of open filehandles.
#fh = map { #$_ } #shuffle;
foreach (#fh){
my $line = $_;
print $line."\n";
}
And out put is like this :
george
jane
elroy
homer
marge
bart
fred
barney
But I need the output like this :
george
homer
fred
jane
marge
barney
elroy
bart
The thing you need to bear in mind is when perl does an 'array of arrays' it's actually an array of array references.
What you're doing with your map there is dereferencing each in turn, and by doing so 'flattening' your array - taking all the elements in the first array reference, then the second and so on.
But that's not what you're trying to accomplish - you're trying to take the first element from each, then second, then third etc.
But what that map statement does do is allow you to count the total number of elements in your array, which can be used thus:
my #shuffle= (
[ "george", "jane", "elroy" ],
[ "homer", "marge", "bart" ],
[ "fred", "barney" ]
);
while ( map { #$_ } #shuffle ) {
foreach my $sub_array ( #shuffle ) {
print shift #$sub_array // '',"\n";
}
}
that's probably not an ideal way to test if you're finished though - but it does allow you to have varying lengths of inner arrays
You should use the each_arrayref function from the List::MoreUtils module.
The code would look like this:
use strict;
use warnings 'all';
use List::MoreUtils qw/ each_arrayref /;
use Data::Dump;
my #shuffle = (
[ qw/ george jane elroy / ],
[ qw/ homer marge bart / ] ,
[ qw/ fred barney / ],
);
my $iter = each_arrayref #shuffle;
while ( my #set = $iter->() ) {
dd \#set;
}
output
["george", "homer", "fred"]
["jane", "marge", "barney"]
["elroy", "bart", undef]
Try this
my #shuffle= (
[ "george", "jane", "elroy" ],
[ "homer", "marge", "bart" ],
[ "fred", "barney" ]
);
for my $i(0..$#shuffle)
{
for my $j(0..$#shuffle)
{
print "$shuffle[$j][$i]\n";
}
}

Perl merge hash of array

I have hash of array of numbers,
I would like to merge the hash elements with common numbers.
eg.
Input Hash
%HoA = (
A1 => [ "1", "2", "3", "4" ],
A2 => [ "5", "6", "7", "8" ],
A3 => [ "1", "9", "10", "11" ],
A4 => [ "12", "13", "14", "10" ],
);
Output Hash
%HoA_output = (
A1 => [ "1", "2", "3", "4", "9", "10", "11", "12", "13", "14" ],
A2 => [ "5", "6", "7", "8" ],
);
I need a solution that could quickly evaluate a hash that has nearly 50k keys with 8 numbers in each array.
regards
This is essentially a graph problem where you want to determine the sets of unconnected components
This solution uses the Graph::Undirected::Components module, whose sole purpose is to do exactly that. Hopefully it will be fast enough for your extended data set, but it is far easier for you to determine that than for me
The program creates a graph, and adds edges (connections) from every key in the data to each element of its value. Then, calling connected_components returns all the distinct sets of nodes — both keys and values — that are connected to one another
The final for loop filters the keys from the values once more using part from List::MoreUtils, based on whether the node value appears as a key in the original hash data. (You will have to adjust this if any of the key values can also appear in the values.) Then the first of the keys together with the sorted value items are used to create a new element in the %result hash
use strict;
use warnings;
use Graph::Undirected::Components;
use List::Util 'minstr';
use List::MoreUtils 'part';
my %data = (
A1 => [ 1, 2, 3, 4 ],
A2 => [ 5, 6, 7, 8 ],
A3 => [ 1, 9, 10, 11 ],
A4 => [ 12, 13, 14, 10 ],
);
my $graph = Graph::Undirected::Components->new;
while ( my ($k, $v) = each %data ) {
$graph->add_edge($k, $_) for #$v;
}
my %result;
for my $component ( $graph->connected_components ) {
my #keys_vals = part { $data{$_} ? 0 : 1 } #$component;
my $key = minstr #{ $keys_vals[0] };
my #values = sort { $a <=> $b } #{ $keys_vals[1] };
$result{$key} = \#values;
}
use Data::Dump;
dd \%result;
output
{ A1 => [1 .. 4, 9 .. 14], A2 => [5 .. 8] }
OK, pretty fundamentally - this isn't an easy one, because you do need to check each element against each other to see if they're present. The best I can come up with is saving some effort by merging lists as you go, and using an index to track dupes.
I would approach it like this:
use strict;
use warnings;
use Data::Dumper;
my %HoA = (
A1 => [ "1", "2", "3", "4" ],
A2 => [ "5", "6", "7", "8" ],
A3 => [ "1", "9", "10", "11" ],
A4 => [ "12", "13", "14", "10" ],
);
print Dumper \%HoA;
my %found;
sub merge_and_delete {
my ( $first_key, $second_key ) = #_;
print "Merging $first_key with $second_key\n";
#use hash to remove dupes.
my %elements;
foreach my $element ( #{ $HoA{$first_key} }, #{ $HoA{$second_key} } )
{
$elements{$element}++;
#update index - don't want to point it to an array we're deleting
$found{$element} = $first_key;
}
#sorting for neatness - you might want to do a numeric sort instead,
#as by default %HoA contains text elements.
$HoA{$first_key} = [ sort keys %elements ];
delete $HoA{$second_key};
}
foreach my $key ( sort keys %HoA ) {
print "$key\n";
foreach my $element ( sort #{ $HoA{$key} } ) {
if ( $found{$element} ) {
#this element is present in another list, we merge.
print "$element found in $found{$element}\n";
merge_and_delete( $found{$element}, $key );
last;
}
else {
#add this unique match to our index
print "$element -> $key\n";
$found{$element} = $key;
}
}
}
print Dumper \%HoA;
You iterate each of the element on %HoA, and make an index table %found. This index table you use to detect if an element has already been seen, and then trigger a merge - and then rebuilt the index. You may need to watch for memory consumption on a large data set though, because your index can grow to be nearly as large as your original data set (if enough unique elements are present).
But because we stop processing on the first match, we don't need to check every key any more, and because we discard the merged array and update the index, we don't need to do an all-to-all comparison any more.

Resources