Adding values together from a hash - arrays

I'm trying to add keys together from a hash to get a total from the added values.
Here is what I have so far. Help is appreciated.
print "What is your name?\n";
$letters = <STDIN>;
%alphabet = {
a=>1, b=>2, c=>3, d=>4, e=>5, f=>6, g=>7, h=>8,
i=>9, j=>10, k=>11, l=>12, m=>13,n=>14, o=>15,
p=>16, q=>17, r=>18, s=>19, t=>20, u=>21, v=>22,
w=>23, x=>24, y=>25, z=>26
};
#characters = split('', $letters);
#$characters = keys (%alphabet);
foreach #$character {
$starting_total = 0;
$total = $starting_total + #$character - 10;
print "$total\n";
};

This program will do as you ask
Rather than using split, it applies a global regular expression that finds all of the alphabetic characters in the string. A call to lc makes each letter lower-case to match the hash keys
use strict;
use warnings 'all';
my %alphabet = (
a => 1, b => 2, c => 3, d => 4, e => 5, f => 6, g => 7, h => 8,
i => 9, j => 10, k => 11, l => 12, m => 13, n => 14, o => 15,
p => 16, q => 17, r => 18, s => 19, t => 20, u => 21, v => 22,
w => 23, x => 24, y => 25, z => 26
);
print 'What is your name? ';
my $name = <>;
my $total = 0;
while ( $name =~ /([a-z])/gi ) {
my $letter = $1;
my $n = $alphabet{lc $letter};
printf "%s %2d\n", $letter, $n;
$total += $n;
}
printf "Total %d\n", $total;
output
What is your name? Kokio
K 11
o 15
k 11
i 9
o 15
Total 61
Note that there is no need for a hash to calculate the index of a letter within the alphabet. You could do arithmetic on the code points of the letters, like this
my $n = 1 + ord(lc $letter) - (ord 'a');
or you could declare a constant string ALPHABET and then use index to find the position of each character within it
use constant ALPHABET => join "", 'a' .. 'z';
my $n = 1 + index ALPHABET, lc $letter;
These alternatives produce exactly the same result, as the solution above, and don't require the hash

I'm trying to add keys together from a hash to get a total from the added values.
I don't think you are. The keys in your hash are letters. You can't (sensibly) add letters together. I think you're trying to add together the values from a hash which match a list of keys.
Accuracy and precision are important traits in a programmer. If you can't describe your problem accurately and precisely, then you have little chance of solving it.
Your code doesn't even compile. Let's take a look at it.
# You should always start your Perl programs with "use strict"
# and "use warnings".
print "What is your name?\n";
# When you "use strict" you will need to declare all of your variables
# using "my". So "my $letters = <STDIN>"
$letters = <STDIN>;
# Similarly, "my %alphabet = ..."
# But there are far better ways to set up this hash, as we'll see
# later.
# Also (as Borodin points out in a comment) you have initialised this
# hash incorrectly. A hash should be initialised with a list:
# %alphabet = (a => 1, ...);
# Note the round parentheses indicating a list.
# You have initialised your hash with a single-element list containing
# a hash reference - braces { ... } are the anonymous hash constructor
# and they return a reference to the new hash.
# This is an error that would have been picked up by "use warnings".
%alphabet = {
a=>1, b=>2, c=>3, d=>4, e=>5, f=>6, g=>7, h=>8,
i=>9, j=>10, k=>11, l=>12, m=>13,n=>14, o=>15,
p=>16, q=>17, r=>18, s=>19, t=>20, u=>21, v=>22,
w=>23, x=>24, y=>25, z=>26
};
# "my #characters ..."
#characters = split('', $letters);
# But you're also using an array reference called $characters.
# That's bound to confuse you at some point in the future
#$characters = keys (%alphabet);
# This is the bit that doesn't compile. It should be
# "foreach (#character)". But that's also not right as it uses
# an array called #character, and you don't have an array called
# #character (you have an array called #characters). "use strict"
# will catch errors like this.
# Also, each time round this loop, one of the elements from #character
# will be put into $_. But you don't use $_ in your code at all.
foreach #$character {
# Do you really want to set this to 0 each time?
$starting_total = 0;
# #$character is the number of elements in the array referenced
# by $character. Which is zero as you don't have an array
# reference called $character. I assume you meant #$characters,
# but that is always going to be 26 - which doesn't seem useful.
# And why subtract 10?
$total = $starting_total + #$character - 10;
print "$total\n";
}
Your description of the problem is incredibly vague, but looking at your code (and guessing a lot) I think what you're trying to do is this:
Get a name for the user
Split the name into individual letters
Encode each letter into a number (a=1, b=2, ..., z=26)
Sum the letters in the name
Here's how I would do that.
#/usr/bin/perl
use strict;
use warnings;
# We use modern Perl, specifically say()
use 5.010;
print 'What is your name? ';
chomp(my $name = <STDIN>);
my %letters;
#letters{'a' .. 'z'} = (1 .. 26);
my $total;
foreach (split //, $name) {
$_ = lc $_; # force lower case
next unless exists $letters{$_}; # ignore non-letters
$total += $letters{$_};
}
say "$name is $total";

