kalee has asked for the wisdom of the Perl Monks concerning the following question:
I've create a simple hash reference with the following data:
my $weapons_ref = {
dagger => { cost => 8, damage => 4, armor => 0 },
shortsword => { cost => 10, damage => 5, armor => 0 },
warhammer => { cost => 25, damage => 6, armor => 0 },
longsword => { cost => 40, damage => 7, armor => 0 },
greataxe => { cost => 74, damage => 8, armor => 0 },
};
I want to reference both the data in the first list of keys, then the data is the subsets. Much like I would in a map with (javascript). Though, I would think this would be just as easy in Perl, I just haven't figured it out and need some help.
I need to look though the weapons "list". I would also like to sum by cost, damage and armor in calculations.
I was hoping there would be an easy was to sum the weapons cost for example. I was thinking I could do this with a postfix operation, but my general confusion with a hash of a hashes keeps getting in the way. Any wisdom would be appreciated!
If you have general information for learning how to manipulate array of hashes and hash of hashes in general, that would be appreciated as well. I really would like to really understand this better!
Thank you
KALee
Re: using ref to hash of hash effectively
by GrandFather (Saint) on Dec 27, 2020 at 02:03 UTC

use warnings;
use strict;
my $weapons_ref = {
dagger => { cost => 8, damage => 4, armor => 0 },
shortsword => { cost => 10, damage => 5, armor => 0 },
warhammer => { cost => 25, damage => 6, armor => 0 },
longsword => { cost => 40, damage => 7, armor => 0 },
greataxe => { cost => 74, damage => 8, armor => 0 },
};
print "$weapons_ref>{dagger}{cost}, ${$weapons_ref}{dagger}{damage}";
Using > or {$reference} is somewhat a matter of personal preference in simple cases, but the {} form can be easier to read when things get complicated. See perlref.
Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
 [reply] [d/l] [select] 
Re: using ref to hash of hash effectively
by Cristoforo (Curate) on Dec 27, 2020 at 02:14 UTC

To get the total sums of cost, damage, armor, you can use the map function to accumulate the various items.
#!/usr/bin/perl
use strict;
use warnings;
use List::Util 'sum';
my $weapons_ref = {
dagger => { cost => 8, damage => 4, armor => 0 },
shortsword => { cost => 10, damage => 5, armor => 0 },
warhammer => { cost => 25, damage => 6, armor => 0 },
longsword => { cost => 40, damage => 7, armor => 0 },
greataxe => { cost => 74, damage => 8, armor => 0 },
};
my $cost = sum(map $_>{cost}, values %$weapons_ref);
my $damage = sum(map $_>{damage}, values %$weapons_ref);
print "cost: $cost damage: $damage\n";
Prints:
cost: 157 damage: 30
 [reply] [d/l] [select] 
Re: using ref to hash of hash effectively
by eyepopslikeamosquito (Bishop) on Dec 27, 2020 at 04:00 UTC

Perhaps the definitive reference for this sort of thing is in the standard Perl docs: perldsc (Perl Data Structures Cookbook).
After you read and understand that, you should be on your way.
For some light relief, some sample code to crudely dump ... then "pretty print" your hash of hashes:
use strict;
use warnings;
use Data::Dumper;
my $weapons_ref = {
dagger => { cost => 8, damage => 4, armor => 0 },
shortsword => { cost => 10, damage => 5, armor => 1 },
warhammer => { cost => 25, damage => 6, armor => 2 },
longsword => { cost => 40, damage => 7, armor => 0 },
greataxe => { cost => 74, damage => 8, armor => 0 },
};
print Dumper($weapons_ref);
# Print by hand so it looks a bit prettier. :)
for my $weapon ( sort keys %{$weapons_ref} ) {
printf "%14.14s\n", $weapon . ( '' x 20 );
for my $attr ( sort keys %{ $weapons_ref>{$weapon} } ) {
printf " %8.8s:%3d\n", $attr, $weapons_ref>{$weapon}{$attr};
}
}
which produces:
$VAR1 = {
'dagger' => {
'cost' => 8,
'armor' => 0,
'damage' => 4
},
'greataxe' => {
'damage' => 8,
'armor' => 0,
'cost' => 74
},
'warhammer' => {
'armor' => 2,
'damage' => 6,
'cost' => 25
},
'shortsword' => {
'damage' => 5,
'armor' => 1,
'cost' => 10
},
'longsword' => {
'cost' => 40,
'damage' => 7,
'armor' => 0
}
};
dagger
armor : 0
cost : 8
damage : 4
greataxe
armor : 0
cost : 74
damage : 8
longsword
armor : 0
cost : 40
damage : 7
shortsword
armor : 1
cost : 10
damage : 5
warhammer
armor : 2
cost : 25
damage : 6
I leave summing by cost and other attributes as an exercise for the reader ... just in case it's a homework question. :)
 [reply] [d/l] [select] 
Re: using ref to hash of hash effectively
by LanX (Cardinal) on Dec 27, 2020 at 02:14 UTC

