Raku by Example: Hashes

Also known as an associative array, map, dictionary, etc. in other programming languages, in Raku a hash is a set of key-value pairs. Instead of positionally indexed values, each value is associated with a string (known as the key).

A hash is usually declared by assigning a list of keys and values to a hash variable (i.e., with % sigil).

my %person-age = 'Joan', 25, 'Ben', 23, 'Lia', 20;

# Or using the hash constructor `%()`:
%person-age = %('Joan', 25, 'Ben', 23, 'Lia', 20); 

# A more distinctive way of filling the hash and showing pair-ness is by 
# using the fat comma (=>). The fat comma quotes its respective key so quotes
# around the keys are unnecessary.
%person-age = Joan => 25, Ben => 23, Lia => 20;

# Or by using the colon pair syntax:
%person-age = :Joan(25), :Ben(23), :Lia(20);

say %person-age{'Joan'};      # accessing an element 
say %person-age<Ben>;         # same as above but the quote-words operator
say %person-age{'Joan'} = 24; # updating an element
%person-age<Thomas> = 26;     # adding a new pair

say %person-age;
$ perl6 hash.p6
25
23
24
{Ben => 23, Joan => 24, Lia => 20, Thomas => 26}

Both the keys and values of hash can have type constraints.

# To constrain a hash's values, place the type between declarator
# and the hash's name.
my Rat %heights = 'Alaia' => 1.56, 'Zelia' => 1.72, 'Riel' => 1.80;

%heights<Dia> = '1.78'; # Error: Type check failed in assignment

# To constrain a hash's keys, place the type inside {} during declaration.
my %dates{Date} = Date.new('2018-12-24')      => 'Christmas Eve', 
                  Date.new('2018-12-24').succ => 'Christmas Day';
                  
%dates{'2018-12-31'} = "New Year's Eve"; # Error: Type check failed in assignment

# Both can be combined to create a hash with typed keys and values.

Many operations can be performed on hashes. For a complete list of methods/subs applicable to hashes, go to https://docs.perl6.org/type/Hash#Methods. Some of them are:

my %eng-to-spa = zero => 'cero', one => 'uno',
                 two => 'dos', three => 'tres';

say "%eng-to-spa{}"; # To interpolate, precede it with `{}`
say "-" x 15;

say "Keys: ", %eng-to-spa.keys;     # returns a list of keys of the hash
say "Values: ", %eng-to-spa.values; # returns a list of values of the hash
say "K-V: ", %eng-to-spa.kv;        # returns a list of both keys and values
say "Pairs:", %eng-to-spa.pairs;    # returns a list of pairs in the hash
say "Elems: ", %eng-to-spa.elems;   # returns the number of pairs

# Some of methods are implemented as adverbs on subscripts. Some of them 
# are `:exists`, `:delete`, `:p`, `:k` and `:v`:

say %eng-to-spa{'four'}:exists; # returns `True` if a key exists in the hash
say %eng-to-spa{'one'}:delete;  # deletes a pair from the hash and returns key
say %eng-to-spa{'two'}:p;       # returns a pair from the hash
say %eng-to-spa{'two'}:k;       # returns the key of a pair
say %eng-to-spa{'two'}:v;       # returns the value of a pair

say %eng-to-spa;
$ perl6 hash-op.p6
one	    uno
three	tres
two	    dos
zero	    cero
---------------
Keys: (two three one zero)
Values: (dos tres uno cero)
K-V: (two dos three tres one uno zero cero)
Pairs:(two => dos three => tres one => uno zero => cero)
Elems: 4
False
uno
two => dos
two
dos
{three => tres, two => dos, zero => cero}

Looping over hashes

A common idiom for processing the elements in a hash is to loop over the keys and values with the .keys and .values methods respectively.

my %eng-to-spa = zero => 'cero', one => 'uno', two => 'dos';

for %eng-to-spa.keys -> $eng-num {
    say $eng-num;
}
say "-" x 20;

for %eng-to-spa.values -> $spa-num {
    say $spa-num;
}
say "-" x 20;

for %eng-to-spa.kv -> $eng-num, $spa-num {
    "%-10s %-10s\n".printf($eng-num.tc, $spa-num.tc);
}
say "-" x 20;

for %eng-to-spa.pairs -> $pair {
    say "Pair: $pair";
}
two
one
zero
--------------------
dos
uno
cero
--------------------
Two        Dos       
One        Uno       
Zero       Cero      
--------------------
Pair: two	dos
Pair: one	uno
Pair: zero	cero

Traditionally, both the keys and the values would be needed to modify the values while iterating over the hash. However, the is rw can be specified in the the signature of the pointy block to indicate the value is read-write. Alternatively, a doubly pointy block (<->) can be used.

my %letters = a => 10, b => 34, A => 7, Z => 3;

# Traditional way
for %letters.kv -> $letter, $freq {
    %letters{$letter} =  $freq + 10;
}

say "Incremented by 10: ", %letters;

# Using `is rw`
for %letters.values -> $freq is rw {      
    $freq += 3;                      
}

say "Incremented by 3: ", %letters;

# Using a doubly pointy block which indicates read-write access
# to its parameters
for %letters.values <-> $freq {      
    $freq += 5;                      
}

say "Incremented by 5: ", %letters;
$ perl6 hash-values.p6
Incremented by 10: {A => 17, Z => 13, a => 20, b => 44}
Incremented by 3: {A => 20, Z => 16, a => 23, b => 47}
Incremented by 5: {A => 25, Z => 21, a => 28, b => 52}

Object hashes

An object hash is a hash with non-string keys. By default keys in a hash are coerced to strings so to prevent this, the syntax :{} must be used while creating the hash.

# string keys hash
my $nonobj-types = %(Int => 'Integer', Rat => 'Rational');

for $nonobj-types.keys -> $k {
    say 'Key: ', $k, " Type: ", $k.^name;
}

# non-string keys hash. Note `:{}` and keys are in parenthesis
my $obj-types = :{ (Int) => "Integer", (Str) => "String"};

for $obj-types.keys -> $k {
    say 'Key: ', $k, " Type: ", $k.^name;
}

my $when = :{ (now) => "Instant", (DateTime.now) => "DateTime" };
say $when;

Hash comprehensions

Hash comprehensions are based on list comprehensions. Nonetheless, to create a hash comprehension, the comprehension must produce an even number of elements and be surrounded by the hash constructor %(). Go to [list comprehensions)[arrays] to read about them.

my @fruits = <mango banana orange pear>;
my %fruit-num-letters = %( $_ => $_.chars for @fruits );

say "# of letters in fruit: ", %fruit-num-letters;

my %letters = a => 10, b => 34, A => 7, Z => 3;
my %letters-freq = %(
   $_.lc => %letters{$_.lc} + %letters{$_.uc} for %letters.keys;
);

say "Letter frequency: ", %letters-freq;

say %( $_ => %($_ => $_.ord, $_.uc => $_.uc.ord) for 'a'..'c');
$ perl6 hash-comp.p6
# of letters in fruit: {banana => 6, mango => 5, orange => 6, pear => 4}
Letter frequency: {a => 17, b => 34, z => 3}
{a => {A => 65, a => 97}, b => {B => 66, b => 98}, c => {C => 67, c => 99}}

For more info, go to:

Back to main