Perl: slicing an array of hashes - arrays

The output of the code below is always empty. Not sure what I am doing wrong and would appreciate any help. How do I get to the values of a key in a specific hash in an array of hashes?
use strict;
use warnings;
my %dot1 = ('a'=>1,'b'=>2);
my %dot2 = ('a'=>3,'b'=>4);
my %dot3 = ('a'=>5,'b'=>6);
my %dot4 = ('a'=>7,'b'=>8);
my #array = (%dot1,%dot2,%dot3,%dot4);
my %x = $array[2];
my $y = $x->{'a'};
print "$y \n";

You don't have an array of hashes. You have an array that looks like a hash, where the keys a and b will be there four times each, in relatively random order.
print Dumper \#array;
$VAR1 = [
'a',
1,
'b',
2,
'a',
3,
'b',
4,
'a',
5,
'b',
6,
'a',
7,
'b',
8
];
Afterwards, you are using $x->{a}, which is the syntax to take the key a from the hashref $x, but you only ever declared a hash %a. That in turn breaks, because you give it an odd-sized list of one value.
Instead, add references to the hashes to your array. That way you will get a multi-level data structure instead of a flat list. Then make the x variable a scalar $x.
my %dot1 = ('a'=>1,'b'=>2);
my %dot2 = ('a'=>3,'b'=>4);
my %dot3 = ('a'=>5,'b'=>6);
my %dot4 = ('a'=>7,'b'=>8);
my #array = (\%dot1,\%dot2,\%dot3,\%dot4); # here
my $x = $array[2]; # here
my $y = $x->{'a'};
print "$y \n";
This will print 5.
You should read up on data structures in perlref and perlreftut.

If you want an array of hash references, you need to say so explicitly.
my #array = (\%dot1, \%dot2, \%dot3, \%dot4);
my %x = %{$array[2]};
my $y = $x{a};
print "$y\n";

What you want to do is add references to your hashes to your #array, otherwise perl will evaluate the hashes in list context.
my #array = (\%dot1,\%dot2,\%dot3,\%dot4);
my $x = $array[2];
my $y = $x->{'a'};
print "$y \n";

Related

PowerShell dictionary of arrays?

