NAME

Sq::Collection::Hash - Hash functions

DESCRIPTION

An Sq Hash is just a Perl Hash blessed in the Hash package. You can create it yourself by just typing.

my $hash = bless({}, 'Hash');

You can work with $hash as any other Hash you are used by Perl. The additional blessing is done so you can call additional methods on it.

Normal Hash

You always can use and threat a Hash created by this package like a normal Perl Hash. Doing this is completely fine.

my $hash = Hash->new(foo => 1, bar => 2);
for my $key ( keys %$hash ) {
    ...
}

$hash->{baz} = 3;
Compatibility

An Sq Hash always should and can be used wherever a Hash reference is expected. This works as long a library does not do any kind of pseudo type-checking and just uses it as a hash-reference.

But it also works the other way around. You always can pass a bare Perl Hash to any function that expects a Hash. Consider that this way you obviously cannot use the Method syntax and are forced to use the functional-style.

# works fine even without blessed Hashes
my $hash = Hash::difference(
    {foo => 1, bar => 1},
    {bar => 1}
);
Performance

Because an Sq Hash is just a normal Perl Hash it doesn't have a performance penalty. The only reason why a blessing is added is because the different functions in this package then can be called as a method on a Hash.

Most functions you find here is code you would have probably written anyway yourself. So in some way you just can consider this package as a utility library providing you many useful functions that you don't need to write yourself.

It makes developing in itself faster because you don't need to re-implement different functions again and again. Also having functions for common tasks makes code in itself shorter and better understandable leading to fewer bugs in general.

Most functions are written in a style that tries to make the task they do as fast as possible but still with the idea to be as correct and useful as possible.

Using functions from this package can still sometimes makes things a little bit slower when it comes to execution timing because this library heavily depends on passing subroutines and calling them. And calling functions are not free especially in Perl. Every function call has some overhead and sadly Perl has no JIT or inlining capability.

But again, as a Sq Hash is just a normal Perl hash with an added blessing only for the purpose for calling the function as a method, you always can use a Sq Hash like any normal Hash and do whatever you want todo with it if the overhead of this module seems to make things slower as expected.

Convenience

This way it just extends the possibility that you can call a lot of functions in this package as methods on a hash.

CONSTRUCTORS

Hash->new(@key_values) : $hash

Creates a new Sq Hash with the defined key and values.

my $hash = Hash->new(foo => 1, bar => 2);
my $hash = { foo => 1, bar => 2 };

Both lines are nearly the same. Hash->new just adds the Hash blessing.

Hash->bless($hash_ref) : $hash

Can be used in two ways. You either can just add the blessing to an existing hash-ref, or use it as initialization.

my $hash_ref = { foo => 1, bar => 2 };
Hash->bless($hash_ref);

my $hash = Hash->bless({ foo => 1, bar => 2 });

Hash->locked($href) : $hash

This creates a locked hash. You must provide a hash-reference. Best is to create the Hash directly in the method call. The hash is blessed in the Hash package and all its keys are locked. This is the fastest way to create a locked hash.

Any attempt to read or write a not allowed key throws an exception.

my $hash = Hash->locked({
    title  => 'Terminator 2',
    rating => 5,
    desc   => 'Awesome',
});

# throws an exception
my $title = $hash->{Title}

# also throws an exception
$hash->{ttitle} = 'Terminator 16';

This is the same as below, but faster.

my $hash = Hash->new(
    title  => 'Terminator 2',
    rating => 5,
    desc   => 'Awesome',
)->lock;

Hash->empty() : $hash

returns a new empty Sq Hash.

my $hash = Hash->empty;

Hash->init($amount, $f_kv) : $hash

Initializes a new Hash with $amount entries. An index is passed to $f_kv that is expected to return a key,value pair.

# { 0 => 0, 1 => 1, 2 => 4, 3 => 9, 4 => 16 }
my $hash = Hash->init(5, sub($idx) {
    return $idx => $idx*$idx;
});

Hash->from_array($array, $f_kv) : $hash

