split numeric array at specific positions - arrays

I am trying to split a numerical array into smaller arrays such that each of the smaller arrays cannot contain any numbers that differ.
Example: The array (2,2,2,2,2,9,3,3,3,3) should be split into the three arrays (2,2,2,2,2), (9) and (3,3,3,3).
Here is what I tried:
my #arr = (2,2,2,2,2,9,3,3,3,3);
my #result = ();
my $last = -1;
my #newarr = ();
for my $i (0 .. $#arr){
if ( ($i>0 && $arr[$i] != $last) || $i == $#arr ){
push #result, \#newarr;
#newarr = ();
}
$last = $arr[$i];
push #newarr, $arr[$i];
}
Firstly, this code does not give me the desired result. I think my mistake is when I push the reference to #newarr into #result, but then I re-initialize #newarr.
Secondly, aren't there more elegant ways to do this? I looked at the functions split and splice, but could not think of a good solution.

List::MoreUtils has the "part" function:
use Data::Dumper;
use feature 'state';
use List::MoreUtils 'part';
my #array = ( 2,2,2,2,2, 9, 3,3,3,3 );
my #part = part {
state $prev;
state $i = -1;
$i++ if !defined($prev) || $_ ne $prev;
$prev = $_;
$i
} #array;
print Dumper #part;
With 'part', the value that the code block returns dictates the top level array index where the current value will be pushed into an anonymous array. $prev starts out undefined, so the first element in the input will trigger $i to increment to 0, so all of the '2's will end up in #{$part[0]}. As soon as an element in #array doesn't match $prev, the index will be incremented, and subsequent elements end up in #{$part[1]}. Each time a change is detected, a new grouping starts.
Update:
If this segment of code might be used more than once, the 'state' variables will persist their values across calls. In such a case, state is more trouble than it's worth, and one should just use lexicals in a subroutine:
use Data::Dumper;
use List::MoreUtils 'part';
my #array = ( 2,2,2,2,2, 9, 3,3,3,3 );
my #part = partition(#array);
print Dumper \#part;
sub partition {
my( $prev, $i ) = ( undef, -1 );
return part {
$i++ if ! defined($prev) || $_ ne $prev;
$prev = $_;
$i;
} #_;
}

Creating an array of arrays grouped by like elements.
For a refresher on complex data structures, check out perldsc.
use strict;
use warnings;
my #array = (2,2,2,2,2,9,3,3,3,3);
my #grouped;
for (#array) {
if (! #grouped || $grouped[-1][0] != $_) {
push #grouped, [];
}
push #{$grouped[-1]}, $_;
}
use Data::Dump;
dd #grouped;
Outputs:
([2, 2, 2, 2, 2], [9], [3, 3, 3, 3])

use List::Util 'reduce';
my #arr = (2,2,2,2,2,9,3,3,3,3);
my $result = reduce {
if ( #$a && $b == $a->[-1][0] ) {
push #{ $a->[-1] }, $b
}
else {
push #$a, [ $b ]
}
$a
} [], #arr;
Simpler but maybe more confusing to read:
my $result = reduce {
push #{ $a->[ #$a && $b == $a->[-1][0] ? -1 : #$a ] }, $b;
$a
} [], #arr;

my #arr = (2,2,2,2,2,9,3,3,3,3);
my %h;
my #newarr = map {
my $ok = !$h{$_};
push #{$h{$_}}, $_;
$ok ? $h{$_} : ();
}
#arr;
use Data::Dumper; print Dumper \#newarr;
or
my #arr = (2,2,2,2,2,9,3,3,3,3);
my %h;
my #newarr;
for my $v (#arr) {
if (!$h{$v}) {
push #newarr, ($h{$v} = []);
}
push #{$h{$v}}, $v;
}
output
$VAR1 = [
[
2,
2,
2,
2,
2
],
[
9
],
[
3,
3,
3,
3
]
];

