Looping through array that's inside hash - perl - arrays

Am I doing this right?
my $variable = "hello,world";
my #somearray = split(',',$variable);
my %hash = ( 'somevalue' => #somearray);
foreach my $key ( keys %hash ) {
print $key;
foreach my $value ( #{$hash{$key}} ) {
print $value; #the value is not being read/printed
}
}
I don't know if I'm accessing the array that is stored in the hash for the particular value

You've been bitten by perl's flattening nature of lists. You see, when you do:
my %hash = ('somekey' => #somearray), perl converts that into a list form of hash assignment. So, what perl actually sees is:
my %hash = ('somekey' => 'hello', 'world' => ''); # I have put '' for simplicity, though it might well be `undef`
So, the next time you look up by 'somekey', you end up getting the string 'hello' and not the array "['hello', 'world']"
To fix this, you can use references. perlref can help you there for more information.
my %hash = ('somekey' => \#somearray);
# $hash{'somekey'} is an array reference now.
# So you use the pointy lookup syntax.
print $hash{'somekey'}->[0];
Another useful tool in visualising data structures is using the module Data::Dumper. It's available in the perl core distribution. Using it is as simple as doing:
use Data::Dumper;
print Dumper \%hash; # remember to pass in a reference to the actual datastructure, not the data structure itself.
Have fun!

This is wrong:
my %hash = ( 'somevalue' => #somearray);
The array is "flattened" to a list, so the line is equivalent to
my %hash = qw( somevalue hello world );
You need an array reference to create the inner array:
my %hash = ( 'somevalue' => \#somearray);

So you wanted to create a hash of array data structure. Something like this will also work.
my $variable = "hello,world";
my #somearray = split(',',$variable);
my %hash;
#my %hash = ( 'somevalue' => #somearray);
push (#{$hash{'somevalue'}},#somearray); #Storing it in a hash of array
foreach my $key ( keys %hash ) {
print $key;
foreach my $value ( #{$hash{$key}} ) {
print $value; #the value is not being read/printed
}
}

Related

Separating CSV file into key and array

I am new to perl, and I am trying to separate a csv file (has 10 comma-separated items per line) into a key (first item) and an array (9 items) to put in a hash. Eventually, I want to use an if function to match another variable to the key in the hash and print out the elements in the array.
Here's the code I have, which doesn't work right.
use strict;
use warnings;
my %hash;
my $in2 = "metadata1.csv";
open IN2, "<$in2" or die "Cannot open the file: $!";
while (my $line = <IN2>) {
my ($key, #value) = split (/,/, $line, 2);
%hash = (
$key => #value
);
}
foreach my $key (keys %hash)
{
print "The key is $key and the array is $hash{$key}\n";
}
Thank you for any help!
Don't use 2 as the third argument to split: it will split the line to only two elements, so there'll be just one #value.
Also, by doing %hash =, you're overwriting the hash in each iteration of the loop. Just add a new key/value pair:
$hash{$key} = \#value;
Note the \ sign: you can't store an array directly as a hash value, you have to store a reference to it. When printing the value, you have to dereference it back:
#! /usr/bin/perl
use warnings;
use strict;
my %hash;
while (<DATA>) {
my ($key, #values) = split /,/;
$hash{$key} = \#values;
}
for my $key (keys %hash) {
print "$key => #{ $hash{$key} }";
}
__DATA__
id0,1,2,a
id1,3,4,b
id2,5,6,c
If your CSV file contains quoted or escaped commas, you should use Text::CSV.
First of all hash can have only one unique key, so when you have lines like these in your CSV file:
key1,val11,val12,val13,val14,val15,val16,val17,val18,val19
key1,val21,val22,val23,val24,val25,val26,val27,val28,val29
after adding both key/value pairs with 'key1' key to the hash, you'll get just one pair saved in the hash, the one that were added to the hash later.
So to keep all records, the result you probably need array of hashes structure, where value of each hash is an array reference, like this:
#result = (
{ 'key1' => ['val11','val12','val13','val14','val15','val16','val17','val18','val19'] },
{ 'key1' => ['val21','val22','val23','val24','val25','val26','val27','val28','val29'] },
{ 'and' => ['so on'] },
);
In order to achieve that your code should become like this:
use strict;
use warnings;
my #AoH; # array of hashes containing data from CSV
my $in2 = "metadata1.csv";
open IN2, "<$in2" or die "Cannot open the file: $!";
while (my $line = <IN2>) {
my #string_bits = split (/,/, $line);
my $key = $string_bits[0]; # first element - key
my $value = [ #string_bits[1 .. $#string_bits] ]; # rest get into arr ref
push #AoH, {$key => $value}; # array of hashes structure
}
foreach my $hash_ref (#AoH)
{
my $key = (keys %$hash_ref)[0]; # get hash key
my $value = join ', ', #{ $hash_ref->{$key} }; # join array into str
print "The key is '$key' and the array is '$value'\n";
}

What is the equivalent hash function of "push' arrays in perl?

I am a beginner programmer who is writing a program using perl that will eventually allow me to search for name, and have it tell me the early steps. So far (with the help of many nice people on here) I have this code for array format.
#!/usr/local/bin/perl
use strict;
use warnings;
my #M_array;
my #F_array;
open (my $input, "<", 'ssbn1898.txt');
while ( <$input> ) {
chomp;
my ( $name, $id ) = split ( /,/ );
if ( $id eq "M" ) {
push ( #M_array, $name );
}
else {
push ( #F_array, $name );
}
}
close ( $input );
print 'M: ' . join("\t", #M_array) . "\n";
print 'F: ' . join("\t", #F_array) . "\n";
And I attempted to use the same code to put it into a hash.
#!/usr/local/bin/perl
use strict;
use warnings;
my %M_hash;
my %F_hash;
open (my $input, "<", 'ssbn1898.txt');
while ( <$input> ) {
chomp;
my ( $name, $id ) = split ( /,/ );
if ( $id eq "M" ) {
push ( %M_hash, $name );
}
else {
push ( %F_hash, $name );
}
}
close ( $input );
print 'M: ' . join("\t", %M_hash) . "\n";
print 'F: ' . join("\t", %F_hash) . "\n";
But I get an error on the "push" function. I would assume then that this function is just for arrays. Is there an equivalent function for a hash? And what does the "push" function really do?
Thank you all for your help
http://www.ourbabynamer.com/popular-baby-names.php?year=1898&top=1000&country=US&order=0&page=1
This is the data I am working with
Push adds an element to the back of an array.
#a = ( 1, 2, 3 );
push #a, 4; # #a is now ( 1, 2, 3, 4 )
Insert adds an element to a hash.
%h = ( foo => 1, bar => 2 );
$h{ qux } = 3; # %h is now ( foo => 1, bar => 2, qux => 3 );
Take a look at perldoc perlintro
http://perldoc.perl.org/perlintro.html
push adds an element at the end of an array. Hashes don't have an end, so there's no equivalent for hashes. You need to specify the key of the element you wish to set.
$hash{$key} = $val;
I don't know why you changed the array into a hash. It makes no sense to use a hash here. The solution is to revert your change.
In a comment, you say that you "must use this data as an array and a hash". I'm not really sure what you mean, but one possible interpretation is that your teacher wants you do use both hashes and arrays in your code.
One way to do that would be to store your data in an hash of arrays. It would look something like this.
#!/usr/local/bin/perl
use strict;
use warnings;
use 5.010;
my %data;
while ( <> ) { # Use <> to read from STDIN. Makes life easier :-)
chomp;
my ( $name, $gender ) = split /,/;
push #{$data{$gender}}, $name;
}
foreach (keys %data) {
say "$_: " . join("\t", #{$data{$_}_);
}
But that would involve using array references, which sounds like it might be a little advanced for your current course.
One advantage of this approach is that it will continue to work (without code changes) should you wish to add new genders to your input data!
push works on arrays, read more at: http://perldoc.perl.org/functions/push.html
Hashes are different than arrays, they are like associate arrays. They are un-ordered group of key-value pairs. To add some key to hash you do something like below:
my %hash = (key1 => "value1", key2 => "value2");
$hash{"key3"} = "value3";
Note that keys must be unique.
See also:
Hashes in Perl - Gabor's blog
keys - perldoc
values - perldoc
There is no specific function to push the element to the hash, you just need assign the value to hash key as below -
$hash{key} = 'value';
Though it will not sure that this item will be appended as the last element as the hash stores it keys in random fashion,

Perl: Load file into hash

I'm struggling to understand logic behind hashes in Perl. Task is to load file in to hash and assign values to keys which are created using this file.
File contains alphabet with each letter on its own line:
a
b
c
d
e
and etc,.
When using array instead of hash, logic is simple: load file into array and then print each element with corresponding number using some counter ($counter++).
But now my question is, how can I read file into my hash, assign automatically generated values and sort it in that way where output is printed like this:
a:1
b:2
c:3
I've tried to first create array and then link it to hash using
%hash = #array
but it makes my hash non-sortable.
There are a number of ways to approach this. The most direct would be to load the data into the hash as you read through the file.
my %hash;
while(<>)
{
chomp;
$hash{$_} = $.; #Use the line number as your autogenerated counter.
}
You can also perform simliar logic if you already have a populated array.
for (0..$#array)
{
$hash{$array[$_]} = $_;
}
Although, if you are in that situation, map is the perlier way of doing things.
%hash = map { $array[$_] => $_ } #array;
Think of a hash as a set of pairs (key, value), where the keys must be unique. You want to read the file one line at a time, and add a pair to the hash:
$record = <$file_handle>;
$hash{$record} = $counter++;
Of course, you could read the entire file into an array at once and then assign to your hash. But the solution is not:
#records = <$file_handle>;
%hash = #records;
... as you found out. If you think in terms of (key, value) pairs, you will see that the above is equivalent to:
$hash{a} = 'b';
$hash{c} = 'd';
$hash{e} = 'f';
...
and so on. You still are going to need a loop, either an explicit one like this:
foreach my $rec (#records)
{
$hash{$rec} = $counter++;
}
or an implicit one like one of these:
%hash = map {$_ => $counter++} #records;
# or:
$hash{$_} = $counter++ for #records;
This code should generate the proper output, where my-text-file is the path to your data file:
my %hash;
my $counter = 0;
open(FILE, "my-text-file");
while (<FILE>) {
chomp;
$counter++;
$hash{$_} = $counter;
}
# Now to sort
foreach $key (sort(keys(%hash))) {
print $key . ":" . $hash{$key} . "\n";
}
I assume you want to sort the hash aplhabetically. keys(%hash) and values(%hash) return the keys and values of %hash as an array, respectively. Run the program on this file:
f
a
b
d
e
c
And we get:
a:2
b:3
c:6
d:4
e:5
f:1
I hope this helps you.

How do you map an array [key1,val1] to a hash { key1 => val1} in perl?

The problem is I have an array that has the key value pairs as elements in an array and I have to split them up somehow into key => value pairs in a hash.
my first attempt at this works, but I think its pretty messy - I have to get every other element of the arrays, and then filter through to find the accepted keys to create a hash with
my $HASH;
my $ARRAY = [ key1, val1, key2, val2, __key3__, val3, __key4__, val4 ];
my #keys = map{ $ARRAY[ $_*2 ] } 0 .. int($#ARRAY/2);
my #vals = map{ $ARRAY[ $_*2+1 ] } 0 .. int($#ARRAY/2);
my $i = 0;
#filter for keys that only have __key__ format
for $key (#keys){
if( $key && $key =~ m/^__(.*)__$/i ){
$HASH{$1} = $vals[$i];
}
$i++;
}
# only key3 and key4 should be in $HASH now
I found this sample code which I think is close to what I'm looking for but I cant figure out how to implement something similar over an array rather than iterating over lines of a text file:
$file = 'myfile.txt'
open I, '<', $file
my %hash;
%hash = map { split /\s*,\s*,/,$_,2 } grep (!/^$/,<I>);
print STDERR "[ITEM] $_ => $hash{$_}\n" for keys %hash;
Can any of you perl gurus out there help me understand the best way to do this? Even if I could somehow join all the elements into a string then split over every second white space token -- that might work too, but for now Im stuck!
This is very easy:
use strict; use warnings;
use YAML;
my $ARRAY = [qw(key1 val1 key2 val2 __key3__ val3 __key4__ val4)];
my $HASH = { #$ARRAY };
print Dump $HASH;
Output:
C:\Temp>
---
__key3__: val3
__key4__: val4
key1: val1
key2: val2
my $ARRAY = [ qw(key1 val1 key2 val2 key3 val3 key4 val4) ];
my $HASH = { #$ARRAY };
In the sample code you found, the <I> portion reads in the entire file and returns a list to grep. grep processes the list and then passes it to map. Map then creates its own list and this list is assigned to the hash.
When you assign a list to a hash, the list is assumed to be an even list of key/value pairs.
It does not matter where this list comes from, it could be the output of a map, grep, or split command. It could be right from a file, it could be stored in an array.
Your line:
my $HASH = ();
Does not do anything useful. Writing my $HASH; is exactly the same.
At this point, $HASH is undefined. When you have an undefined value and you dereference it as a hash, %$HASH, the undefined value will become a hash.
You can make this explicit by writing:
my $HASH = {}; # note the curly braces and not parens
If you have a list of key value pairs in an array:
%$HASH = #array;
If you have a list of keys and a list of values:
#$HASH{#keys} = #values;
Per your question, here is one simple way of creating your hash from the array while filtering the values:
my $HASH = {};
my $ARRAY = [ qw(key1 val1 key2 val2 __key3__ val3 __key4__ val4) ];
{my #list = #$ARRAY; # make a copy since splice eats the list
while (my ($k, $v) = splice #list, 0, 2) {
if ($k =~ /^__(.+)__$/) {
$$HASH{$1} = $v
}
}
}
use Data::Dumper;
print Dumper($HASH);
which prints:
$VAR1 = {
'key4' => 'val4',
'key3' => 'val3'
};
If you want to do that all in one line, you could use the function mapn from my module List::Gen which is a function like map but that allows you to move over the list with any step size you want, not just one item at a time.
use List::Gen 'mapn';
%$HASH = mapn {/^__(.+)__$/ ? ($1, $_[1]) : ()} 2 => #$ARRAY;
I didnt know you could dump the array and hash would figure it out.
There's nothing magical about values that come from an array. There's nothing for the hash to figure out.
If you assign a list to a hash, it will clear the hash, then treat the list as a list of key-value pairs from which to initialise the hash. So
%hash = ( foo => 1, bar => 2 );
is equivalent to
my #anon = ( foo => 1, bar => 2 );
%hash = ();
while (#anon) {
my $key = shift(#anon);
my $val = shift(#anon);
$hash{$key} = $val;
}
A list is a list. It doesn't matter if it was produced using the list/comma op
x => "foo", y => "bar"
using qw()
qw( x foo y bar )
or using an array
#array
So that means the following are all the same.
%hash = ( x => "foo", y => "bar" );
%hash = qw( x foo y bar );
%hash = #array; # Assuming #array contains the correct values.
And so are
$hash = { x => "foo", y => "bar" };
$hash = {qw( x foo y bar )};
$hash = { #array }; # Assuming #array contains the correct values.
Since your particular case has already been answered, I thought I would answer the case of what I took your question to ask. I expected to see an array of pairs like [ key => $value ], and that you wanted to put into either an array of hashes or a hash:
That answer goes like so:
my %hash = map { #$_ } [[ key1 => $value1 ], [ key2 => $value2 ], ... ];
my #ha = map {; { #$_ } } [[ key1 => $value1 ], [ key2 => $value2 ], ... ];
my %hash = #$array_ref_of_values;
Only, I take each one and "explode" them, through dereferencing ( #$_ ).

Perl Array of Hashes - Reference each hash within array?

I am trying to create an array of hashes, and I am wondering how to reference each hash within the array?
For eg:
while(<INFILE>)
{
my $row = $_;
chomp $row;
my #cols = split(/\t/,$row);
my $key = $cols[0]."\t".$cols[1];
my #total = (); ## This is my array of hashes - wrong syntax???
for($i=2;$i<#cols;$i++)
{
$total[$c++]{$key} += $cols[$i];
}
}
close INFILE;
foreach (sort keys %total) #sort keys for one of the hashes within the array - wrong syntax???
{
print $_."\t".$total[0]{$_}."\n";
}
Thanks in advance for any help.
You don't need
my #total = ();
This:
my #total;
is sufficient for what you are after. No special syntax needed to declare that your array will contain hashes.
There's probably different ways of doing the foreach part, but this should work:
foreach (sort keys %{$total[$the_index_you_want]}) {
print $_."\t".$total[$the_index_you_want]{$_}."\n";
}
[BTW, declaring my #total; inside that loop is probably not what you want (it would be reset on each line). Move that outside the first loop.]
And use strict; use warnings;, obviously :-)
Here's what I get:
print join( "\t", #$_{ sort keys %$_ } ), "\n" foreach #total;
I'm simply iterating through #total and for each hashref taking a slice in sorted order and joining those values with tabs.
If you didn't need them in sorted order, you could just
print join( "\t", values %$_ ), "\n" foreach #total;
But I also would compress the line processing like so:
my ( $k1, $k2, #cols ) = split /\t/, $row;
my $key = "$k1\t$k2";
$totals[ $_ ]{ $key } += $cols[ $_ ] foreach 0..$#cols;
But with List::MoreUtils, you could also do this:
use List::MoreUtils qw<pairwise>;
my ( $k1, $k2, #cols ) = split /\t/, $row;
my $key = "$k1\t$k2";
pairwise { $a->{ $key } += $b } #totals => #cols;

Resources