Creates a new Hash from an existing Array. It iterates through an Array passing every value of the array to $f_kv. It then expects that $f_kv returns the key,value to be used in the newly created Hash.

my $names = [qw/Alice Anny Sola Candy Lilly/];
my $hash  = Hash->from_array($names, sub($idx, $name) { $name => $idx });

# this builds an associative hash, also could be done like this
my $idx  = 0;
my %hash = map { $_ => $idx++ } @$names;

METHODS

Like an Sq Array all methods can be called in a method style.

my $length = Hash->empty->length;

or a functional style.

my $length = Hash::length( Hash->empty );

You also always can pass an unblessed perl hash reference.

my $length = Hash::length( {} );

If the above doesn't work it is considered a bug. Additionally every $hash or $array returned by any Hash function should always be blessed in the package Hash or Array. None of those methods mutate a Hash, all of them return a new hash.

copy($hash) : $hash

Creates a shallow copy of the Hash.

my $h = Hash->new(
    foo => 1, bar => 2,
    baz => 3, maz => 4,
);

my $hash = $h->copy;

slice($hash, @keys) : $hash

Hash slice, similar to built-in Hash slice in Perl, but doesn't copy fields that are not defined.

my $h = Hash->new(
    foo => 1, bar => 2,
    baz => 3, maz => 4,
);

my $hash = $h->slice(qw/foo bar/); # { foo => 1, bar => 2 }
my $hash = $h->slice(qw/foo baz/); # { foo => 1, baz => 3 }
my $hash = $h->slice('maz');       # { maz => 4 }

# Perl built-in
my %hash = $h->%{qw/foo bar/};

with($hash, @kvs) : $hash

Like set but creates a new hash by copying the hash. Either adds or changes the specified key,values.

my $h = Hash->new(foo => 1);              # { foo => 1 }

my $i = $h->with(foo => 2, bar => 3);     # { foo => 2, bar => 3 }
my $j = $h->with(bar => 2);               # { foo => 1, bar => 2 }
my $k = $i->with(maz => 4);               # { foo => 2, bar => 3, maz => 4 }
my $l = $k->with(bar => 2, ratatat => 1); # { foo => 2, bar => 2, maz => 4, ratatat => 1 }

withf($hash, @kfs) : $hash

Similar to with but instead of key,value you specify key,function instead. The function you specify gets the current value passed and it is expected to return the new value used in the new hash.

This allows copying a hash and change certain keys that goes beyond just defining a new value. When you specify a key that does not exist in the hash, than this key is ignored.

my $points = Hash->new(
    Anne  => 10,
    Frank => 3,
);

my $hash = $points->withf(Anne  => sub($points) { $points + 1 }); # { Anne => 11, Frank => 3 }
my $hash = $points->withf(Frank => sub($points) { $points + 1 }); # { Anne => 10, Frank => 4 }

my $games = Hash->new(
    n64  => Array->new("Mario 64", "Zelda"),
    snes => Array->new("Super Mario Kart", "Street Fighter 2"),
);

# { n64 => "Mario 64,Zelda", snes => "Super Mario Kart,Street Fighter 2" }
my $hash = $games->withf(
    n64  => sub($array) { $array->join(',') },
    snes => sub($array) { $array->join(',') },
);

map($hash, $f_kv) : $hash

Iterates through a hash and passes every $key and $value to the function $f_kv. $f_kv is supposed to return a new $key and $value that is used to create a new Hash.

my $hash = Hash->new(foo => 1, bar => 2);

# { foofoo => 2, barbar => 4 }
my $new  = $hash->map(sub($key,$value) {
    my $new_key   = $key . $key;
    my $new_value = $value * 2;
    return $new_key, $new_value;
});

bind($hash, $f_hash) : $hash

bind iterates through every key,value of $hash and passes its values to $f_hash. This then returns another hash for every key,value pair. All hashes are then concatenated into one single hash and returned.

my $files = Hash->new(
    etc => Array->new(qw/fstab passwd crontab/),
    bin => Array->new(qw/vim ls man ps/),
);