In VBScript I can do something like this
Set objDict = CreateObject("Scripting.Dictionary")
objDict.Add "item1", Array("data1", "data2")
objDict.Add "item2", Array("data3", "data4")
Then I can do a lookup using something like this
dataArray = objDict.Item("item2")
elem1 = dataArray(0)
elem2 = dataArray(1)
the result is elem1 contains "data3" and elem2 contains "data4"
I am not sure how to replicate this in PowerShell. Anyone help?
Dictionaries are called hashtables in PowerShell. When PowerShell first came out Microsoft also released a VBScript-to-PowerShell Conversion Guide, covering dictionaries and arrays among other things.
You define hashtables (dictionaries) like this:
$d = #{
'foo' = 'something'
'bar' = 42
}
Alternatively –for instance if you need to populate the hashtable dynamically– you can create an empty hashtable and add elements to it, like this:
$d = #{}
$d.Add('foo', 'something')
$d.Add('bar', 42)
or like this:
$d = #{}
$d['foo'] = 'something'
$d['bar'] = 42
Usually I prefer the latter, because it replaces existing keys instead of throwing an error.
$d1 = #{}
$d1.Add('foo', 23)
$d1.Add('foo', 42) # can't add another key with same name => error
$d2 = #{}
$d2['foo'] = 23 # key is automatically added
$d2['foo'] = 42 # replaces value of existing key
Arrays are defined as a simple comma-separated list:
$a = 'a', 'b', 'c'
Optionally you can also use the array subexpression operator:
$a = #('a', 'b', 'c')
That's useful if you need an array result, but are unsure of how many results your expression will produce. Without the operator you'd get $null, a single value, or an array, depending on the result of the expression. By using the operator you always get an array, with zero, one, or more elements.
Both array and hashtable elements can be accessed via the index operator ([]):
$d['foo'] # output: "something"
$a[1] # output: "b"
The elements of hashtables can also be accessed via dot-notation:
$d.foo # output: "something"
Of course you can nest arrays in hashtables and vice versa, just like you can do in VBScript. Your example would look somewhat like this in PowerShell
$dict = #{
'item1' = 'data1', 'data2'
'item2' = 'data3', 'data4'
}
$dataArray = $dict['item2']
$elem1 = $dataArray[0]
$elem2 = $dataArray[1]
PowerShell has learned a thing or two from other scripting languages, though. For instance you can assign arrays to a list of variables, so the above assignment of array elements to individual variables could be simplified to this:
$elem1, $elem2 = $dict['item2']
If the array has more elements than the number of variables on the left side of the assignment, the last variable takes the remainder of the array:
$x, $y, $z = 'a', 'b', 'c', 'd'
# $x -> 'a'
# $y -> 'b'
# $z -> #('c', 'd')
$x, $y, $z = 'a', 'b'
# $x -> 'a'
# $y -> 'b'
# $z -> $null
The index operator allows access to multiple elements:
$a = 'a', 'b', 'c', 'd', 'e'
$a[1,3,4] # output: "b", "d", "e"
as well as elements from the end of the array:
$a = 'a', 'b', 'c', 'd', 'e'
$a[-1] # output: "e"
$a[-2] # output: "d"
And the range operator (..) allows you to get a slice from an array by just specifying the first and last index. It produces a list of numbers starting with the first operand and ending with the second operand (e.g. 2..5→2,3,4,5).
$a = 'a', 'b', 'c', 'd', 'e'
$a[1..3] # equivalent to $a[1,2,3], output: "b", "c", "d"
$a[-1..-3] # equivalent to $a[-1,-2,-3], output: "e", "d", "c"
Another solution, you can use a System.Collections.Generic.Dictionary like this:
#for create your dictionary
$mydico = New-Object 'System.Collections.Generic.Dictionary[string,string]'
#For add element into your dictionary
$mydico.Add("mykey1", "myvalue1")
$mydico.Add("mykey2", "myvalue2")
#for get value by key
$value = $mydico["mykey1"] # $value has myvalue1 like value

Adding values together from a hash

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.

Using an array to index into another array

I have two arrays, let's call them #a1 and #a2. What I'm trying to do is obtain elements from #a2 using the values in #a1 as indices. My current attempt doesn't work properly.
foreach (#a1) {
print $a2[$_] . "at" . $_;
}
This only prints $_ but not $a2[$_].
I sense there is a trivial solution to this, but I just can't find it.
There is nothing wrong with the code you have. I have tested a small script and it works as expected. Asi i suggested in my comment, try using something like Data::Dumper to see whats in the arrays before the loop.
use strict;
use warnings;
use Data::Dumper;
my #a1 = (0..4);
my #a2 = ("a".."e");
print Dumper \#a1, \#a2;
foreach (#a1){
print $a2[$_]." at ".$_."\n";
}
OUTPUT
$VAR1 = [
0,
1,
2,
3,
4
];
$VAR2 = [
'a',
'b',
'c',
'd',
'e'
];
a at 0
b at 1
c at 2
d at 3
e at 4
there's no reason your code shouldn't work as long as the values of the first array are valid addresses in the second array. but if all you really want to do is just get the values and address of the second array, you could just do:
for my $i (0..$#a2) {
print "$i: $a2[$i]","\n";
}
$#a2 is the last element address of the array.

Two-dimensional array access in Perl

