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 }