Perl: assign array to hash values? - arrays

I was trying to initiate a hash that have 7 NAs as values.
This is what I have:
values %hash = ("NA") x 7;
print join("\t",values %hash),"\n";
I got this error:
Can't modify values in scalar assignment at line 22, near "7;"
Apparently, I can't do this with values of hash although I can assign array to hash keys
keys %hash = ["a","b","c","d","e","f","g"];
Why is it working for keys but not values for hash assignment?

From perldoc -f keys:
Used as an lvalue, "keys" allows you to increase the number of hash buckets allocated for the given hash. This can gain you a measure of efficiency if you know the hash is going to get big.
I.e. this method is not useful to set the keys, only to allocate space for a certain number of entries. When using a reference as the number, the result will probably be something ridiculously large that will eat most of your memory – not exactly recommended.
To initialize a hash with some values, you have to specify the required keys. But you can use a slice as an lvalue:
my %hash;
#hash{1..7} = ("NA") x 7;
Note: an lvalue is a value that can be used on the left side of an assignment.

A hash has two parts to it, keys and values. e.g.:
my %hash = ( a => 1, b => 2, c => 3 );
Here, the keys are 'a', 'b' and 'c'. The values are 1, 2 and 3.
If you look at what keys and values do, they (unsurprisingly) return the keys and values of the hash respectively.
They are not meant to set the values or keys of a hash, merely to retrieve.

Related

Compare hash values with corresponding array values in Ruby

I've scripted my way into a corner where I now need to compare the values of a hash to the corresponding element in an array.
I have two "lists" of the same values, sorted in different ways, and they should be identical. The interesting part is when they don't, so I need to identify those cases. So basically I need to check whether the first value of the first key-value pair in the hash is identical to the first element of the array, and likewise the second value checked towards the second element and so on for the entire set of values in the hash.
I'm sort of new to Ruby scripting, but though this should be easy enough, but alas....
Sounds like all you need is something simple like:
hash.keys == array
The keys should come out in the same order as they are in the Hash so this is comparing the first key of the hash with the first element of the array, the second key with the second array element, ...
You could also transliterate what you're saying into Ruby like this:
hash.each_with_index.all? { |(k, _), i| k == array[i] }
Or you could say:
hash.zip(array).all? { |(k, _), e| k == e }
The zip version is pretty much the each_with_index version with the array indexing essentially folded into the zip.
Technically speaking, hash is not guaranteed to ordered, so your assumption of matching the value at 'each' index of hash may not always hold true. However, for sake of answering your question, assuming h is your hash, and a is your array:
list_matches = true
h.values.each_with_index {|v, i| list_matches = list_matches && a[i] == v}
if list_matches is not equal to true than that is where the the items in the two collections don't match.

Perl - Assign an array to another variable