I don't know what you exactly want. I added the script which gives the addition of the character position
print "Enter your name: ";
chomp (my $name = <STDIN>);
my #arc = split('',$name);
my $total;
my $lc_offset = ord("a") - 1;
foreach (#arc)
{
$total+=(ord(lc($_))) - $lc_offset;
}
print $total;
No need to store the position of the alphabets in hashes. Becuase perl has inbuilt function ord. so the small letters are starts at 97.

It's quite unclear from your question, so I will guess you want to get the numeric sum of all letters in a word.
#!/usr/bin/perl
use strict;
use warnings;
use constant ORD_LC_OFFSET => ord('a') - 1;
print "What is your name?\n";
chomp (my $name = <STDIN>);
my $sum = 0;
$sum += ord( lc($_) ) - ORD_LC_OFFSET for grep { m/[a-zA-Z]/ } split '', $name;
print "$sum\n";
We split the name to the characters and grep only the letter characters. Then we convert each character to the index of the letter (ord does the magic and converts the letter to it's ASCII value). Now we add that to $sum.

Related

Printing "Multi-Dimensional" Array in Perl

I am having a problem attempting to print an array that contains arrays. When printing the array #dev which contains the other arrays, I am only managing to print the first three as it is indicated by the #printing in-line comments. The commented line #print($dev[4][2]); works fine, as well as any of the other combination of numbers within the allowable range. For some reason the for loop does not work. Help!?
my #dev;
my #tf;
my #a;
my #t;
my #f;
my #ofv;
my #tfv;
#tf = ('123456787', '123456788', '123456789'); #printing
#a = (78, 65, 57); #printing
#t = (70, 55, 42); #printing
#f = (77, 64, 56);
#ofv = ('true', 'false', 'false');
#tfv = ('false', 'true', 'true');
#dev = (
[#tf],
[#a],
[#t],
[#f],
[#ofv],
[#tfv],
);
#print($dev[4][2]);
for (my $i = 0; $i <= (scalar(#tf) - 1); $i++) {
for (my $j = 0; $j <= (scalar(#dev) - 1); $j++) {
print($dev[$i][$j]);
print("\n");
}
}
Thank you.
If you just want to show the data of such complex data struct, the modules Data::Dumper or Smart::Comments may be good options.
use Data::Dumper;
print Dumper(\#dev);
or
use Smart::Comments;
### #dev
The output is much more perl-style and not that readable, but is quite convenient to show the struct of such complex data.
Perl can be quite compact.
This snippet of code do the same thing for my arrays #arr1, #arr2 and #arr3:
#arr1 = (1..10);
#arr2 = ('a'..'j');
#arr3 = ('.') x 10;
#multid = \(#arr1, #arr2, #arr3);
print "#$_\n" for (#multid);
OUTPUT:
1 2 3 4 5 6 7 8 9 10
a b c d e f g h i j
. . . . . . . . . .
Also the [] copies an array and gives a reference to it (It's an anonymous array in memory, regardless of the array, a copy of which he is). If there is no need to such duplicate, it is better to use the backslash \ which instead gives a reference to existing array without coping. (like & operator in C, as tell us perldoc)
Your outermost for loop is constrained by the length of t, which is 3. It will never print more than three arrays.
If I understand what you're trying to do, you need top swap #t and #dev. That will print all your values.
That won't, however, print any array that is longer than 3 (the length of dev).
For that, you need:
#dev = (
[#tf], # Probably meant tf
[#a],
[#t],
[#f],
[#ofv],
[#tfv],
);
#print($dev[4][2]);
for (my $i = 0; $i < #dev; $i++) {
for (my $j = 0; $j < #{ $dev[$i] }; $j++) {
print($dev[$i][$j]);
print("\n");
}
}

Is it possible to assign two variables in Perl foreach loop?

Is it possible to assign two variables the same data from an array in a Perl foreach loop?
I am using Perl 5, I think I came across something in Perl 6.
Something like this:
my $var1;
my $var2;
foreach $var1,$var2 (#array){...}
It's not in the Perl 5 core language, but List::Util has a pairs function which should be close enough (and a number of other pair... functions which may be more convenient, depending on what you're doing inside the loop):
#!/usr/bin/env perl
use strict;
use warnings;
use 5.010;
use List::Util 'pairs';
my #list = qw(a 1 b 2 c 3);
for my $pair (pairs #list) {
my ($first, $second) = #$pair;
say "$first => $second";
}
Output:
a => 1
b => 2
c => 3
The easiest way to use this is with a while loop that calls splice on the first two elements of the array each time,
while (my($var1, $var2) = splice(#array, 0, 2)) {
...
}
However, unlike foreach, this continually does a double-shift on the original array, so when you’re done, the array is empty. Also, the variables assigned are copies, not aliases as with foreach.
If you don’t like that, you can use a C-style for loop:
for (my $i = 0; $i < #array; $i += 2) {
my($var1, $var2) = #array[$i, $i+1];
...
}
That leaves the array in place but does not allow you to update it the way foreach does. To do that, you need to address the array directly.
my #pairlist = (
fee => 1,
fie => 2,
foe => 3,
fum => 4,
);
for (my $i = 0; $i < #pairlist; $i += 2) {
$pairlist[ $i + 0 ] x= 2;
$pairlist[ $i + 1 ] *= 2;
}
print "Array is #pairlist\n";
That prints out:
Array is feefee 2 fiefie 4 foefoe 6 fumfum 8
You can get those into aliased variables if you try hard enough, but it’s probably not worth it:
my #kvlist = (
fee => 1,
fie => 2,
foe => 3,
fum => 4,
);
for (my $i = 0; $i < #kvlist; $i += 2) {
our ($key, $value);
local(*key, $value) = \#kvlist[ $i, $i + 1 ];
$key x= 2;
$value *= 2;
}
print "Array is #kvlist\n";
Which prints out the expected changed array:
Array is feefee 2 fiefie 4 foefoe 6 fumfum 8
Note that the pairs offered by the List::Pairwise module, which were but very recently added to the core List::Util module (and so you probably cannot use it), are still not giving you aliases:
use List::Util 1.29 qw(pairs);
my #pairlist = (
fee => 1,
fie => 2,
foe => 3,
fum => 4,
);
for my $pref (pairs(#pairlist)) {
$pref->[0] x= 2;
$pref->[1] *= 2;
}
print "Array is #pairlist\n";
That prints out only:
Array is fee 1 fie 2 foe 3 fum 4
So it didn’t change the array at all. Oops. :(
Of course, if this were a real hash, you could double the values trivially:
for my $value (values %hash) { $value *= 2 }
The reasons that works is because those are aliases into the actual hash values.
You cannot change the keys, since they’re immutable. However, you can make a new hash that’s an updated copy of the old one easily enough:
my %old_hash = (
fee => 1,
fie => 2,
foe => 3,
fum => 4,
);
my %new_hash;
#new_hash{ map { $_ x 2 } keys %old_hash } =
map { $_ * 2 } values %old_hash;
print "Old hash is: ", join(" " => %old_hash), "\n";
print "New hash is: ", join(" " => %new_hash), "\n";
That outputs
Old hash is: foe 3 fee 1 fum 4 fie 2
New hash is: foefoe 6 fiefie 4 fumfum 8 feefee 2
A general algorithm for more than 2 variables:
while( #array ){
my $var1 = shift #array;
my $var2 = shift #array;
my $var3 = shift #array;
# other variables from #array
# do things with $var1, $var2, $var3, ...
}
PS: Using a working copy of the array to that it is preserved for use later:
if( my #working_copy = #array ){
while( #working_copy ){
my $var1 = shift #working_copy;
my $var2 = shift #working_copy;
my $var3 = shift #working_copy;
# other variables from #working_copy
# do things with $var1, $var2, $var3, ...
}
}
PPS: another way is to use indexing. Of course, that is a sure sign that the data structure is wrong. It should be an array of arrays (AoA) or an array of hashes (AoH). See perldoc perldsc and perldoc perllol.
my $i = 0;
while( $i < #array ){
my $var1 = $array[ $i++ ];
my $var2 = $array[ $i++ ];
my $var3 = $array[ $i++ ];
# other variables from #array
# do things with $var1, $var2, $var3, ...
}
PPPS: I've been asked to clarify why the data structure is wrong. It is a flatten set of tuples (aka records aka datasets). The tuples are recreated by counting of the number of data for each. But what is the reader constructing the set has a bug and doesn't always get the number right? If, for a missing value, it just skips adding anything? Then all the remaining tuples are shifted by one, causing the following tuples to be grouped incorrectly and therefore, invalid. That is why an AoA is better; only the tuple with the missing data would be invalid.
But an better structure would be an AoH. Each datum would access by a key. Then new or optional data can be added without breaking the code downstream.
While I'm at it, I'll add some code examples:
# example code for AoA
for my $tuple ( #aoa ){
my $var1 = $tuple->[0];
my $var2 = $tuple->[1];
my $var3 = $tuple->[2];
# etc
}
# example code for AoH
for my $tuple ( #aoh ){
my $var1 = $tuple->{keyname1};
my $var2 = $tuple->{key_name_2};
my $var3 = $tuple->{'key name with spaces'};
my $var4 = $tuple->{$key_name_in_scalar_variable};
# etc
}
Here is a module-less way to "loop" by an arbitrary value ($by) and output the resulting group of elements using an array slice:
#!perl -l
#array = "1".."6";
$by = 3; $by--;
for (my $i = 0 ; $i < #array ; $i += $by ) {
print "#array[$i..$i+$by]";
$i++ ;
}
As a one-liner to test (cut and paste to a Unix shell):
perl -E '#array = "1".."6"; $by = 3; $by--;
for (my $i = 0 ; $i < #array ; $i += $by ) {
say "#array[$i..$i+$by]"; $i++ }'
Output:
1 2 3
4 5 6
If you make $by = 2; it will print pairs of numbers. To get at specific elements of the resulting slice access it as an anonymous array: (e.g. [#array[$i..$i+$by]]->[1]).
See also:
How do I read two items at a time in a Perl foreach loop?
Perl way of iterating over 2 arrays in parallel
Some good responses there, including reference to natatime which is quite easy to use. It's easy to implement too - it is essentially a wrapper around the splice solutions mentioned in the responses here.
The following is not the nicest example, but I've been using autobox::Core and made an #array->natatime() "method" ;-) like this:
use autobox::Core ;
sub autobox::Core::ARRAY::natatime {
my ($self, $by) = #_;
my #copy = #$self ;
my #array ;
push #array, [splice (#copy, 0, $by) ] while #copy ;
if ( not defined wantarray ) {
print "#{ $_ } \n" for #array ;
}
return wantarray ? #array : \#array;
}
The #copy array is spliced destructively, but $self (which is how the #array in front of the autobox method -> arrow gets passed to the function) is still there. So I can do:
my #dozen = "1" .. "12" ; # cakes to eat
#dozen->natatime(4) ; # eat 4 at time
my $arr_ref = #dozen->natatime(4) ; # make a reference
say "Group 3: #{ $arr_ref->[2] }" ; # prints a group of elements
say scalar #dozen , " cakes left" ; # eat cake; still have it
Output:
1 2 3 4
5 6 7 8
9 10 11 12
Group 3: 9 10 11 12
12 cakes left
One other approach that also uses a CPAN module (I gave this answer elsewhere but it is worth repeating). This can also be done non-destructively, with Eric Strom's excellent List::Gen module:
perl -MList::Gen=":all" -E '#n = "1".."6"; say "#$_" for every 2 => #n'
1 2
3 4
5 6
Each group of elements you grab is returned in an anonymous array so the individual values are in: $_->[0] $_->[1] ... etc.
You mentioned Perl6, which handles multiple looping values nicely:
my #qarr = 1 .. 6;
my ($x, $y, $z) ;
for #qarr -> $x , $y , $z { say $x/$y ; say "z = " ~ $z }
Output:
0.5
z = 3
0.8
z = 6
For more on the Perl6 approach see: Looping for Fun and Profit from the 2009 Perl6 Advent Calendar, or the Blocks and Statements Synopsis for details. Perhaps Perl 5 will have a similar "loop by multliple values" construct one day - à la perl5i's foreach :-)

searching two array string for equal words

I am a beginner in Perl. I have two string arrays array1 and array2. I want to check the each and every element in 2nd array. if there is i want to give a relative value one to that particular element in the 2nd array. the relative values are store in an array.I try it out but it wont work and git gives a warning like" Use of uninitialized value in string eq at pjt.pl line 52, line 3".
while($i <= (scalar #resultarray-1))
{
while ($j <= (scalar #inputsymbl-1))
{
if ($resultarray[$i] eq $inputsymbl[$j])
{
$rel[$j]=1;
$i=$i+1;
$j=0;
}
else
{
$j=$j+1;
}
}
if($j==(scalar #inputsymbl))
{
$i=$i+1;
$j=0;
}
}
try this:
my $i = 0;
my $j = 0;
## walk each array element
foreach(#resultarray) {
my $result = $_;
foreach(#inputsymbl) {
my $symbl = $_;
if ($result eq $symbl) {
$rel[$j] = 1;
$i++;
} else {
$j++;
}
}
if ($j == (scalar #inputsymbl - 1)) {
$i++;
$j = 0;
}
}
provide more informations if you need detailed help.
From your question and code, it appears that you want to flag the indexes, by using a third array, of the two array's elements that are equal. By doing this, however, you're creating a sparse array. Also, if the two arrays don't have the same number of elements, a "Use of uninitialized value in string eq..." warning will eventually occur. Given these issues, consider using the smaller index of the two arrays (done using the ternary operator below) and pushing the indexes of the equal elements onto the third array:
use strict;
use warnings;
use Data::Dumper;
my #results;
my #arr1 = qw/A B C D E F G H I J/;
my #arr2 = qw/A D C H E K L H N J P Q R S T/;
# Equal: ^ ^ ^ ^ ^
# Index: 0 2 4 7 9
for my $i ( 0 .. ( $#arr1 <= $#arr2 ? $#arr1 : $#arr2 ) ) {
push #results, $i if $arr1[$i] eq $arr2[$i];
}
print Dumper \#results;
Output:
$VAR1 = [
0,
2,
4,
7,
9
];
Hope this helps!

Perl: correctly print array of arrays (dereference)

Hey fellow perl monks,
I'm still wrapping my head around how to correctly dereference. (I read the similar posts prior to posting, but unfortunately am still a bit cloudy on the concept.)
I have the following array, which internally is composed of two arrays. (BTW, I am using strict and warning pragmas.)
use strict; use warnings;
my #a1; my #a2;
where:
#a1 = ( "1MB", "2MB", ... )
and..
#a2 = ( "/home", "/home/debug", ... )
Both #a1 & #a2 are arrays which contain 51 rows. So, I populate these into my 2nd array.
my #b;
push (#b, [ #a1, #a2 ]);
However, when I try to print the results of #b:
sub newl { print "\n"; print "\n"; }
my $an1; my #an1;
$an1 = $#a1;
#an1 = ( 0, 1..$an1 );
for my $i (#an1) { print #b[$i]; &newl; }
I see references to the arrays:
ARRAY(0x81c0a10)
.
ARRAY(0x81c0a50)
.
.
.
How do I properly print this array? I know I need to dereference the array, I'm not sure how to go about doing this. I tried populating my array as such:
push (#b, [ \#a1, \#a2 ]);
Which produces the same results. I also tried:
for my $i (#an1) { print #{$b[$i]}; &newl; }
Which unfortunately errors due to having 0 as an array reference?
Can't use string ("0") as an ARRAY ref while "strict refs" in use at p_disk_ex6.pl line 42.
Any suggestions are greatly appreciated!
A short example program, which might help you:
use strict;
use warnings;
my #a1 = qw(1MB 2MB 10MB 7MB);
my #a2 = qw(/foo /bar /flub /blub);
my #b = (\#a1, \#a2);
# equivalent long version:
# my #b = ();
# $b[0] = \#a1;
# $b[1] = \#a2;
for (my $i = 0; $i <= $#a2; $i++) {
print "a1[$i]: $b[0][$i]\n";
print "a2[$i]: $b[1][$i]\n";
print "\n";
}
In your example you were pushin an anoymous arrayref [] into #b. Therefore $b[0] contained the arrayref.
my #b;
push (#b, [ \#a1, \#a2 ]);
# this corresponds to:
# $b[0][0] = \#a1;
# $b[0][1] = \#a2;
In the example where you wrote [#a1, #a2] you were creating an array_ref which contained the joined arrays #a1 and #a2 (first all elements of #a1, and then all elements of #a2):
my #b;
push(#b , [#a1, #a2]);
# $b[0] = ['1MB' , '2MB', '10Mb', '7MB', '/foo', '/bar', '/flub', '/blub']
Even Simply this also works
use strict;
use warnings;
my #a1 = qw(1MB 2MB 10MB 7MB);
my #a2 = qw(/foo /bar /flub /blub);
my #b = (#a1, #a2);
print "#b";
If you want a general solution that doesn't assume how many elements there are in each of the sub-arrays, and which also allows arbitrary levels of nesting, you're better off using packages that someone else has already written for displaying recursive data structures. A particularly prevalent one is YAML, which you can install if you don't already have it by running cpan:
$ cpan
Terminal does not support AddHistory.
cpan shell -- CPAN exploration and modules installation (v1.9800)
Enter 'h' for help.
cpan[1]> install YAML
Then you can display arbitrary data structures easily. To demonstrate with a simple example:
use YAML;
my #a1 = qw(1MB 2MB 10MB 7MB);
my #a2 = qw(/foo /bar /flub /blub);
my #b = (\#a1, \#a2);
print Dump(\#b);
results in the output
---
-
- 1MB
- 2MB
- 10MB
- 7MB
-
- /foo
- /bar
- /flub
- /blub
For a slightly more complicated example
my #b = (\#a1, \#a2,
{ a => 0, b => 1 } );
gives
---
-
- 1MB
- 2MB
- 10MB
- 7MB
-
- /foo
- /bar
- /flub
- /blub
- a: 0
b: 1
To read this, the three "-" characters in column 1 indicate an array with three elements.
The first two elements have four sub elements each (the lines with "-" in column 3). The
third outer element is a hash reference, since it is made up of "key: value" pairs.
A nice feature about YAML is that you can use it to dump any recursive data structure into a file, except those with subroutine references, and then read it back later using Load.
If you really have to roll your own display routine, that is certainly possible, but you'll have a much easier time if you write it recursively. You can check whether your argument is an array reference or a hash reference (or a scalar reference) by using ref:
my #a1 = qw(1MB 2MB 10MB 7MB);
my #a2 = qw(/foo /bar /flub /blub);
my #b = (\#a1, \#a2,
{ a => 0, b => 1 } );
print_recursive(\#b);
print "\n";
sub print_recursive {
my ($obj) = #_;
if (ref($obj) eq 'ARRAY') {
print "[ ";
for (my $i=0; $i < #$obj; $i++) {
print_recursive($obj->[$i]);
print ", " if $i < $#$obj;
}
print " ]";
}
elsif (ref($obj) eq 'HASH') {
print "{ ";
my #keys = sort keys %$obj;
for (my $i=0; $i < #keys; $i++) {
print "$keys[$i] => ";
print_recursive($obj->{$keys[$i]});
print ", " if $i < $#keys;
}
print " }";
}
else {
print $obj;
}
}
which produces the output
[ [ 1MB, 2MB, 10MB, 7MB ], [ /foo, /bar, /flub, /blub ], { a => 0, b => 1 } ]
I have not written my example code to worry about pretty-printing, nor does it
handle scalar, subroutine, or blessed object references, but it should give you the idea of how you can write a fairly general recursive data structure dumper.

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