my $path_length = $files->bind(sub($folder,$files) {
    return $files->to_hash(sub($file) {
        my $path   = $folder . '/' . $file;
        my $length = length $path;
        return $path => $length;
    });
});

# $path_length
# {
#     'etc/fstab'   => 9,
#     'etc/passwd'  => 10,
#     'etc/crontab' => 11,
#     'bin/vim'     => 7,
#     'bin/ls'      => 6,
#     'bin/man'     => 7,
#     'bin/ps'      => 6,
# }

filter($hash, $predicate) : $hash

Iterates through every key,value of a hash and passes it to the $predicate function. When this function returns a truish value, then the (key,value) are used to build and return a new hash.

my $player_points = Hash->new(
    Anne   => 10,
    Marie  => 12,
    Ralph  => 8,
    Rudolf => 9,
);

my $hash = $player_points->filter(sub($k,$v) { $v > 9 ? 1 : 0   }); # { Anne => 10, Marie => 12 }
my $hash = $player_points->filter(sub($k,$v) { $k =~ m/\AR/     }); # { Ralph => 8, Rudolf => 9 }
my $hash = $player_points->filter(sub($k,$v) { $v > 100 ? 1 : 0 }); # {}

append($hashA, $hashB) : $hash

Returns a new hash by combining both hashes. Key,Values in $hashB overwrites entries from $hashA.

my $a = Hash->new(Anne  => 10, Marie => 12);
my $b = Hash->new(Frank => 4,  Anne  => 6);

my $c = Hash::append($a, $b); # { Anne => 6, Marie => 12, Frank => 4 }
my $c = $a->append($b);       # { Anne => 6, Marie => 12, Frank => 4 }

concat(@hashes) : $hash

Like append but repeadetly appends all hashes into a single hash. You must at least pass a single hash otherwise function throws an error.

my $hash = Hash::concat($hashA, $hashB, $hashC, $hashD, ...);
my $hash = $hashA->concat($hashB, $hashC, $hashD, ...);

union($hashA, $hashB, $f_value) : $hash

Union of two hashes. This means they are added together like append. But while append just overwrites key,values from the first hash, this function let's you choose what should happen when a key exists in both hashes.

In that case $f_value is called with $key,$a_value,$b_value and should return the value that is used for that $key.

my $h = Hash->new(foo => 1, bar => 2);
my $i = Hash->new(bar => 3, baz => 4);

# { foo => 1, bar => 5, baz => 4 }
my $hash = $h->union($i, sub($k, $v1, $v2) { $v1 + $v2 });

intersection($hashA, $hashB, $f_value) : $hash

Intersection of two Hashes. This means that only keys that appear in both hashes are picked. Then $f_value is called with $key,$a_value,$b_value that then is supposed to return the $value used for the newly created $hash.

my $h = Hash->new(foo => 1, bar => 2);
my $i = Hash->new(bar => 3, baz => 4);

my $hash = $h->intersection($i, sub($k,$x,$y) { [$x,$y]           }); # { bar => [2,3] }
my $hash = $h->intersection($i, sub($k,$x,$y) { $x > $y ? $x : $y }); # { bar => 3     }

difference($hashA, $hashB) : $hash

returns a new hash with the difference of both. You can think of it as doing $hashA - $hashB. All keys that appear in $hashB are removed from $hashA and the result is returned.

my $h = Hash->new(foo => 1, bar => 2);
my $i = Hash->new(bar => 3, baz => 4);

my $hash = $h->difference($i);      # { foo => 1 }
my $hash = Hash::difference($h,$i); # { foo => 1 }

CONVERTERS

Converters are also METHODS that either transforms or extract data from a Hash. Usually returning something different than a Hash. Like METHODS they also can be called in a functional-style.

is_subset_of($hashA, $hashB) : $bool

Checks if all keys in $hashA also appear in $hashB. When this is the case than a truish value is returned, otherwise not.

my $a = Hash->new(foo => 1);
my $b = Hash->new(foo => 1, bar => 2);