It's not clear for me what you want and all those typos don't help make it clearer.
The following example should get you started
It shows how to iterate easily thru a nested HoH
use strict;
use warnings;
use 5.10.0;
my $weapons_ref =
{
dagger => { cost => 8, damage => 4, armor => 0 },
shortsword => { cost => 10, damage => 5, armor => 0 },
warhammer => { cost => 25, damage => 6, armor => 0 },
longsword => { cost => 40, damage => 7, armor => 0 },
greataxe => { cost => 74, damage => 8, armor => 0 },
};
while ( my ($k1, $v1) = each %$weapons_ref ){
while ( my ($k2, $v2) = each %$v1 ){
say "$k1 $k2 $v2";
}
}
I'd love to see your "easy" JS code tho! ;)
Cheers Rolf
_{(addicted to the Perl Programming Language :)
Wikisyntax for the Monastery
}
 [reply] [d/l] 

// Day 21: RPG Simulator 20XX
// boss's stats
// Hit Points: 100
// Damage: 8
// Armor: 2
function part1() {
const WEAPONS = new Map([
["dagger", { cost: 8, damage: 4, armor: 0 }],
["shortsword", { cost: 10, damage: 5, armor: 0 }],
["warhammer", { cost: 25, damage: 6, armor: 0 }],
["longsword", { cost: 40, damage: 7, armor: 0 }],
["greataxe", { cost: 74, damage: 8, armor: 0 }],
]);
const ARMOR = new Map([
["nothing", { cost: 0, damage: 0, armor: 0 }],
["leather", { cost: 13, damage: 0, armor: 1 }],
["chainmail", { cost: 31, damage: 0, armor: 2 }],
["splintmail", { cost: 53, damage: 0, armor: 3 }],
["bandedmail", { cost: 75, damage: 0, armor: 4 }],
["platemail", { cost: 102, damage: 0, armor: 5 }],
]);
const RINGS = new Map([
["nothing", { cost: 0, damage: 0, armor: 0 }],
["damage+1", { cost: 25, damage: 1, armor: 0 }],
["damage+2", { cost: 50, damage: 2, armor: 0 }],
["damage+3", { cost: 100, damage: 3, armor: 0 }],
["defense+1", { cost: 20, damage: 0, armor: 1 }],
["defense+2", { cost: 40, damage: 0, armor: 2 }],
["defense+3", { cost: 80, damage: 0, armor: 3 }],
]);
const BOSS = new Map([
["damage", 8],
["armor", 2],
["health", 100],
]);
const PLAYER = new Map([
["damage", 0],
["armor", 0],
["health", 100],
]);
const getTotalStats = (weapon, armor, leftRing, rightRing) => {
return {
cost: weapon.cost + armor.cost + leftRing.cost + rightRing.cost,
damage: weapon.damage + armor.damage + leftRing.damage + rightRi
+ng.damage,
armor: weapon.armor + armor.armor + leftRing.armor + rightRing.a
+rmor,
};
};
const hitPerSecond = (defenderHealth, defenderArmor, attackerDmg) =>
Math.ceil(defenderHealth / Math.max(1, attackerDmg  defenderArmor
+));
const makeMove = (boss, player) =>
hitPerSecond(boss.get("health"), boss.get("armor"), player.get("da
+mage")) <=
hitPerSecond(player.get("health"), player.get("armor"), boss.get("
+damage"));
function* possibleBundles() {
for (let weapon of WEAPONS.values()) {
for (let armor of ARMOR.values()) {
for (let leftRing of RINGS.values()) {
for (let rightRing of RINGS.values()) {
if (rightRing.cost !== leftRing.cost)
yield getTotalStats(weapon, armor, leftRing, rightRing);
}
}
}
}
}
let result = Infinity;
for (let bundle of possibleBundles()) {
PLAYER.set("damage", bundle.damage).set("armor", bundle.armor);
if (makeMove(BOSS, PLAYER)) result = Math.min(result, bundle.cost)
+;
}
console.log(result);
}
part1();
 [reply] [d/l] 
Re: using ref to hash of hash effectively
by alexander_lunev (Pilgrim) on Dec 27, 2020 at 07:21 UTC

Hello!
You already has been provided with more than one way to do this, and I want to participate in this game too. Here's another way  just like you wanted  using map:
use strict;
my $weapons_ref = {
dagger => { cost => 8, damage => 4, armor => 0 },
shortsword => { cost => 10, damage => 5, armor => 0 },
warhammer => { cost => 25, damage => 6, armor => 0 },
longsword => { cost => 40, damage => 7, armor => 0 },
greataxe => { cost => 74, damage => 8, armor => 0 },
};
my $sum_cost;
my $sum_damage;
my $sum_armor;
map {
$sum_damage += $weapons_ref>{$_}>{damage};
$sum_cost += $weapons_ref>{$_}>{cost};
$sum_armor += $weapons_ref>{$_}>{armor}
} keys %{ $weapons_ref };
print join(' ',$sum_cost, $sum_damage, $sum_armor), '\n';
157 30 0
This code is a little bit specific, while I think we should move towards more general code, to make reusing it more simpler. Let's refactor it:
use strict;
use Data::Dumper;
my $weapons_ref = {
dagger => { cost => 8, damage => 4, armor => 0 },
shortsword => { cost => 10, damage => 5, armor => 0 },
warhammer => { cost => 25, damage => 6, armor => 0 },
longsword => { cost => 40, damage => 7, armor => 0 },
greataxe => { cost => 74, damage => 8, armor => 0 },
};
my $sum_result;
my @sum_fields = qw/cost damage armor/;
map {
my $name = $_;
map { $sum_result>{$_} += $weapons_ref>{$name}>{$_} } @sum_fields
} keys %{ $weapons_ref };
print Dumper($sum_result);
$VAR1 = {
'armor' => 0,
'cost' => 157,
'damage' => 30
};
Next step is to refactor this code to a function, which will receive a hash, an array of fields that we like to sum, and return a hash with sums.  [reply] [d/l] [select] 

Though it's nice to show different ways to do it, I feel obliged to point out
that many folks disapprove of using map in void context.
Though map in void context no longer suffers the huge performance penalty it once
did, some still consider it poor style
(see, for example, the performancerelated discussion in this old thread).
I personally applaud the simple stylistic advice summary given in
Effective Perl Programming, in the item
"Use foreach, map and grep as appropriate", namely:
 Use foreach to iterate readonly over each element of a list
 Use map to create a list based on the contents of another list
 Use foreach to modify elements of a list
 Use grep to select elements in a list
because I find the code clearer and easier to maintain if everyone in my team follows those four simple rules.
Note that Perl::Critic provides a ProhibitVoidMap
policy to allow the code police to prohibit using map in void context.
 [reply] [d/l] [select] 

Well, yes, there are some folks, that disapprove using our beloved language in some way that they think is wrong. But this is an aesthetical dispute, not technical. Some folks like it one way, other folks like it another way  and they do so they like, and all they do is beautiful (at least to themselves), and our main principle  multiple ways of doing one thing  flourishes.
And in a spiritual matter, it is a god's blessing that recipes from nomapinvoidcontext part of folks and recipes from the other folks has been given. In a dark times of which I think no need to mention, every single group, even one that think of usingmapinvoidcontext as of heresy, comes to help seeking wisdom in our monastery.
And thank you for pointing out that our beloved monastery is not a dogmatic place. Yes, there are different monks that chant for gloriness with different means and using different techniques, and all as one are welcoming and kind to strangers  and to themselves. Such fellowship is a true blessing.
 [reply] 






Since the weapon names don't seem to matter, only their attributes,
I think it's simpler to iterate over the values of the
weapons hash per Cristoforo's approach. This works
with map, but I think it's even simpler (and IMHO clearer) to use
forloops:
Win8 Strawberry 5.8.9.5 (32) Sun 12/27/2020 5:07:00
C:\@Work\Perl\monks
>perl Mstrict Mwarnings l
use Data::Dumper;
my $weapons_ref = {
dagger => { cost => 8, damage => 4, armor => 0, },
shortsword => { cost => 10, damage => 5, armor => 0, },
warhammer => { cost => 25, damage => 6, armor => 0, },
longsword => { cost => 40, damage => 7, armor => 0, },
greataxe => { cost => 74, damage => 8, armor => 0, },
};
my $sum_result;
my @sum_fields = qw/cost damage armor/;
# map { # works
# my $attrib = $_;
# map { $sum_result>{$_} += $attrib>{$_} } @sum_fields
# } values %$weapons_ref;
for my $hr_attrib (values %$weapons_ref) {
$sum_result>{$_} += $hr_attrib>{$_} for @sum_fields;
}
print Dumper $sum_result;
^Z
$VAR1 = {
'cost' => 157,
'damage' => 30,
'armor' => 0
};
Give a man a fish: <%{{{<
 [reply] [d/l] [select] 

This is definitely the direction I was looking for! Both of these!
The problem is part of adventofcode dot com  2015 day 21, so not a homework problem per say, and I've already solved it in another language. This is really me wanting to learn how to use arrays and hashes of stuff more effectively.
I really should have added that this is a Data Structure/Algorithm problem. In general, I want to learn how to structure the data so I can slice and dice it better. Leaving the data in two nested anonymous hashes isn't necessarily the goal. Rearranging the data to easily manipulate it would definitely be best!
I have been trying to figure out how I could use List::AllUtils to get groups of data out of this grouping or set of data. Though, changing the initial structure may be a better solution. I don't know what I don't know. That is kind of the open question. When you get a set of data like this, and please look at this fun and interesting problem, is there some sort of best practice for what you check for to decide what type of data structures to put the data into? That would truly be the magical part!
 [reply] [d/l] 

Re: using ref to hash of hash effectively
by perlfan (Vicar) on Dec 28, 2020 at 17:55 UTC

You may also trade space for complexity, and maybe a few more brain cells, by transforming a cononical hash into data structures that might be more efficient for computing and tracking various things. Although it's sometimes implied, there is no reason you have to have just one hash (or stucture)  the biggest issue there is going to be ensuring all of your structures are synchronized. Again, this is in the event that you find yourself doing mental hoops to iterate over a data structure when it'd be a lot easier to compute or look up what you need after a straightforward transformation.  [reply] 

