regex matching I think - arrays

First sorry if I should have added this to my earlier question today, but I now have the below code and am having problems getting things to add up to 100...
use strict;
use warnings;
my #arr = map {int( rand(49) + 1) } ( 1..100 ); # build an array of 100 random numbers between 1 and 49
my #count2;
foreach my $i (1..49) {
my #count = join(',', #arr) =~ m/,$i,/g; # ???
my $count1 = scalar(#count); # I want this $count1 to be the number of times each of the numbers($i) was found within the string/array.
# push(#count2, $count1 ." times for ". $i); # pushing a "number then text and a number / scalar, string, scalar" to an array.
push(#count2, [$count1, $i]);
}
#sort #count2 and print the top 7
my #sorted = sort { $b->[0] <=> $a->[0] } #count2;
my $sum = 0;
foreach my $i (0..$#sorted) { # (0..6)
printf "%d times for %d\n", $sorted[$i][0], $sorted[$i][1];
$sum += $sorted[$i][0]; # try to add up/sum all numbers in the first coloum to make sure they == 100
}
print "Generated $sum random numbers.\n"; # doesn't add up to 100, I think it is because of the regex and because the first number doesn't have a "," in front of it
# seem to be always 96 or 97, 93...

Replace these two lines:
my #count = join(',', #arr) =~ m/,$i,/g; # ???
my $count1 = scalar(#count); # I want this $count1 to be the number of times each of the numbers($i) was found within the string/array.
with this:
my $count1 = grep { $i == $_ } #arr;
grep will return a list of elements where only the expression in {} evaluates to true. This is less error-prone and much more efficient than joining the entire array and using a a regex. Also note that scalar is not necessary since the variable $count1 is scalar, so perl will return the result of grep in scalar context.
You can also get rid of this line:
push(#count2, $count1 ." times for ". $i); # pushing a "number then text and a number / scalar, string, scalar" to an array.
since you are already printing the same information in your last foreach loop.

#!/usr/bin/perl
use strict; use warnings;
use YAML;
my #arr;
$#arr = 99;
my %counts;
for my $i (0 .. 99) {
my $n = int(rand(49) + 1);
$arr[ $i ] = $n;
++$counts{ $n };
}
my #result = map [$_, $counts{$_}],
sort {$counts{$a} <=> $counts{$b} }
keys %counts;
my $sum;
$sum += $_->[1] for #result;
print "Number of draws: $sum\n";

You can probably reuse some well-tested code from List::MoreUtils.
use List::MoreUtils qw/ indexes /;
...
foreach my $i (1..49) {
my #indexes = indexes { $_ == $i } #arr;
my $count1 = scalar( #indexes );
push( #count2, [ $count1, $i ] );
}
If you don't need the warns in the sum loop, then I'd recommend using sum from List:Util.
use List::Util qw/ sum /;
...
my $sum = sum map { $_->[0] } #sorted;
If you insist on the loop, rewrite it as:
foreach my $i ( #sorted ) {

Related

For Loop Issues in creating nested array

Creating a matrix of products for three element arrays. I understand Perl does not have multi-dimensional arrays and are flattened. I have been using refs but I can't seem to get past the for loop issue in getting three products into a single array and pushing that array into a different single array. And I could be way off too. Be nice, but I've spent too many hours on this.
I have moved values inside and out of various places i.e. { }, printed out variables until I'm blue and used $last all over for debugging. I'm likely fried at this point.
use strict;
use warnings;
my #array1 = (1, 2, 3);
my #array2 = (2, 4, 6);
my #matrixArray = ();
my $matrixArray;
my #row;
my #finalArray = maths(\#array1, \#array2);
print #finalArray;
sub maths{
my $array1ref = shift;
my $array2ref = shift;
my $value1;
my $value2;
my $maths;
my #row = ();
my #array1 = #{$array1ref};
my #array2 = #{$array2ref};
my $len1 = #array1;
my $len2 = #array2;
for my $x (0 ..($len1 -1)){
#iterate through first array at each value
$value1 = $array1[$x];
#print $value1, " value1 \n";
for my $y (0 .. ($len2 -1)){
#iterate through second array at each value
$value2 = $array2[$y];
#print $value2, " value2 \n";
#calculate new values
$maths = $value1 * $value2;
#exactly right here
#print $maths, " maths \n" ;
push #row, $maths;
}
}
#and exactly right here but not set of arrays
#print #row, "\n";
return #row;
}
Currently I'm able to get this: 246481261218. Which is the correct dumb math but...
it should appear as a matrix:
2 4 6
4 8 12
6 12 18
I am not passing three arrays so it seems my issue is up in the sub routine before I can get on with anything else. This seems to be a theme that I often miss. So sorry if I sound inept.
EDIT***
This was working but I couldn't unpack it
use strict;
use warnings;
my #array1 = (1, 2, 3);
my #array2 = (2, 4, 6);
my #matrixArray = ();
maths(\#array1, \#array2);
foreach my $x (#matrixArray){
print "$x \n";
}
sub maths{
my $array1ref = shift;
my $array2ref = shift;
my $value1;
my $value2;
my $maths;
my #row = ();
my $row;
my #array1 = #{$array1ref};
my #array2 = #{$array2ref};
my $len1 = #array1;
my $len2 = #array2;
for my $x (0 ..($len1 -1)){
#iterate through first array at each value
$value1 = $array1[$x];
for my $y (0 .. ($len2 -1)){
#iterate through second array at each value
$value2 = $array2[$y];
#calculate new values
$maths = $value1 * $value2;
push #row, $maths;
$row = \#row;
}
push #matrixArray, $row;
}
return #matrixArray;
}
The output right after the function call is this:
ARRAY(0x55bbe2c667b0)
ARRAY(0x55bbe2c667b0)
ARRAY(0x55bbe2c667b0)
which would be the (line 10) print of $x.
****EDIT
This Works (almost):
print join(" ", #{$_}), "\n" for #matrixArray;
Output is a bit wrong...
2 4 6 4 8 12 6 12 18
2 4 6 4 8 12 6 12 18
2 4 6 4 8 12 6 12 18
And of note: I knew $x was an array but I seemed to run into trouble trying to unpack it correctly. And I'm no longer a fan of Perl. I'm pining for the fjords of Python.
And *****EDIT
This is working great and I get three arrays out of it:
sub maths{
my ($array1, $array2) = #_;
my #res;
for my $x (#$array1) {
my #row;
for my $y (#$array2) {
push #row, $x * $y;
}
push #res, \#row;
}
#This is the correct structure on print #res!
return #res;
}
But, though it's putting it together correctly, I have no output after the call
maths(\#array1, \#array2);
NOTHING HERE...
print #res;
print join(" ", #{$_}), "\n" for #res;
foreach my $x (#res){
print join(" ", #{$x}), "\n";
}
And of course a million thanks! I regret taking this stupid course and fear my grade will eventually do me in. Still pining for Python!
It appears that you need a matrix with rows obtained by multiplying an array by elements of another.
One way
use warnings;
use strict;
use Data::Dump qw(dd);
my #ary = (2, 4, 6);
my #factors = (1, 2, 3);
my #matrix = map {
my $factor = $_;
[ map { $_ * $factor } #ary ]
} #factors;
dd #matrix;
The array #matrix, formed by the outer map, has array references for each element and is thus (at least) a two-dimensional structure (a "matrix"). Those arrayrefs are built with [ ], which creates an anonymous array out of a list inside. That list is generated by map over the #ary.
I use Data::Dump to nicely print complex data. In the core there is Data::Dumper.
With a lot of work like this, and with large data, efficiency may matter. The common wisdom would have it that direct iteration should be a bit faster than map, but here is a benchmark. This also serves to show more basic ways as well.
use warnings;
use strict;
use feature 'say';
use Benchmark qw(cmpthese);
my $runfor = shift // 5; # run each case for these many seconds
sub outer_map {
my ($ary, $fact) = #_;
my #matrix = map {
my $factor = $_;
[ map { $_ * $factor } #$ary ]
} #$fact;
return \#matrix;
}
sub outer {
my ($ary, $fact) = #_;
my #matrix;
foreach my $factor (#$fact) {
push #matrix, [];
foreach my $elem (#$ary) {
push #{$matrix[-1]}, $elem * $factor;
}
}
return \#matrix;
}
sub outer_tmp {
my ($ary, $fact) = #_;
my #matrix;
foreach my $factor (#$fact) {
my #tmp;
foreach my $elem (#$ary) {
push #tmp, $elem * $factor;
}
push #matrix, \#tmp;
}
return \#matrix;
}
my #a1 = map { 2*$_ } 1..1_000; # worth comparing only for large data
my #f1 = 1..1_000;
cmpthese( -$runfor, {
direct => sub { my $r1 = outer(\#a1, \#f1) },
w_tmp => sub { my $r2 = outer_tmp(\#a1, \#f1) },
w_map => sub { my $r3 = outer_map(\#a1, \#f1) },
});
On a nice machine with v5.16 this prints
Rate direct w_map w_tmp
direct 11.0/s -- -3% -20%
w_map 11.4/s 3% -- -17%
w_tmp 13.8/s 25% 21% --
The results are rather similar on v5.29.2, and on an oldish laptop.
So map is a touch faster than building a matrix directly, and 15-20% slower than the method using a temporary array for rows, which I'd also consider clearest. The explicit loops can be improved a little by avoiding scopes and scalars, and the "direct" method can perhaps be sped up some by using indices. But these are dreaded micro-optimizations, and for fringe benefits at best.
Note that timings such as these make sense only with truly large amounts of data, what the above isn't. (I did test with both dimensions ten times as large, with very similar results.)
The second program is mostly correct.
The problem is that you didn't unpack the second level of the array.
foreach my $x (#matrixArray){
print "$x \n";
}
should be something like:
foreach my $x (#matrixArray) {
print join(" ", #{$x}), "\n";
}
# or just:
print join(" ", #{$_}), "\n" for #matrixArray;
Your maths function can be made shorter without losing legibility (it may actually make it more legible) by cutting out unnecessary temporary variables and indexing. For example:
sub maths {
my #array1 = #{ $_[0] };
my #array2 = #{ $_[1] }; # or: ... = #{ (shift) };
my #res = ();
for my $x (#array1) {
my #row = (); # <-- bugfix of original code
for my $y (#array2) {
my $maths = $x * $y;
push #row, $maths;
}
push #res, \#row;
}
return #res;
}

compare multiple arrays in perl

I have 2D arrays (#AoA) that contains references to other arrays of strings. Size of this #AoA is different each time. I would like to compare each of these arrays of strings to each other.
To compare first array of strings to each other I can use something like this:
for (my $i=0; $i < $#AoA; $i++) {
my $lcm = List::Compare->new( $aAoA[$i], $AoA[$i+1] );
my #intersection = $lcm->get_intersection;
if (#intersection) {
#some code here
}
But what the best way to compare Each array with Each other?
I would like the results like this:
Arr1 Arr2 …. ArrN
Arr1 x 1 match 3 matches 0 matches
Arr2 x N matches 3 matches
…. x 1 match
ArrN x
If you feel comfortable with List::Compare, then you could use it instead of my function intersect_count.
#!/usr/bin/perl
use strict;
use warnings;
use List::MoreUtils 'uniq';
use List::Util 'any';
my #AoA = ([1,2,3,4,], [3,4,5,6], [4,7,8,9], [11,22,33]);
my #hdrs = map "Array_$_", 1 .. #AoA;
my $fmt = "%-10s" . "%-10s" x #hdrs . "\n";
printf $fmt, ' ', #hdrs;
for (my $i=0; $i < $#AoA; $i++) {
my #matches;
for (my $j = $i+1; $j < #AoA; $j++) {
$matches[$j] = intersect_count( $AoA[$i], $AoA[$j]);
}
printf $fmt, $hdrs[$i], map $_ // ' ', #matches;
}
sub intersect_count {
my ($a1, $a2) = #_;
my $cnt;
for my $item (uniq #$a1) {
$cnt += any {$item eq $_} uniq #$a2;
}
return $cnt;
}
This prints
. Array_1 Array_2 Array_3 Array_4
Array_1 2 1 0
Array_2 1 0
Array_3 0

Perl while loop won't exit when blank line is entered

I am trying to print the sum, maximum, and minimum values of list of numbers but I am struggling to get it working.
When I press Enter the loop should exit but the program keeps running
use strict;
use warnings;
my #items;
my ( $sum, $max, $min );
while ( chomp( my $num = <STDIN> ) ) {
last if ( $num eq '\n' );
$max ||= $num;
$min ||= $num;
$sum += $num;
$max = $num if ( $num > $max );
$min = $num if ( $num < $min );
push( #items, $num );
}
printf( "Entered numbers are: %s \n", join( ', ', #items ) );
print( "Sum of all numbers is : ", $sum );
print "\n";
print( "Minimum number is : ", $min );
print "\n";
print( "Maximum number is : ", $max )
You can't use chomp inside a while condition like this
while (chomp(my $num = <STDIN>)) { ... }
because the while loop needs to terminate when the <> returns undef at end of file. So you must put the chomp as the first statement of the loop
The simplest way to exit a loop like this is to check whether the input contains any non-space characters using the regular expression /\S/
The check
last if ( $num eq '\n' )
won't work because you have used chomp to remove the newline from the input. Also, if you use single quotes '\n' is the two-character string \ followed by n. You need double quotes like "\n" to create a newline
When a scalar variable is first declared it has the value undef, so you can avoid any clumsy initialisation by testing for this and updating $min and $max unless the previous value is already defined and higher (or lower) than the new value
I would rewrite your program like this.
use strict;
use warnings;
my #items;
my ($sum, $max, $min);
while (my $num = <STDIN>) {
chomp $num;
last unless $num =~ /\S/;
$max = $num unless defined $max and $max >= $num;
$min = $num unless defined $min and $min <= $num;
$sum += $num;
push #items, $num;
}
print 'Entered numbers are: ', join(', ', #items), "\n";
print "Sum of all numbers is: $sum\n";
print "Minimum number is: $min\n";
print "Maximum number is: $max\n";
If you want to detect Enter, you can't chomp the input before. Also, '\n' is not the enter, you have to use double quotes "\n" to enable special characters.
The while (<>) loop is usually ended by Ctrl+D.
Moreover, you can find a module to do the work for you.
#!/usr/bin/perl
use warnings;
use strict;
use List::Util qw{ max min sum };
my #items = <>;
chomp #items;
my $max = max(#items);
my $min = min(#items);
my $sum = sum(#items);
print "Entered numbers are: #items\n";
print "Sum of all numbers is: $sum\n";
print "Minimum number is: $min\n";
print "Maximum number is: $max";

How do I use an array of values to limit a for loop in perl?

The question is rather vague I know, but I hope the space to explain may help shed light, this is something I've wracked my brain around all day and couldn't find any advice through searching.
Basically I have an array #cluster that I'm trying to use to make an iterator $x skip over the values located in that array. The array will vary in size, so I can't just (rather atrociously) make if statements to fit all cases unfortunately.
Normally when I need to do this with a scalar value I just do:
for my $x (0 .. $numLines){
if($x != $value){
...
}
}
Any advice?
You can do:
my #cluster = (1,3,4,7);
outer: for my $x (0 .. 10){
$x eq $_ and next outer for #cluster;
print $x, "\n";
}
With Perl 5.10 you can also do:
for my $x (0 .. 10){
next if $x ~~ #cluster;
print $x, "\n";
}
or better to use a hash:
my #cluster = (1,3,4,7);
my %cluster = map {$_, 1} #cluster;
for my $x (0 .. 10){
next if $cluster{$x};
print $x, "\n";
}
Hmm... If you are skipping over lines, why not use that criteria directly instead of remembering the lines that need to be filtered out?
The grep function is a powerful construct for filtering lists:
my #array = 1 .. 10;
print "$_\n" for grep { not /^[1347]$/ } #array; # 2,5,6,8,9,10
print "$_\n" for grep { $_ % 2 } #array; # 1,3,5,7,9
my #text = qw( the cat sat on the mat );
print "$_\n" for grep { ! /at/ } #text; # the, on, the
Much less clutter, and much more DWIM!
Dou you mean something like that:
for my $x (0 .. $numLines){
my $is_not_in_claster = 1;
for( #claster ){
if( $x == $_ ){
$is_not_in_claster = 0;
last;
}
}
if( $is_not_in_claster ){
...
}
}
?

How can I multiply arrays and print the result as a matrix in perl?

I am new to Perl and would like to multiply two arrays:
my #array1 = (1,2,3);
my #array2 = (2,4,6);
and print out as such:
# output ==
# 2 4 6
# 4 8 12
# 6 12 18
edit:
Got it
my #array1 = (1,2,3);
my #array2 = (2,4,6);
multiply_array(\#array1, \#array2);
sub multiply_array{
my ($a1, $a2)=#_;
for(my $i=0; $i<3; $i++){
for(my $j=0; $j<3; $j++){
my $x = #$a1[$i]*#$a2[$j];
print $x."\t";
}
print "\n";
}
}
The operation you are trying to do on the arrays is called a cartesian product.
Creating an array of arrays with the cartesian product:
my #product = map { my $a = $_; [map { $_ * $a } #array2] } #array1;
Printing it in a very verbose format:
use Data::Dumper;
Dumper(#product);
Just printing the product without storing it into an array:
for my $x (#array1) {
for my $y (#array2) {
print $x * $y . "\t";
}
print "\n";
}
You have passed references of the arrays to the function.
Hence you need to use -> to access the array elements.
Here's what you can do:
use strict;
use warnings;
my #array1 = (1,2,3);
my #array2 = (2,4,6);
multiply_array(\#array1, \#array2);
sub multiply_array{
my ($a1, $a2)=#_;
for(my $i=0; $i<3; $i++){
for(my $j=0; $j<3; $j++){
my $x = $a1->[$i]*$a2->[$j];
print $x."\t";
}
print "\n";
}
}

Resources