my $bool = $a->is_subset_of($b); # 1
my $bool = $b->is_subset_of($a); # 0
my $bool = Hash::is_subset_of(   # 1
    {a => 1},
    {a => 1, b => 1}
);

is_empty($hash) : $bool

returns a boolean value indicating if the Hash is empty or not.

my $bool = Hash->empty->is_empty;         # 1
my $bool = hash->new(foo => 1)->is_empty; # 0

length($hash) : $int_length

returns the amount of entries in a Hash.

my $x = Hash->new(foo => 1, bar => 1, baz => 1)->length; # 3

keys($hash) : $array

returns all keys of a Hash as a Sq Array. Same as perl built-in keys functions.

for my $key ( @{ $hash->keys } ) {
    say $key;
}

# keys returns a Sq Array
$hash->keys->iter(sub($key) {
    say $key;
});

values($hash) : $array

returns all values of a hash as a Sq Array. Same as perl built-in values.

for my $value ( @{ $hash->values } ) {
    say $value;
}

# values returns a Sq Array
$hash->values->iter(sub($value) {
    say $value;
});

has_keys($hash, @keys) : $bool

Check if @keys exists and are defined in $hash.

my $data = Hash->new(
    foo => 1,
    bar => Array->new(1..5),
    baz => Array->new(
        Hash->new(name => "one"),
        Hash->new(name => "two"),
    ),
    raz => undef,
);

my $bool = $data->has_keys(qw/foo/);             # 1
my $bool = $data->has_keys(qw/foo bar baz/);     # 1
my $bool = $data->has_keys(qw/foo bar baz raz/); # 0
my $bool = $data->has_keys(qw/foo bar maz/);     # 0

get($hash, $key) : $opt_value

Get $key as an optional value from $hash. When the value already is an optional than it is returned as-is. When the key does not exists or is not defined a None is returned. In all other cases Some will be returned.

my $opt    = $hash->get('name');
my $string = $hash->get('name')->or('Anne');

An optional value is not wrapped again.

my $movie = Hash->new(
    title  => 'Terminator 2',
    rating => Some(5),
);

my $opt_title  = $movie->get('title');  # Some('Terminator 2')
my $opt_rating = $movie->get('rating'); # Some(5)

extract($hash, @keys) : $array_of_opt

Extracts many keys at once and returns them in an array in order they were specified. Every extracted key is an Option type representing if the key existed or not.

my $h = Hash->new(foo => 1, bar => 2, baz => 3);

my $array_opts = $h->extract(qw/foo latz bar/);                       # [Some 1, None, Some 2]
my $opt_array  = Option->all_valid(   $h->extract(qw/foo bar/));      # Some([1, 2])
my $opt_array  = Option->all_valid(   $h->extract(qw/foo latz bar/)); # None
my $array      = Option->filter_valid($h->extract(qw/foo latz bar/)); # [1, 2]

equal($hashA, $hashB) : $bool

Checks if two hashes are equal. At the moment this is just a shallow check, but will be extended in the future. Both hashes must have the same keys to be equal including its values.

my $h = Hash->new(foo => 1);
my $i = Hash->new(foo => 1);

my $bool = $h->equal($i);                  # 1
my $bool = $h->equal($i->with(test => 1)); # 0

find($hash, $predicate) : $opt_tuple_of_kv

Goes through every key and value of a Hash, passes it to $predicate and returns the first key,value that $predicate returns a truish value as an optional value. The key,value is put into an array/tuple. When $predicate does not find any key,value than it returns None.

Hashes have no order, so if $predicate could return true for multiple entries then you cannot expect a certain entry.

my $opt_found = $hash->find(sub($k,$v) {
    return 1 if $v > 1000;
});

my $data = Hash->new(
    1  => 'foo',
    2  => 'bar',
    10 => 'baz',
);

my $baz  = $data->find(sub($k,$v){ return $k >= 10 ? 1 : 0 }); # Some([10 => 'baz'])
my $foo  = $data->find(sub($k,$v){ return $k < 2   ? 1 : 0 }); # Some([ 1 => 'foo'])
my $none = $data->find(sub($k,$v){ return $k > 100 ? 1 : 0 }); # None