I have an array populated with cities. I want to pass the array by reference to a sub routine and print each city to output. However, I have the following problems:
I can access each element before my while loop in the subroutine. But I cannot access the elements within my while loop. I get the error message:
...
Use of uninitialized value in print at line 44, line 997 (#1)
Use of uninitialized value in print at line 44, line 998 (#1)
...
The following is some code. I have commented what prints and what doesn't (I tried to cut out code that is not needed for my explanation...):
#cities;
# Assume cities is loaded successfully
&loadCities(getFileHandle('cities.txt'), $NUM_CITIES, \#cities);
&printElements(getFileHandle('names.txt'), \#cities);
sub printElements{
my $counter = 0;
my $arraySize = scalar $_[1];
# Prints fine!!!
print #{$_[1][($counter)%$arraySize];
while ((my $line = $_[0]->getline()) && $counter < 1000){
# Doesn't print. Generates the above error
print #{$_[1][($counter)%$arraySize];
$counter += 1;
}
}
The Perl syntax has me super confused. I do not understand what is going on with #{$_[1]}[0]. I am trying to work it out.
$_[1], treat the value at this location as scalar value (memory
address of the array)
#{...}, interpret what is stored at this
memory address as an array
#{...} [x], access the element at index x
Am I on the right track?
My first tip is that you should put use strict; and use warnings; at the top of your script. This generally reveals quite a few things.
This line: print #{$_[1][($counter)%$arraySize]; doesn't have a closing }. You also don't need the parenthesis around $counter.
Like you mentioned, the best/most clear way to get the length of an array is my $arraySize = scalar #{$_[1]};.
You can check out the documentation here for working with references. I'll give you a quick overview.
You can declare an array as normal
my #array = (1, 2, 3);
Then you can reference it using a backslash.
my $array_ref = \#array;
If you want to use the reference, use #{...}. This is just like using a regular array.
print #{$array_ref};
You could also declare it as a reference to begin with using square braces.
my $array_ref = [1, 2, 3];
print #{$array_ref}; # prints 123
In Perl, a 2-dimensional array is actually an array of array references. Here is an example:
my #array = ( ['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i'] );
print #{$array[1]}; # prints def
Now let's try passing in an array reference to a subroutine.
my #array = ( ['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i'] );
example(\#array); # pass in an array reference
sub example {
my #arr = #{$_[0]}; # use the array reference to assign a new array
print #{$arr[1]};
print #{$_[0][1]}; # using the array reference works too!
}
Now let's put it together and print the whole 2-d array.
my #array = ( ['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i'] );
example(\#array);
sub example {
my #arr = #{$_[0]};
for my $ref (#arr) {
print #{$ref};
}
} # prints abcdefghi
You could adapt this example pretty easily if you wanted to use it for your printElements subroutine.
Another note about printing the elements in an array. Let's take this line from the last example:
print #{$ref};
Since we are calling it every time through the loop, we may want to print a new line at the end of it.
print #{$ref} . "\n";
What does this print? Try it! Does it work?
This is where the built-in subroutine join comes in handy.
print join(" ", #{$ref}) . "\n";
For loops are generally the best way to iterate through an array. My answer here talks a little about doing it with a while loop: https://stackoverflow.com/a/21950936/2534803
You can also check out this question: Best way to iterate through a Perl array
To make references a bit easier to understand, I prefer the -> syntax instead of the munge-it-all-together syntax.
Instead of:
#{$_[1]}[0].
Try
$_[1]->[0];
It means the same thing. It's easier, and it's cleaner to see. You can see that $_[1] is an array reference, and that you're referencing the first element in that array reference.
However, an even better way is to simply set variables for your various element in #_. You have to type a few more letters, but your code is much easier to understand, and is a lot easier to debug.
sub print_elements {
my $file_handle = shift; # This isn't a "reference", but an actual file handle
my $cities_array_ref = shift; # This is a reference to your array
my #cities = #{ $cities_array_ref }; # Dereferencing makes it easier to do your program
Now, your subroutine is dealing with variables which have names, and your array reference is an array which makes things cleaner. Also, you cannot accidentally affect the values in your main program. When you use #_, it's a direct link to the values you pass to it. Modifying #_ modifies the value in your main program which is probably not what you want to do.
So, going through your subroutine:
sub printElements {
my file_handle = shift;
my $cities_array_ref = shift;
my #cities = #{ $cities_array_ref };
my $counter;
my $array_size = #cities; # No need for scalar. This is automatic
while ( my $line = $file_handle->getline and $counter < 1000 ) {
chomp $line;
my $city_number = $counter % $array_size;
print $cities[$city_number]. "\n";
$counter += 1;
}
}
Note, how much easier it is to see what's going on by simply assigning a few variables instead of trying to cram everything together. I can easily see what your parameters to your subroutine are suppose to be. If you called the subroutine with the incorrect parameter order, you could easily spot it. Also notice I broke out $counter % $array_size and assigned that to a variable too. Suddenly, it's obvious what I'm trying to get out of it.
However, I can't see where you're using the $line you're getting with getline. Did I miss something?
By the way, I could have done this without referencing the array in the while loop too:
sub printElements {
my file_handle = shift;
my $cities = shift; # This is an array reference!
my $counter;
my $array_size = #{ $cities }; # I need to deref to get an array
while ( my $line = $file_handle->getline and $counter < 1000 ) {
chomp $line;
my $city_number = $counter % $array_size;
print $cities->[$city_number]. "\n"; # That's it!
$counter += 1;
}
}
See how that -> syntax makes it easy to see that $cities is a reference that points to an array? A lot cleaner and easier to understand than ${$cities}[$city_number].
This code wouldn't actually compile.
print #{$_[1][($counter)%$arraySize];
It probably wants to be
print $_[1]->[($counter)%$arraySize];
after you fix arraySize.
If the result is somehow a pointer to an array then
print "#{$_[1]->[($counter)%$arraySize]}";
I figured how to solve my #1 problem (still looking for help on my #2 if anyone can).
I changed
my $arraySize = scalar $_[1];
to
my $arraySize = #{$_[1]};
And my second print statement is printing the way I want.
It seems that scalar $_[1] was taking the memory address of the array and I was moding against this allowing my $counter to go way beyond the number of elements in the array.
References confuse me too! I always like to dereference them as soon as possible. This works for me:
sub printElements{
my $counter = 0;
my $fh = $_[0];
my #array = #{$_[1]};
my $arraySize = scalar #array;
# Prints fine!!!
print #array[($counter)%$arraySize];
while ((my $line = $fh->getline()) && $counter < 1000){
#Doesn't print. Generates the above error
print #array[($counter)%$arraySize];
$counter += 1;
}
}
I'm sure someone else could explain in the comments why they think working with the reference is a better way (please do), but under the mantra of "keep it simple", I don't like working with them. Probably because I was never a C programmer...

How can I create a variable array name in Perl?

Array #p is a multiline array, e.g. $p[1] is the second line.
This code will explain what I want:
$size=#p; # line number of array #p
for($i=0; $i<$size; $i++)
{
#p{$i}= split(/ +/,$p[$i]);
}
I want the result should be like this:
#p0 = $p[0] first line of array #p goes to array #p0;
#p1 = $p[1] second line of array #p goes to array #p1;
...
...
and so on.
But above code does not work, how can I do it?
It is a bad idea to dynamically generate variable names.
I suggest the best solution here is to convert each line in your #p array to an array of fields.
Lets suppose you have a better name for #p, say #lines. Then you can write
my #lines = map [ split ], <$fh>;
to read in all the lines from the file handle $fh and split them on whitespace. The first field of the first line is then $lines[0][0]. The third field of the first line is $lines[0][2] etc.
First, the syntax #p{$i} accesses the entry with the key $i in a hash %p, and returns it in list context. I don't think you meant that. use strict; use warnings; to get warned about undeclared variables.
You can declare variables with my, e.g. my #p; or my $size = #p;
Creating variable names on the fly is possible, but a bad practice. The good thing is that we don't need to: Perl has references. A reference to an array allows us to nest arrays, e.g.
my #AoA = (
[1, 2, 3],
["a", "b"],
);
say $AoA[0][1]; # 2
say $AoA[1][0]; # a
We can create an array reference by using brackets, e.g. [ #array ], or via the reference operator \:
my #inner_array = (1 .. 3);
my #other_inner = ("a", "b");
my #AoA = (\#inner_array, \#other_array);
But careful: the array references still point to the same array as the original names, thus
push #other_inner, "c";
also updates the entry in #AoA:
say $AoA[1][2]; # c
Translated to your problem this means that you want:
my #pn;
for (#p) {
push #pn, [ split /[ ]+/ ];
}
There are many other ways to express this, e.g.
my #pn = map [ split /[ ]+/ ], #p;
or
my #pn;
for my $i ( 0 .. $#p ) {
$pn[$i] = [ split /[ ]+/, $p[$i] ];
}
To learn more about references, read
perlreftut,
perldsc, and
perlref.

Resources