I'm trying to assign an array to a value in my hash as follows:
$authors->[$x]->{'books'} = #books;
$authors is an array of hashes that contains his/her first name, last name, date of birth, etc. And now I'm creating a books key where I want to assign an array of books. However, when I try to print it afterwords, it's just printing the size of the array, as if I'm doing $value = scalar #books.
What am I doing wrong?
Array elements and hash values are scalars, so when you are nesting arrays and hashes, you must use references. Just as $authors->[$x] is not a hash but a reference to a hash, you must set $authors->[$x]->{'books'} to a reference to the array.
$authors->[$x]->{'books'} = \#books; # reference the original array
$authors->[$x]->{'books'} = [#books]; # reference a copy
You would then access elements of the array using something like
$authors->[$x]->{'books'}->[0]
which can be abbreviated
$authors->[$x]{books}[0]
or access the whole array as
#{$authors->[$x]{books}}
Your original attempt
$authors->[$x]->{'books'} = #books;
is exactly equivalent to
$authors->[$x]->{'books'} = scalar #books;
because the left operand of the = operator is a hash value, which is a scalar, so the right operand is evaluated in scalar context to provide something that can be assigned there.
P.S.
On rereading this answer I realized it may be confusing to say "a hash value is a scalar" because of the possible interpretation of "hash value" as meaning "the value of a hash variable" i.e. "the whole hash". What I mean when I write "hash value" is an item that is stored in a hash as a value (as opposed to a key).
While the first awnser is absolutely right,
as an alternative, you could also fo this:
push #{$authors->[$x]->{'books'}}, #books;
Then $authors->[$x]->{'books'} will be an Array that contains a copy of all
the elements from #books.
This might be more "foolproof" then working with references, as
mentioned above.

How to create two dimensional array with 2 arrays

How to create multiple dimentional array with 2 arrays?
#param=("param1","param2","param3");
#value=("value1_1, value1_2, value1_3", "value2_1, value2_2, value2_3","value3_1, value3_2, value3_3");
Output:
#out=(["param1"]["value1_1", "value1_2", "value1_3"], ["param2"]["value2_1", "value2_2", "value2_3"], ["param3"]["value3_1", "value3_2", "value3_3"])
I've tried this way:
$j=0;
foreach $i(#param1){
push #{$out[$i]}, split(", ", $value[$j]);
$j++;}
It's not exactly clear to me what data structure you want to create.
However, I assume you are trying to create a hash of arrays (a hash table is also known as dictionary or associative array), not an array.
The difference in Perl is that an array always uses integers as keys, whereas a hash always uses strings.
The resulting data structure would then look like
%out = (
'param1' => ['value1_1', 'value1_2', 'value1_3'],
'param2' => ['value2_1', 'value2_2', 'value2_3'],
'param3' => ['value3_1', 'value3_2', 'value3_3'],
);
We can create this data structure like so:
my %out;
for my $i (0 .. $#param) {
$out{$param[$i]} = [split /,\s*/, $value[$i]];
}
Note that $#foo is the highest index in the #foo array. Therefore, 0 .. $#foo would be the range of all indices in #foo. Also note that entries in hashes are accessed with a curly brace subscript $hash{$key}, unlike arrays which use square brackets $array[$index].
You can access multiple elements in a hash or array at the same time by using a slice – #foo{'a', 'b', 'c'} is equivalent to ($foo{a}, $foo{b}, $foo{c}). We can also transform a list of elements by using the map {BLOCK} LIST function. Together, this allows for the following solution:
my %out;
#out{#param} = map { [split /,\s*/, $_] } #value;
Inside the map block, the $_ variable is set to each item in the input list in turn.
To learn more about complex data structures, read (in this order):
perldoc perreftut
perldoc perllol
perldoc perldsc
You can also read the documentation for the map function and for the foreach loop.

How do I update values in an array of hashes, which is in a hash of a hash in perl?

Seems very confusing I know. I'll try to "draw" this data structure:
hash-> key->( (key)->[(key,value),(key,value),(key,value),...],
(key,value))
So there is the first key, whose value is enclosed in the parenthesis. The value for the first key of the hash is two keys, one (the right one) being another simple key, value pair. The other (the left one) key's value is an array of hashes. I am able to update the "right" key, value pair with the following line of code:
$hash{$parts[1]}{"PAGES"} += $parts[2];
Where $parts[1] and $parts[2] are just elements from an array. I am +=ing a number to the "right" key, value pair from my hash. What I need to do now is update the "left" key,value pair - the array of hashes within a hash of hashes. Here is how I initialize the array for both key, value pairs in the hash of hashes:
$hash{$printer}{"PAGES"} = 0;
$hash{$printer}{"USERS"} = [#tmp];
Here is one of my many attempts to access and update the values in the array of hashes:
$hash{$parts[1]}{"USERS"}[$parts[0]] += $parts[2];
I just can't figure out the correct syntax for this. If anyone could help me I'd appreciate it. Thanks.
Edit: I guess a more pointed question is: How do I get a hash key from an array of hashes (keeping in mind that the array is in a hash of hashes)?
Edit: Added this to the code:
#Go through each user to check to see which user did a print job and add the page
#count to their total
#$parts[0] is the user name, $parts[1] is the printer name, $parts[2] is the page
#count for the current print job
for(my $i=0;$i<$arr_size;$i++)
{
my $inner = $hash{$parts[1]}{"USERS"}[$i];
my #hash_arr = keys %$inner;
my $key = $hash_arr[0];
#problem line - need to compare the actual key with $parts[0]
#(not the key's value which is a number)
if($hash{$parts[1]}{"USERS"}[$i]{$key} eq $parts[0])
{
$hash{$parts[1]}{"USERS"}[$i]{$parts[0]} += $parts[2];
}
}
Edit: Whoops hehe this is what I needed. It still isn't quite there but this is kind of what I am looking for:
if($key eq $parts[0])
{
$hash{$parts[1]}{"USERS"}[$i]{$parts[0]} += $parts[2];
}
Edited to respond to the edited question: How do I get a hash key from an array of hashes (keeping in mind that the array is in a hash of hashes).
use strict;
use warnings;
my %h;
$h{printer}{PAGES} = 0;
$h{printer}{USERS} = [
{a => 1, b => 2},
{c => 3, d => 4},
{e => 5, f => 6},
];
# Access a particular element.
$h{printer}{USERS}[0]{a} += 100;
# Access one of the inner hashes.
my $inner = $h{printer}{USERS}[1];
$inner->{$_} += 1000 for keys %$inner;
# Ditto, but without the convenience variable.
$h{printer}{USERS}[2]{$_} += 9000 for keys %{ $h{printer}{USERS}[2] };
use Data::Dumper qw(Dumper);
print Dumper \%h;
Output:
$VAR1 = {
'printer' => {
'PAGES' => 0,
'USERS' => [
{
'a' => 101,
'b' => 2
},
{
'c' => 1003,
'd' => 1004
},
{
'e' => 9005,
'f' => 9006
}
]
}
};
I have trouble understanding the structure from your description.
My advice is to avoid such structures like pestilentious devils from bad neighbourhoods.
One reason people end up with such maintenance nightmares is they're using XML::Simple.
Whatever the reason in your example, do yourself a favour and prevent that horrible data structure from ever getting created. There are always alternatives. If you describe the problem, people will be able to suggest some.
The way you've described the structure is somewhat impenetrable to me, but accessing array references embedded within other structures is done thusly, using a simpler example structure:
my $ref = {k => [1, 3, 5, 6, 9]};
Below, the value of 6 is incremented to 7:
$ref->{k}->[3] += 1;
See perlref for more details on the -> operator, but briefly, the expression to the left of the arrow can be anything that returns a reference. In some cases, the -> operator is optional but it's best left in for clarity.
I still haven't decoded your structure. But, I'll make two comments:
Use the -> syntactic sugar. For example, $hash->{key}->[2]->{key} is a bit clearer than trying to parse things out without the syntactic sugar: ${{hash}{key}}[1]{key} (if that's even correct...)
Look into using Object Oriented Perl for this structure. It's not as scary as it sounds. In Perl, objects are subroutines that handle the dirty work for you. Take a look at perldoc perlboot. It's what I used to understand how object oriented Perl works. You don't even have to create a full separate module. The object definitions can live in the same Perl script.
Using Object Oriented Perl keeps the mess outside your main program and makes it easier to maintain your program. Plus, if you have to modify your structure, you don't have to search your entire code to find all the places to change. The syntactic sugar makes it easier to see where you're going with your structure.
Compare this monstrosity with this object oriented monstrosity. Both programs do the same thing. I wrote the first one long ago, and found it so hard to maintain that I rewrote it from scratch in an object oriented style. (They're pre-commit hooks for Subversion in case anyone is wondering).

In Perl, how do I create a hash whose keys come from a given array?

Let's say I have an array, and I know I'm going to be doing a lot of "Does the array contain X?" checks. The efficient way to do this is to turn that array into a hash, where the keys are the array's elements, and then you can just say if($hash{X}) { ... }
Is there an easy way to do this array-to-hash conversion? Ideally, it should be versatile enough to take an anonymous array and return an anonymous hash.
%hash = map { $_ => 1 } #array;
It's not as short as the "#hash{#array} = ..." solutions, but those ones require the hash and array to already be defined somewhere else, whereas this one can take an anonymous array and return an anonymous hash.
What this does is take each element in the array and pair it up with a "1". When this list of (key, 1, key, 1, key 1) pairs get assigned to a hash, the odd-numbered ones become the hash's keys, and the even-numbered ones become the respective values.
#hash{#array} = (1) x #array;
It's a hash slice, a list of values from the hash, so it gets the list-y # in front.
From the docs:
If you're confused about why you use
an '#' there on a hash slice instead
of a '%', think of it like this. The
type of bracket (square or curly)
governs whether it's an array or a
hash being looked at. On the other
hand, the leading symbol ('$' or '#')
on the array or hash indicates whether
you are getting back a singular value
(a scalar) or a plural one (a list).
#hash{#keys} = undef;
The syntax here where you are referring to the hash with an # is a hash slice. We're basically saying $hash{$keys[0]} AND $hash{$keys[1]} AND $hash{$keys[2]} ... is a list on the left hand side of the =, an lvalue, and we're assigning to that list, which actually goes into the hash and sets the values for all the named keys. In this case, I only specified one value, so that value goes into $hash{$keys[0]}, and the other hash entries all auto-vivify (come to life) with undefined values. [My original suggestion here was set the expression = 1, which would've set that one key to 1 and the others to undef. I changed it for consistency, but as we'll see below, the exact values do not matter.]
When you realize that the lvalue, the expression on the left hand side of the =, is a list built out of the hash, then it'll start to make some sense why we're using that #. [Except I think this will change in Perl 6.]
The idea here is that you are using the hash as a set. What matters is not the value I am assigning; it's just the existence of the keys. So what you want to do is not something like:
if ($hash{$key} == 1) # then key is in the hash
instead:
if (exists $hash{$key}) # then key is in the set
It's actually more efficient to just run an exists check than to bother with the value in the hash, although to me the important thing here is just the concept that you are representing a set just with the keys of the hash. Also, somebody pointed out that by using undef as the value here, we will consume less storage space than we would assigning a value. (And also generate less confusion, as the value does not matter, and my solution would assign a value only to the first element in the hash and leave the others undef, and some other solutions are turning cartwheels to build an array of values to go into the hash; completely wasted effort).
Note that if typing if ( exists $hash{ key } ) isn’t too much work for you (which I prefer to use since the matter of interest is really the presence of a key rather than the truthiness of its value), then you can use the short and sweet
#hash{#key} = ();
I always thought that
foreach my $item (#array) { $hash{$item} = 1 }
was at least nice and readable / maintainable.
There is a presupposition here, that the most efficient way to do a lot of "Does the array contain X?" checks is to convert the array to a hash. Efficiency depends on the scarce resource, often time but sometimes space and sometimes programmer effort. You are at least doubling the memory consumed by keeping a list and a hash of the list around simultaneously. Plus you're writing more original code that you'll need to test, document, etc.
As an alternative, look at the List::MoreUtils module, specifically the functions any(), none(), true() and false(). They all take a block as the conditional and a list as the argument, similar to map() and grep():
print "At least one value undefined" if any { !defined($_) } #list;
I ran a quick test, loading in half of /usr/share/dict/words to an array (25000 words), then looking for eleven words selected from across the whole dictionary (every 5000th word) in the array, using both the array-to-hash method and the any() function from List::MoreUtils.
On Perl 5.8.8 built from source, the array-to-hash method runs almost 1100x faster than the any() method (1300x faster under Ubuntu 6.06's packaged Perl 5.8.7.)
That's not the full story however - the array-to-hash conversion takes about 0.04 seconds which in this case kills the time efficiency of array-to-hash method to 1.5x-2x faster than the any() method. Still good, but not nearly as stellar.
My gut feeling is that the array-to-hash method is going to beat any() in most cases, but I'd feel a whole lot better if I had some more solid metrics (lots of test cases, decent statistical analyses, maybe some big-O algorithmic analysis of each method, etc.) Depending on your needs, List::MoreUtils may be a better solution; it's certainly more flexible and requires less coding. Remember, premature optimization is a sin... :)
In perl 5.10, there's the close-to-magic ~~ operator:
sub invite_in {
my $vampires = [ qw(Angel Darla Spike Drusilla) ];
return ($_[0] ~~ $vampires) ? 0 : 1 ;
}
See here: http://dev.perl.org/perl5/news/2007/perl-5.10.0.html
Also worth noting for completeness, my usual method for doing this with 2 same-length arrays #keys and #vals which you would prefer were a hash...
my %hash = map { $keys[$_] => $vals[$_] } (0..#keys-1);
Raldi's solution can be tightened up to this (the '=>' from the original is not necessary):
my %hash = map { $_,1 } #array;
This technique can also be used for turning text lists into hashes:
my %hash = map { $_,1 } split(",",$line)
Additionally if you have a line of values like this: "foo=1,bar=2,baz=3" you can do this:
my %hash = map { split("=",$_) } split(",",$line);
[EDIT to include]
Another solution offered (which takes two lines) is:
my %hash;
#The values in %hash can only be accessed by doing exists($hash{$key})
#The assignment only works with '= undef;' and will not work properly with '= 1;'
#if you do '= 1;' only the hash key of $array[0] will be set to 1;
#hash{#array} = undef;
You could also use Perl6::Junction.
use Perl6::Junction qw'any';
my #arr = ( 1, 2, 3 );
if( any(#arr) == 1 ){ ... }
If you do a lot of set theoretic operations - you can also use Set::Scalar or similar module. Then $s = Set::Scalar->new( #array ) will build the Set for you - and you can query it with: $s->contains($m).
You can place the code into a subroutine, if you don't want pollute your namespace.
my $hash_ref =
sub{
my %hash;
#hash{ #{[ qw'one two three' ]} } = undef;
return \%hash;
}->();
Or even better:
sub keylist(#){
my %hash;
#hash{#_} = undef;
return \%hash;
}
my $hash_ref = keylist qw'one two three';
# or
my #key_list = qw'one two three';
my $hash_ref = keylist #key_list;
If you really wanted to pass an array reference:
sub keylist(\#){
my %hash;
#hash{ #{$_[0]} } = undef if #_;
return \%hash;
}
my #key_list = qw'one two three';
my $hash_ref = keylist #key_list;
#!/usr/bin/perl -w
use strict;
use Data::Dumper;
my #a = qw(5 8 2 5 4 8 9);
my #b = qw(7 6 5 4 3 2 1);
my $h = {};
#{$h}{#a} = #b;
print Dumper($h);
gives (note repeated keys get the value at the greatest position in the array - ie 8->2 and not 6)
$VAR1 = {
'8' => '2',
'4' => '3',
'9' => '1',
'2' => '5',
'5' => '4'
};
You might also want to check out Tie::IxHash, which implements ordered associative arrays. That would allow you to do both types of lookups (hash and index) on one copy of your data.

Resources