Mandatory regex answer:
my #result = map [ (ord) x length ], grep --$|, join( '', map chr, #arr ) =~ /((.)\2*)/sg;
(under no warnings "non_unicode";).

This will do what you ask. It works by packing the contents of the data as a set of digits and counts and then unpacks it in the required format. The output data is in #num. I have used Data::Dump only to display the resulting data structure.
use strict;
use warnings;
my #arr = (2,2,2,2,2,9,3,3,3,3);
my (%rep, #num);
$rep{$_}++ or push #num, $_ for #arr;
#num = map [ ($_) x $rep{$_} ], #num;
use Data::Dump;
dd \#num;
output
[[2, 2, 2, 2, 2], [9], [3, 3, 3, 3]]
Update
The above solution collects all the elements with the same value into one group, even if they came from separate sequences. If you need the output arrays to be split at every change of value then this will do what you need.
use strict;
use warnings;
my #arr = (2,2,2,2,2,9,9,9,2,2,2,9,9,9);
my #groups;
for (#arr) {
push #groups, [ ] unless #groups and $_ == $groups[-1][-1];
push #{ $groups[-1] }, $_;
}
use Data::Dump;
dd \#groups;
output
[[2, 2, 2, 2, 2], [9, 9, 9], [2, 2, 2], [9, 9, 9]]
Update 2
Here's another version in view of your answer to ikegami's comment, which revealed that a list of values and their associated counts is probably closer to what you need.
use strict;
use warnings;
my #arr = (2,2,2,2,2,9,9,9,2,2,2,9,9,9);
my #groups;
for (#arr) {
if (#groups and $_ == $groups[-1][0]) {
$groups[-1][1] += 1;
}
else {
push #groups, [ $_, 1 ];
}
}
use Data::Dump;
dd \#groups;
output
[[2, 5], [9, 3], [2, 3], [9, 3]]

You can create a hash of arrays where by the key of the has will be a number. and for each time you encounter that number you can push it on to the array reference of the hash. thus all numbers will be split into arrays as you expected. you can then just iterate through the hash to print the arrays or access each array by its number.
use strict;
use Data::Dumper;
my #arr = (2,2,2,2,2,9,3,3,3,3);
my %hash;
push(#{$hash{$_}},$_) foreach (#arr);
print Dumper(\%hash);
output
$VAR1 = {
'3' => [
3,
3,
3,
3
],
'9' => [
9
],
'2' => [
2,
2,
2,
2,
2
]
};

Related

Remove even amounts of duplicates from an array

I have an array
[ 1, 0, 0, 0, 5, 2, 4, 5, 2, 2 ]
I need to delete even amounts of duplicates.
That means, if a value appears an even number of times in the array then remove them all, but if it appears an odd number of times then keep just one.
The result from the array above should be
[ 1, 0, 2, 4 ]
How can I do that?
Removing duplicates is usually done as follows:
use List::Util 1.44 qw( uniqnum );
#a = uniqnum #a;
or
my %seen;
#a = grep { !$seen{$_}++ } #a;
To achieve what you want, we simply need chain grep that removes the other undesired elements.
use List::Util 1.44 qw( uniqnum );
#a = uniqnum grep { $counts{$_} % 2 } #a;
or
my %seen;
#a = grep { !$seen{$_}++ } grep { $counts{$_} % 2 } #a;
or
my %seen;
#a = grep { ( $counts{$_} % 2 ) && !$seen{$_}++ } #a;
The above solutions rely on having the count of each value. To obtain that, we can use the following:
my %counts;
++$counts{$_} for #a;
All together:
my ( %counts, %seen );
++$counts{$_} for #a;
#a = grep { ( $counts{$_} % 2 ) && !$seen{$_}++ } #a;
Note that these methods of removing duplicates preserve the order of the elements (keeping the first duplicate). This is more efficient (O(N)) then involving sort (O(N log N)) to avoid producing something non-deterministic.
This really isn't difficult, and it is very bad form to show no attempt at all at solving it yourself. I would like someone who posted questions like this to describe how they feel comfortable getting someone else to do their work for them. Even difficult crosswords don't get this flood of requests for a solution, but in this case presumably you are being paid for a solution written by someone else? Why is that not a problem to you?
Build a hash to calculate the current count for each value
use $_ % 2 do determine the new final count
Deconstruct the hash to a new array
my $array = [ 1, 0, 0, 0, 5, 2, 4, 5, 2, 2 ];
my #new_array = do {
my %counts;
++$counts{$_} for #$array;
map {
( $_ ) x ( $counts{$_} % 2 )
} sort { $a <=> $b } keys %counts;
};
use Data::Dump;
dd \#new_array;
output
[0, 1, 2, 4]
See the comments, to see how this possible solution does it.
#!/usr/bin/perl
use strict;
use warnings;
my #a = qw(1 0 0 0 5 2 4 5 2 2);
# Move through the array.
for (my $i = 0; $i < scalar(#a); ) {
# Move through the positions at and ahead of current position $i
# and collect all positions $j, that share the value at the
# current position $i.
my #indexes;
for (my $j = $i; $j < scalar(#a); $j++) {
if ($a[$j] == $a[$i]) {
push(#indexes, $j);
}
}
if (scalar(#indexes) % 2) {
# If the number of positions collected is odd remove the first
# position from the collection. The number of positions in the
# collection is then even afterwards.
shift(#indexes);
# As we will keep the value at the current position $i no new
# value will move into that position. Hence we have to advance
# the current position.
$i++;
}
# Move through the collected positions.
for (my $k = 0; $k < scalar(#indexes); $k++) {
# Remove the element at the position as indicated by the
# $k'th element of the collect positions.
# We have to subtract $k from the collected position, to
# compensate for the movement of the remaining elements to the
# left.
splice(#a, $indexes[$k] - $k, 1);
}
}
print("#a");
You have a bunch of answers, here's another:
use strict;
use warnings;
use Data::Dumper;
my $input = [ 1, 0, 0, 0, 5, 2, 4, 5, 2, 2 ];
my $output = dedupe_evens($input);
print Data::Dumper->Dump([$input, $output], ['$input', '$output']);
exit;
sub dedupe_evens {
my($input) = #_;
my %seen;
$seen{$_}++ foreach #$input;
my #output = grep {
my $count = delete $seen{$_}; # only want first occurrence
$count && $count % 2;
} #$input;
return \#output;
}
Which produces this output (reformatted for brevity):
$input = [ 1, 0, 0, 0, 5, 2, 4, 5, 2, 2 ];
$output = [ 1, 0, 2, 4 ];

In Perl, how can I stringify a two dimensional array?

Technically speaking there are no multi-dimensional arrays in Perl, but you can use single dimensional arrays in Perl to act as if they had more than one dimension.
In Perl each element of an array can be a reference to another array, but syntactically they would look like a two-dimensional array.
I want to convert 2-dimensional integer array into string in Perl. I have declared 2-dimensional integer array as follows:
my #array1=[[1,2,3],[1,2,3],[1,2,3]];
OR
my #array2=((1,2,3),(1,2,3),(1,2,3));
now I need to create a subroutine that will return string as "{{1,2,3},{1,2,3},{1,2,3}}". I have tried the following subroutine:
sub TwoDArrayOutputString {
my ($outputs)= #_;
my $finaloutput="{";
foreach my $output ($outputs) {
foreach my $out (#$output) {
$finaloutput.="{"."}";
#$finaloutput.="{".join(',',#output)."}";
}
$finaloutput.=",";
}
$finaloutput.="}";
return $finaloutput;
}
sub TwoDArrayOutputString1 {
my ($outputs)= #_;
if ( ref($outputs) eq "REF" ) {$outputs = ${$outputs};}
my $finaloutput="{";
foreach my $output ($outputs) {
foreach my $out (#$output) {
$finaloutput.="{"."}";
#$finaloutput.="{".join(',',#output)."}";
}
$finaloutput.=",";
}
$finaloutput.="}";
return $finaloutput;
}
sub TwoDArrayOutputString2{
my ($array)= #_;
my $finaloutput="{";
for my $row ( 0..$#array ) {
my #columns = #{ $array[$row] }; # Dereferencing my array reference
$finaloutput.="{";
for my $column ( #columns ) {
$finaloutput.=$column.",";
}
$finaloutput=substr($finaloutput,0,length($finaloutput)-1);
$finaloutput.="}".",";
}
$finaloutput=substr($finaloutput,0,length($finaloutput)-1);
$finaloutput.="}";
return $finaloutput;
}
print TwoDArrayOutputString(#array1)."\n";
print TwoDArrayOutputString1(#array1)."\n";
print TwoDArrayOutputString2(#array1)."\n"."\n"."\n"."\n";
print TwoDArrayOutputString(#array2)."\n";
print TwoDArrayOutputString1(#array2)."\n";
print TwoDArrayOutputString2(#array2)."\n"."\n"."\n"."\n";
Output:
{{}{}{},}
{{}{}{},}
}
{,}
{,}
}
and my expected output is {{1,2,3},{1,2,3},{1,2,3}}.
First off, both of your syntaxes are wrong (compared to what I think you think they do):
my #array1=[[1,2,3],[1,2,3],[1,2,3]];
This results in #array1 holding a single reference to an anonymous array which further holds three references to three anonymous arrays when what I think you want is:
my $array1 = [[1,2,3],[1,2,3],[1,2,3]];
$array1 now is a reference to an array that holds three references to three anonymous arrays.
my #array2=((1,2,3),(1,2,3),(1,2,3));
In this case, you are just fooling yourself with all the extra parentheses: All you have is a single array whose elements are 1, 2, 3, 1, 2, 3, 1, 2, 3.
You say
now I need to create a subroutine that will return string as {{1,2,3},{1,2,3},{1,2,3}}.
That is an odd requirement. Why exactly do you need to create such a subroutine?
If you want to serialize the array as a string, you'd be better off using one of the more standard and interoperable ways of doing it, and pick a format such as JSON, YAML, XML, Data::Dumper, or something else.
For example:
$ perl -MJSON::MaybeXS=encode_json -E '#array1=([1,2,3],[1,2,3],[1,2,3]); say encode_json \#array1'
[[1,2,3],[1,2,3],[1,2,3]]
or
$ perl -MData::Dumper -E '#array1=([1,2,3],[1,2,3],[1,2,3]); say Dumper \#array1'
$VAR1 = [
[
1,
2,
3
],
[
1,
2,
3
],
[
1,
2,
3
]
];
or
$ perl -MYAML::XS -E '#array1=([1,2,3],[1,2,3],[1,2,3]); say Dump \#array1'
---
- - 1
- 2
- 3
- - 1
- 2
- 3
- - 1
- 2
- 3
or
$ perl -MXML::Simple -E '#array1=([1,2,3],[1,2,3],[1,2,3]); say XMLout(\#array1)'
<opt>
<anon>
<anon>1</anon>
<anon>2</anon>
<anon>3</anon>
</anon>
<anon>
<anon>1</anon>
<anon>2</anon>
<anon>3</anon>
</anon>
<anon>
<anon>1</anon>
<anon>2</anon>
<anon>3</anon>
</anon>
</opt>
If your purpose is to learn how to traverse a multi-dimensional structure and print it, doing it correctly requires attention to a few details. You could study the source of YAML::Tiny:
sub _dump_array {
my ($self, $array, $indent, $seen) = #_;
if ( $seen->{refaddr($array)}++ ) {
die \"YAML::Tiny does not support circular references";
}
my #lines = ();
foreach my $el ( #$array ) {
my $line = (' ' x $indent) . '-';
my $type = ref $el;
if ( ! $type ) {
$line .= ' ' . $self->_dump_scalar( $el );
push #lines, $line;
} elsif ( $type eq 'ARRAY' ) {
if ( #$el ) {
push #lines, $line;
push #lines, $self->_dump_array( $el, $indent + 1, $seen );
} else {
$line .= ' []';
push #lines, $line;
}
} elsif ( $type eq 'HASH' ) {
if ( keys %$el ) {
push #lines, $line;
push #lines, $self->_dump_hash( $el, $indent + 1, $seen );
} else {
$line .= ' {}';
push #lines, $line;
}
} else {
die \"YAML::Tiny does not support $type references";
}
}
#lines;
}
Now, for your simple case, you could do something like this:
#!/usr/bin/env perl
use feature 'say';
use strict;
use warnings;
my #array = ([1, 2, 3], [4, 5, 6], [7, 8, 9]);
say arrayref_to_string([ map arrayref_to_string($_), #array]);
sub arrayref_to_string { sprintf '{%s}', join(q{,}, #{$_[0]}) }
Output:
{{1,2,3},{4,5,6},{7,8,9}}
You could do something like below:
#!/usr/bin/perl
use strict;
use warnings;
my #array1=[[1,2,3],[1,2,3],[1,2,3]];
foreach my $aref (#array1){
foreach my $inner (#$aref){
print "{";
foreach my $elem (#$inner){
print "$elem";
print ",";
}
print "}";
}
}
PS: I did not understand second array in your example i.e. my #array2=((1,2,3),(1,2,3),(1,2,3));. It's basically just my #array2=(1,2,3,1,2,3,1,2,3);.
One way could be with Data::Dumper. But correctly pass array or array-refs to Dumper. Your #array2 is one-dimensional array.
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
my #array1=[[1,2,3],[1,2,3],[1,2,3]];
my $string = Dumper(#array1);
$string =~ s/\n|\s+|.*?=|;//g;
$string =~ s/\[/\{/g;
$string =~ s/\]/\}/g;
print $string."\n";
output:
{{1,2,3},{1,2,3},{1,2,3}}

How to build a Perl multidimensional array or hash?

I have a set of CSV values like this:
device name, CPU value, frequency of CPU value, CPU in percentage
For example
router1,5,10,4
router1,5,1,5
router2,5,10,4
router2,5,2,5
router3,4,5,6
router3,7,6,5
I need to form a data structure like this:
array = {
router1 => [5,10,4],[5,1,5],
router2 => [5,10,4],[5,2,5],
router3 => [4,5,6],[7,6,5]
}
I need help in forming this data structure in Perl.
I have tried visualizing how to do this but am unable to do so. I would appreciate any help on this.
The end goal for me is to convert this into a JSON object.
This should get you started. It uses the DATA file handle so that I could embed the data in the program itself. I have used to_json from the JSON module to format the hash as JSON data. The statement $_ += 0 for #values converts the contents of #values from string to to numeric, to avoid quotation marks in the resultant JSON data.
use strict;
use warnings;
use JSON;
my %data;
while (<DATA>) {
chomp;
my ($device, #values) = split /,/;
$_ += 0 for #values;
push #{ $data{$device} }, \#values;
}
print to_json(\%data, { pretty => 1, canonical => 1 });
__DATA__
router1,5,10,4
router1,5,1,5
router2,5,10,4
router2,5,2,5
router3,4,5,6
router3,7,6,5
output
{
"router1" : [
[
5,
10,
4
],
[
5,
1,
5
]
],
"router2" : [
[
5,
10,
4
],
[
5,
2,
5
]
],
"router3" : [
[
4,
5,
6
],
[
7,
6,
5
]
]
}
Here is a simple solution which prints desired JSON object.
#!/usr/bin/env perl
use strict;
use warnings;
use 5.010;
my %hash;
while (my $line = <DATA>) {
chomp $line;
my ($device, #cpu_values) = split(/,/, $line);
my $cpu_token = join(",", #cpu_values);
$hash{$device} .= '[' . $cpu_token . '], ';
}
my #devices = keys %hash;
print "array = { \n";
foreach (sort #devices) {
print "$_ => [$hash{$_}]\n";
}
print "}\n";
__DATA__
router1,5,10,4
router1,5,1,5
router2,5,10,4
router2,5,2,5
router3,4,5,6
router3,7,6,5
In Perl you need to use references in the way of anonymous arrays and hashes to make multidimensional arrays, arrays of arrays, hashes containing hashes and anywhere in between. perlreftut should cover how to accomplish what you are trying to do. Here is an example I wrote the other day that could help explain as well:
print "\nFun with multidimensional arrays\n";
my #myMultiArray = ([1,2,3],[1,2,3],[1,2,3]);
for my $a (#myMultiArray){
for my $b (#{$a}){
print "$b\n";
}
}
print "\nFun with multidimensional arrays containing hashes\nwhich contains an anonymous array\n";
my #myArrayFullOfHashes = (
{'this-key'=>'this-value','that-key'=>'that-value'},
{'this-array'=>[1,2,3], 'this-sub' => sub {return 'hi'}},
);
for my $a (#myArrayFullOfHashes){
for my $b (keys %{$a}){
if (ref $a->{$b} eq 'ARRAY'){
for my $c (#{$a->{$b}}){
print "$b.$c => $c\n";
}
} elsif ($a->{$b} =~ /^CODE/){
print "$b => ". $a->{$b}() . "\n";
} else {
print "$b => $a->{$b}\n";
}
}
}

Loop over one dimension of a multi-dimensional array in Perl using for each

* UPDATED* for typos
Another PERL question.... I am trying to loop through a 2D array. I am positive about the size of one dimension but unsure on the second. The code snippet:
foreach my $value (#surfaces[1])
{
my $sum = 0;
my $smallest = 9999;
my $limit_surface = 0;
for (my $i = 0; $i < 3; $i++)
{
$sum += $surfaces[$i][$counter];
if ($surfaces[$i][$counter] <= $smallest)
{
$smallest = $surfaces[$i][$counter];
$limit_surface = $subchannel_number[$i];
}
}
$counter++;
push(#avg_value,$sum/#rodsurface_number);
push(#limiting_schan,$limit_surface);
push(#limiting_value,$smallest);
}
I am compiled but $value variable is failing to initialize.
Repeat after me:
Perl does not have multidimensional arrays
Perl does not have multidimensional arrays
Perl does not have multidimensional arrays
What Perl does have is have are arrays that contain references pointing to other arrays. You can emulate multidimensional arrays in Perl, but they are not true multidimensional arrays. For example:
my #array;
$array[0] = [ 1, 2, 3, 4, 5 ];
$array[1] = [ 1, 2, 3 ];
$array[2] = [ 1, 2 ];
I can talk about $array[0][1], and $array[2][1], but while $array[0][3] exists, $array[2][3] doesn't exist.
If you don't understand references, read the tutorial on references.
What you need to do is go through your array and then find out the size of each subarray and go through each of those. There's no guarantee that
The reference contained in your primary array actually points to another array:
That your sub-array contains only scalar data.
You can use the $# operator to find the size of your array. For example $#array is the number of items in your array. You an use ( 0..$#array ) to go through each item of your array, and this way, you have the index to play around with.
use strict;
use warnings;
my #array;
$array[0] = [ 1, 2, 3, 4, 5 ];
$array[1] = [ 1, 2, 3 ];
$array[2] = [ 1, 2, ];
#
# Here's my loop for the primary array.
#
for my $row ( 0..$#array ) {
printf "Row %3d: ", $row ;
#
# My assumption is that this is another array that contains nothing
# but scalar data...
#
my #columns = #{ $array[$row] }; # Dereferencing my array reference
for my $column ( #columns ) {
printf "%3d ", $column;
}
print "\n";
}
Note I did my #columns = #{ $array[$row] }; to convert my reference back into an array. This is an extra step. I could have simply done the dereferencing in my for loop and saved a step.
This prints out:
Row 0: 1 2 3 4 5
Row 1: 1 2 3
Row 2: 1 2
I could put some safety checks in here. For example, I might want to verify the size of each row, and if one row doesn't match the other, complain:
my $row_size = $array[0];
for my $row ( 1..$#array ) {
my #columns = #{ $array[$row] };
if ( $#columns ne $array_size ) {
die qq(This is not a 2D array. Not all rows are equal);
}
}
You do not describe your data structure, nor explain exactly what you want to do with it. This limits the advice that we can give to just the general variety.
If you're trying to iterate over an array of arrays, I would advise you to do it based off of element instead of index.
For example, below I have a 4 by 5 matrix of integers. I would like to find the average of these values. One way to do this is to simply iterate over each row and then column, and add up the values:
use strict;
use warnings;
my #AoA = (
[11, 12, 13, 14, 15],
[21, 22, 23, 24, 25],
[31, 32, 33, 34, 35],
[41, 42, 43, 44, 45],
);
my $sum = 0;
my $count = 0;
for my $row (#AoA) {
for my $element (#$row) { # <-- dereference the array ref
$sum += $element;
$count++;
}
}
print "Average of Matrix is " . ($sum / $count) . "\n";
Outputs:
Average of Matrix is 28
For more information on complex data structures, check out: Perl Data Structures Cookbook
I've set up some dummy variables and changed a few things around. This compiles and produces the results I show below.
This might not answer your question, but should allow you to copy and paste the code, run it yourself, edit the input and see how the output compares to what you want.
use warnings;
use strict;
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
my #surfaces = ( ['1','2','3'],
['10','20','30'],
['100','200','400'],
);
my #subchannel_number = ( ['1','2','3'],
['10','20','30'],
['100','200','400'],
);
my #rodsurface_number = (1 .. 10);
my $counter = 0;
my (#avg_value, #limiting_schan, #limiting_value);
foreach my $value ($surfaces[1]){
my $sum = 0;
my $smallest = 9999;
my $limit_surface = 0;
for (my $i = 0; $i < 3; $i++) {
$sum += $surfaces[$i][$counter];
if ($surfaces[$i][$counter] <= $smallest){
$smallest = $surfaces[$i][$counter];
$limit_surface = $subchannel_number[$i];
}
}
$counter++;
push(#avg_value,$sum/#rodsurface_number);
push(#limiting_schan,$limit_surface);
push(#limiting_value,$smallest);
}
print Dumper (\#avg_value, \#limiting_schan, \#limiting_value);
$VAR1 = [
'11.1'
];
$VAR2 = [
[
'1',
'2',
'3'
]
];
$VAR3 = [
1
];

Finding common elements in arrays

I have a hash whose values are arrays. I need to find the common elements of those arrays,
ie. the elements that are present in all the arrays. So I extracted the values of the hash into
a multidimensional array whose each row corresponds to an array in the hash. Then I took the first row
of this matrix into another array (#arr1) and iterated through it to find if there was any element
in arr1 that was also in the rest of the rows of the matrix. If such an element is found, it is
pushed onto another array that contains the final list of all the elements. The code is as follows
(I hope it is clear enough):
sub construct_arr(my %records) {
my $len = keys %records;
my #matrix;
my $i = 0;
# Extract the values of the hash into a matrix
foreach my $key (keys %records) {
$matrix[$i] = $records{$key};
$i++;
}
my #arr1 = $matrix[0];
my #final;
# Iterate through each element of arr1
for my $j (0..$#{$arr1[0]}) {
my $count = 1;
# Iterate through each row of the matrix, starting from the second
for ( my $i = 1; $i < $len ; $i++ ) {
my $flag = 0;
# Iterate through each element of the row
for my $k (0..$#{$matrix[$i]}) {
if ($arr1[0][$j] eq $matrix[$i][$k]) {
$flag = 1;
$count++;
}
}
# On finding the first instance of the element in a row, go to the next row
if (!$flag == 1) {
last;
}
}
# If element is in all the rows, push it on to the final array
if ($count == $len) {
push(#final, $arr1[0][$j]);
}
}
return #final;
}
I know that the above works, but I would like to know if there is any other (perlish) way to do this.
I am starting to learn perl and I am very interested in knowing things that could make my work easier
in perl as compared to other languages. If my code is the best that can be done, please let me know that
too. Any guidance would be appreciated. Thanks!
Take a look at Chris Charley's link for calculating the intersection of arrays.
Hashes are the clear way to go for problems like this. Together with map and grep a solution can be reduced to just a few lines.
This program uses sundar's data for want of anything better, and seems to do what you need.
use strict;
use warnings;
my %records = (
a => [ qw/ A B C / ],
b => [ qw/ C D E A / ],
c => [ qw/ A C E / ],
);
print "$_\n" for construct_arr(\%records);
sub construct_arr {
my $records = shift;
my %seen;
$seen{$_}++ for map #$_, values %$records;
grep $seen{$_} == keys %$records, keys %seen;
}
output
A
C
Edit
I thought it may help to see a more Perlish, tidied version of your own solution.
use strict;
use warnings;
my %records = (
a => [ qw/ A B C / ],
b => [ qw/ C D E A / ],
c => [ qw/ A C E / ],
);
print "$_\n" for construct_arr(\%records);
sub construct_arr {
my $records = shift;
my #matrix = values %$records;
my #final;
# iterate through each element the first row
for my $i ( 0 .. $#{$matrix[0]} ) {
my $count = 1;
# look for this value in all the rest of the rows, dropping
# out to the next row as soon as a match is found
ROW:
for my $j ( 1 .. $#matrix ) {
for my $k (0 .. $#{$matrix[$j]}) {
next unless $matrix[0][$i] eq $matrix[$j][$k];
$count++;
next ROW;
}
}
# If element is in all the rows, push it on to the final array
push #final, $matrix[0][$i] if $count == #matrix;
}
return #final;
}
The output is the same as for my own program, but the functionality is slightly different as mine assumes the values in each row are unique. If the sama value appears more than once my solution will break (the same applies to sundar's). Please let me know if that is acceptable.
Although the poster explained there aren't duplicates within a single array, here is my attempt which handles that case too (notice the slightly modified test data - "5" should not be printed):
#!/usr/bin/env perl
use warnings;
use strict;
my %records = (
a => [1, 2, 3],
b => [3, 4, 5, 1],
c => [1, 3, 5, 5]
);
my %seen;
while (my ($key, $vals) = each %records) {
$seen{$_}{$key} = 1 for #$vals;
}
print "$_\n" for grep { keys %{$seen{$_}} == keys %records } keys %seen;
You can find the size of the hash easily using scalar(keys %hash);
Here's an example code that does what you need:
#!/usr/bin/perl
use strict;
use warnings;
my %records = ( a => [1, 2, 3],
b => [3, 4, 5, 1],
c => [1, 3, 5]
);
my %count;
foreach my $arr_ref (values %records) {
foreach my $elem (#$arr_ref) {
$count{$elem}++;
}
}
my #intersection;
my $num_arrays = scalar(keys %records);
foreach my $elem (keys %count) {
#If all the arrays contained this element,
#allowing for multiple entries per array
if ($count{$elem} >= $num_arrays) {
push #intersection, $elem;
}
}
Feel free to comment if you need any clarification in this code. And the second foreach that constructs the #intersection array is written this way only for clarity - if you're learning Perl, I'd suggest you study and rewrite it using the map construct, since that's arguably more idiomatic Perl.

Resources