# "baz"
my $value =
    $data
    ->find(sub($k,$v) { $k >= 10    })
    ->map(sub($array) { $array->[1] })  # Option::map
    ->or("whatever");                   # Option::or

A Tuple is usually considered the combination of two values of possible different types. They are used in ML languages. For example in F# they are created with the , operator. So you can write stuff like let x = "foo", 3. And x now represents two values, a string and an integer. Usually those languages also supports creating tuples of even more values tahn just two. In a ML language those tuples are used quite often for representing things like key,value pairs or all other kind of things. They are still fully statically typed there. But in Perl this concept is not really needed because of dynamic-typing. In Perl we can just use an Array to represent 2, 3 or many more values of different types because Perl Arrays are not restricted to just one type.

pick($hash, $f_opt) : $opt_x

Similar to find as it searches for a key,value pair. But instead of providing a function that returns a truish value it is expected that it returns an optional value.

If $f_opt returns Some value then the value is used, when it return None then the value is skipped. When $f_opt returns None for all values, then None is returned by pick.

pick is like finding a value, and then calling Option::map on the result, but in a single operation.

my $data = Hash->new(
    1  => 'foo',
    2  => 'bar',
    10 => 'baz',
);

my $opt = $data->pick(sub($k,$v){ $k >= 10 ? Some [$k,$v] : None}) # Some [10 => 'baz']
my $opt = $data->pick(sub($k,$v){ $k  <  2 ? Some [$k,$v] : None}) # Some [ 1 => 'foo']
my $opt = $data->pick(sub($k,$v){ $k > 100 ? Some [$k,$v] : None}) # None
my $opt = $data->pick(sub($k,$v){ $k >   9 ? Some $k * 2  : None}) # Some 20

fold($hash, $state, $f_state) : $state

Iterates through every ($key,$value) of a Hash and passes $key,$value,$state to the function $f_state. $f_state then computes the next $state.

When all (key,value) pairs are iterated, returns the last $state.

my $money = Hash->new(
    Anne         => 100,
    Marie        => 50,
    Frankenstein => 250,
);

# 400
my $total_money = $money->fold(0, sub($name,$money,$state) {
    $state + $money;
});

# ['Anne', 'Marie', 'Frankenstein']  in any order
my $player_names = $money->fold(Array->new, sub($name,$money,$state) {
    $state->push($name);
    $state;
});

to_array($hash, $f_x) : $array

Iterates through the hash and passes $key,$value to the function $f_x. This function is then supposed to return $x that is used to build a new Array.

The newly created Array is an Sq Array.

my $points = Hash->new(
    Alice => 10,
    Frank => 5,
    Marie => 11,
);

my $array = $points->to_array(sub($k,$v) { $k }); # ["Frank", "Marie", "Alice"]

dump($hash, $inline=60, $depth=0) : $str

Recursivly traverses data-structures and creates a human readable dump from it.

$inline controls the amount of characters a data-structure is completely inlined into a single string without newlines. The higher the number the more compact your dump will be.

It currently has a bug as it also collaps whitespace in a string and it shouldn't do that. This could be avoided by setting $inline to 0. But consider that dumping in its current form is considered for debugging purposes, not for serializing data.

Currently it is not perfect. It only works with Perl Array/Hash and Sq Array/Hash and the Option type. Sq Array/Hash are just dumped as normal Perl Array/Hash. No other object is being dumped. It also does not dump any other object and has no configuration. Also doesn't detect cyclic data-structures. But for most practical data-structures it works good enough at the moment. Get's improved in the future.

printf "%s\n", $hash->dump;

dumpw($hash, $inline=60, $depth=0) : void

Same as dump but instead of returning the dump as a string, automatically prints it using warn.

$hash->dumpw;
warn $hash->dump, "\n";

SIDE-EFFECTS

lock($hash, @keys) : $hash

This locks the keys of a Hash. You can specify additional @keys to be locked. Locking keys means you are only allowed to read and write the allowed keys. Reading or writing any not allowed key will throw an exception.

This function just returns the hash itself again. So you can chain the lock method after creation of a new hash.

Mutation of values is still allowed.

my $person = Hash->new(
    name     => 'Alice',
    birthday => '1970-01-01',
)->lock("hair_color");

$person->{foo} = 1;        # throws exception
my $foo = $person->{foo};  # throws exception

$person->{name}       = 'Candy'; # okay
$person->{hair_color} = 'black'; # fine too

on($hash, %kfs) : void

Similar to change. It expects a list of key and function. It reads the value of the key and passes it to the function when the value is defined.

Any return value of the function is ignored. Usually the idea is todo some kind of side-effect with the value of the keys. But consider that when the value is some kind of mutable value you also can use it to change the value.

my $hash = Hash->new(
    name => 'Anne',
    tags => Array->new('red', 'hot'),
);

# It's like selecting the key 'tags' to do some work with it
$hash->on(tags => sub($tags) {
    $tags->iter(sub($tag) {
        printf "Tag = %s\n", $tag;
    });
});

# select tags an adds 'blue' to it
$hash->on(tags => sub($tags) {
    $tags->push('blue');
});

# Use 'push' as a shortcut for selecting an array and pushing values on it.
$hash->push(tags => 'blue');

Like change or withf it works with multiple keys.

$hash->on(
    key1 => sub($value1) { ... },
    key2 => sub($value2) { ... },
);

iter($hash, $f) : void

Iterates through all key,values of the hash and passes $key,$value to the function $f that then can do something with it.

$hash->iter(sub($k,$v) {
    printf "Key = %s, Value = %s\n", $k, $v;
});

foreach($hash, $f) : void

Same as iter.

$hash->foreach(sub($k,$v) {
    printf "Key = %s, Value = %s\n", $k, $v;
});

MUTATIONS

These METHODS do mutate the Hash.

set($hash, @kvs) : void

Allows you to add/set multiple keys at once. This is the mutable version of with.

my $hash = Hash->empty;

$hash->set(
    name   => 'Anne',
    age    => 20,
    points => 100,
);

change($hash, %kfs) : void

Similar to set but instead of just providing a value you pass in a function and that function gets the current value of the key returning the new value to be used. This is the mutable version of withf.

This function cannot add new keys as it expects a current value. When you pass in a key that does not exist on the hash, then the function will never be called.

my $hash = Hash->new(
    name   => 'Anne',
    age    => 20,
    points => 100,
);

$hash->change(
    name   => sub($name)   { $name . $name     },
    age    => sub($age)    { $age    + 1       },
    points => sub($points) { $points + 10      },
    what   => sub($what)   { Array::sum($what) },
);

# $hash is now
# {
#     name   => 'AnneAnne',
#     age    => 21,
#     points => 110,
# }

push($hash, $key, @values) : void

Pushes @values to the $key. When the $key does not exist then an Array will be created. When $key is not an Array then it will be transformed into an Array containing its current value as the first entry in the Array.

The Array will be turned into an Sq Array. Even when you start with a pure perl Array it gets the 'Array' blessing added.

my $h = Hash->new;
$h->push(foo => 1);
$h->push(foo => 2,3);
$h->push(bar => 1);
$h->push(bar => 2);

# $h
# { foo => [1,2,3], bar => [1,2] }

my $data = Hash->new(
    id   => 1,
    tags => 'one',
);
$data->push(tags => 'two');

# $data
# { id => 1, tags => ['one', 'two'] }

# tags is now an Sq Array and you can call methods on it
$data->change(tags => sub($array) { $array->join(',') });

# $data
# { id => 1, tags => 'one,two' }

delete($hash, @keys) : void

Delete keys from a Hash. Same as Perl built-in delete.

my $hash = Hash->new(foo => 1, bar => 2, baz => 3);

$hash->delete("foo", "bar");

# $hash
# { baz => 3 }