NAME
Seq - A lazy sequence implementation
SYNOPSIS
A sequence is a builder/generator for iterators. You use a sequence and describe operations on a sequence. A sequence can then be asked to return an iterator that do the actual work.
The idea is that you never use the iterators directly. You only use a sequence. The iterator behind it is an implementation detail.
The advantage is that you have a high-level API with map, filter, fold and so on. It combines all the functionality you see from map, grep, List::Util, ...
But it does it lazily instead of computing everything at once. Thus it can provide immidiat results (when possible) and/or use less memory. Sometimes even saving computation time. It can work with infinity sequences and really large inputs.
Everything that is an iterator can potentially used with this API. This includes file-handles, sockets, pipes and whatever you can think of.
Once you have defined a sequence. You can execute the sequence as often you want on whatever data you give it. From its usage it looks like an immutable iterator.
# always represents the range from 1 to 100.
my $range = Seq->range(1,100);
# prints numbers 1 to 100
$range->iter(sub($x) { say $x });
# prints numbers 1 to 100 again ...
$range->iter(sub($x) { say $x });
At the moment Documentation is lacking, but the source-code is well-documented including the test-files. Maybe you want to look at the test-files until I have written more documentation. The API is not fully stable at the moment.
use v5.36;
use Seq;
# Fibonacci Generator
my $fib =
Seq->concat(
Seq->wrap(1,1),
Seq->unfold([1,1], sub($state) {
my $next = $state->[0] + $state->[1];
return $next, [$state->[1],$next];
})
);
# prints: 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765
$fib->take(20)->iter(sub($x) {
say $x;
});
# Represents all possible combinations
# [[clubs => 7], [clubs => 8], [clubs => 9], ...]
my $cards =
Seq::cartesian(
Seq->wrap(qw/clubs spades hearts diamond/),
Seq->wrap(qw/7 8 9 10 B D K A/)
);
use Path::Tiny qw(path);
# get the maximum id from test-files so far
my $maximum_id =
Seq
# get all files from 't' folder
->wrap( path('t')->children )
# get basename of each file
->map( sub($x) { $x->basename })
# extract all numbers from test files. returns matches as array
->regex_match( qr/\A(\d+) .* \.t\z/xms, [1])
# get the first entry of each array (there is only one)
->fsts
# get the maximum number, or -1 if sequence is empty
->max(-1);
CONCEPT
Functions are divided into CONSTRUCTORS, METHODS, CONVERTERS.
CONSTRUCTORS
All constructor functions are directly called from the Seq
module. Like Seq->init( ... )
. The return a Seq
.
METHODS
All methods can be called like a method on a Seq
or still be called in a functional-style. Both styles are supported. In the documentation only the functional style how it is defined in Perl is shown.
$seq->length();
Seq::length($seq);
CONVERTERS
Calling style is the same as METHODS. They are called CONVERTERS because they usually return something different than a sequence again. For example $seq->map(...)
returns a new sequence, but $seq->max
will return a maximum value out of a sequence that can be potentially something different than a sequence.
IMPORTING / EXPORTING / LOADING
This module is automatically loaded when you load Sq
. The module is available under Seq
.
CALLING STYLE
Describe functional and chaining style here.
CONSTRUCTORS
This module uses functional-programming as the main paradigm. Functions are divided into constructors, methods and converters.
Constructor create a sequence. Methods operate on sequences and return another new sequence. Converter transforms a sequence to some other data-type.
Methods are called methods for convenience, but no object-orientation is involved. Perls OO capabilities are only used as a chaning mechanism.
Constructors must be called with the Package name. Functions that operate on Sequences can either be called as a method or directly from the Package.
my $range =
Seq
->wrap(1,2,3)
->append(Seq->wrap(4,5,6));
or
my $range =
Seq::append(
Seq->wrap(1,2,3),
Seq->wrap(4,5,6),
)
Seq->empty() : $seq
Returns an empty sequence. Useful as an initial state or as a starting point.
my $seq = Seq->empty->append( $another_seq );
Seq->range($start, $stop) : $seq
Returns a sequence from $start to $stop. Range can also be backwards. $start and $stop are inclusive.
my $seq = Seq->range(1, 5); # 1,2,3,4,5
my $seq = Seq->range(5, 1); # 5,4,3,2,1
my $seq = Seq->range(1, 1); # 1
Seq->range_step($start, $step, $stop) : $seq
Like Seq->range
but let's you specify a step. Both values are inclusive and also supports steps with floating-point. Consider floating-point inaccurassy.
my $seq = Seq->range(1,2,10) # [1,3,5,7,9]
my $seq = Seq->range(10,2,1) # [10,8,6,4,2]
my $seq = Seq->range(1,0.2,2) # [1,1.2,1.4,1.6,1.8,2]
my $seq = Seq->range(1,0.2,2) # [2,1.8,1.6,1.4,1.2,1]
Seq->new(...) : $seq
Just takes whatever you pass it to, and puts it in a sequence. This should be your primarily way to create a sequence with values.
my $seq = Seq->new(qw/Hello World/); # "Hello", "World"
my $seq = Seq->new(1 .. 10); # AVOID this, use Seq->range(1, 10) instead.
my $seq = Seq->new(@array);
Seq->wrap(...) : $seq
Same as Seq->new
.
my $seq = Seq->wrap(qw/Hello World/); # "Hello", "World"
my $seq = Seq->wrap(1 .. 10); # AVOID this, use Seq->range(1, 10) instead.
my $seq = Seq->wrap(@array);
Seq->concat(@sequences) : $seq
Takes multiple *Sequences* and returns a single flattened sequence.
# 0, 1, 2, 3, 4, 5, 5, 4, 3, 2, 1, 0
my $seq = Seq->concat(
Seq->new(0),
Seq->range(1, 5),
Seq->range(5, 1),
Seq->new(0),
);
Seq->always($x) : $seq
Returns an infinite sequence that always returns $x
as it's value.
my $seq = Seq->always(10);
my $array1 = $seq->take(5)->to_array; # [10, 10, 10, 10, 10]
my $array2 = $seq->take(3)->to_array; # [10, 10, 10]
Seq->replicate($count, $x) : $seq
Creates a sequence representing $x
$count
times.
# A sequence representing 100_000 times the value 1
my $seq = Seq->replicate(100_000, 1);
Seq->unfold($state, $f) : $seq
unfold
is like writing a loop to generate values. But instead that the whole loop is completely run to an end it just runs the function $f
with the current $state
to generate the next value in a sequence and which next $state
should be used for the next call. It runs as long you try to query data from it or the sequence indicates an end by returning undef
.
my $seq = Seq->unfold(0, sub($x) { # $x is 0 on first invocation
return $x, $x+1; # return $x, and uses $x+1 as the next state.
});
my $first100 = $seq->take(100); # represents a sequence containing 0-99
my $first500 = $seq->take(500); # represents a sequence containing 0-499
Seq->init($count, $f) : $seq
Generates a maximum of $count
items and passes the current index to the function $f
that performs the generation for that entry.
my $seq = Seq->init(10, sub($idx) { $idx }); # seq { 0 .. 9 }
my $seq = Seq->init(10, sub($idx) { $idx * 2 }); # seq { 0,2,4,6,8,10,12,14,16,18 }
Seq->from_array($array) : $seq
Generates a sequence from an array reference. This operation is very efficent as the array is not copied. When you created an array (in a function) but want to return it as a sequence this is the function you maybe wanna use. This way you also can return an array as an immutable sequence, at least as long the original array reference gets out of scope. Otherwise changing the original mutable array will yield new/updated values.
my $array = [1 .. 10];
my $seq = Seq->from_array($array);
my $sumA = $seq->sum; # 55
push @$array, 10;
my $sumB = $seq->sum; # 65
Seq->from_hash($hash, $f) : $seq
Turns a hash-ref into a sequence. When data is queried than every key,value pair is passed to $f
that then can return a value that is used as the sequence item. The hash-ref is not copied like in Seq->from_array
. This means changing the hashref and quering the sequence again will get updated values.
my $hash = {
foo => 1,
bar => 2,
baz => 3,
};
my $keys = Seq->from_hash($hash, sub($key,$value) { $key });
my $values = Seq->from_hash($hash, sub($key,$value) { $value });
my $ka = $keys->to_array; # ["foo", "bar", "baz"]
my $sum = $values->sum; # 6
$hash->{maz} = 4;
my $ka = $keys->to_array; # ["foo", "bar", "baz", "maz"]
my $sum = $values->sum; # 10
Seq->from_sub($f) : $seq
The foundation from that all sequences are created. You can turn any function that returns a function into a sequence. The inner function must return undef
to indicate the end of the sequence. As soon the inner function returns undef
you can be sure that the inner function is never called again and memory should be released.
For example an upto
function that just returns the value from 0 upto $x
could be implemented this way.
sub upto($x) {
return Seq->from_sub(sub {
# Here comes the Initial State of the iterator
my $current = 0;
return sub {
if ( $current <= $x ) {
return $current++;
}
return undef;
};
});
}
# A sequence from 0-100
my $seq = upto(100);
# [0, 2, 4, 6, 8]
my $first5even =
$seq
->filter(sub($x) { $x % 2 == 0 })
->take(5)
->to_array;
METHODS
Implemented, but not documented yet:
append, map, bind, flatten cartesian, join, merge, select*, choose, mapi, filter, take, skip, indexed, distinct, distinct_by, iter, do, rev
* will maybe change
copy($seq) : $seq
Copies a sequence, but because how sequence works it basically does nothing. It is just here for API compatibility with other modules like Array::copy or List::copy.
my $same = $seq->copy;
append($seqA, $seqB) : $seq
returns a new sequence where both sequences are appended into a single one. When you want to append multiple sequences it is usually better to use Seq->concat
instead.
my $new = $seq->append($other_seq);
my $upto10 = Seq->range(1,5)->append( Seq->range(6,10) ); # seq { 1 .. 10 }
map($seq, $f) : $seq
returns a new sequence where every value is mapped with the function $f
.
my $seq = Seq->range(1,10);
my $double = $seq->map(sub($x) { $x * 2 }); # seq { 2,4,6,8,10,12,14,16,18,20 }
my $squared = $seq->map(sub($x) { $x * $x }); # seq { 1,4,9,16,25,36,49,64,81,100 }
map2($seqA, $seqB, $f) : $seq
Goes through two sequences at once, and passes both values to $f
. $f
then generates the new value used in the sequence. Stops as soon one sequence finishes.
# [1+5, 2+4, 3+3, 4+2, 5+1]
my $additions =
Seq::map2(
Seq->new(1,2,3,4,5,6),
Seq->new(5,4,3,2,1),
sub($x,$y) { $x + $y });
# [1+5, 2+4, 3+3, 4+2, 5+1]
my $additions =
Seq->new(1,2,3,4,5,6)->map2(
Seq->new(5,4,3,2,1),
sub($x,$y) { $x + $y });
mapi($seq, $f) : $seq
Similar to map
but the function $f
additionally receives the current index of the item it processes.
# seq { [5,0], [6,1], [7,2], [8,3], [9,4], [10,5] }
my $seq =
Seq
->range(5,10)
->mapi(sub($x,$idx) { [$x,$idx] });
filter($seq, $predicate) : $seq
returns a new sequence with the elements filtered by $predicate
. A predicate function is a function that returns a boolish value. When it returns a truish value then the element is kept in the sequence. Similar zu perl built-in grep
for an array.
# contains only even numbers
my $seq = $seq->filter(sub($x) { $x % 2 == 0 });
choose($seq, $f_opt) : $seq
Similar to map
as it executes $f_opt
function on every element of the sequence. $f_opt
is supposed to return an optional value that indicates if that element should be picked or not. When it returns Some value it will use the value, and None values are skipped.
This function is a combination of map
followed by filter
in a single call.
# seq { 2,4,6,8,10,12,14,16,18,20 }
my $seq =
Seq
->range(1,10)
->map( sub($x) { $x * 2 })
->filter(sub($x) { $x % 2 == 0 });
# seq { 2,4,6,8,10,12,14,16,18,20 }
my $seq =
Seq
->range(1,10)
->choose(sub($x){
my $double = $x * 2;
return $double % 2 == 0 ? Some($double) : None;
});
bind($seq, $f) : $seq
Similar to map
as every value of the sequence is passed to function $f
. But $f
is supposed to return a new sequence again. Instead of returning a sequence of sequence it just returns them as a flattened sequence.
# seq { 0,1, 0,1,2, 0,1,2,3, 0,1,2,3,4, 0,1,2,3,4,5 }
my $nums =
Seq
->range(1,5)
->bind(sub($x) { Seq->range(0,$x) });
Think of bind
as a way to just iterate through every item of a sequence, but the function you provide to bind
returns a sequence again. But instead of getting a sequence of sequence you get just a single sequence flattened.
This operation is powerful as you can implement a lot of other functions just with bind
. This operation is also for example called then
in JavaScript/promises API. It allows you to chain operations. Like an async operation that returns an async again.
It is called bind
because it resembles binding a value. Consider the code
my $x = 10;
then you would bind the value 10
to the variable $x
. When you loop through an array.
for my $x ( @array ) {
...
}
Then you could say that you bind every value inside of @array
to $x
.
$seq->bind(sub($x) {
...
});
Here we also bind every value of a sequence to $x
. The binding is just reversed in the sense that instead of having something on the right and binding it to the variable on the left, we just bind it from left-to-right.
You could for example implement Seq::map
the following way. Even though this is not implemented this way because of performance reasons.
sub seq_map($seq, $f) {
$seq->bind(sub($x) {
Seq->new($f->($x));
});
}
flatten($seq_of_seq) : $seq
returns a new sequence that flattens a sequence of sequences into a single sequence. You typically can get a sequence of sequence when you use map
with a function that returns a sequence. Instead of calling flatten
on that result use bind
instead of map
.
# seq { seq {1,2,3}, seq{4,5,6}, seq{7,8,9} }
my $nums = Seq->new(
Seq->range(1,3),
Seq->range(4,6),
Seq->range(7,9),
);
# seq { 1,2,3,4,5,6,7,8,9 }
my $flatten = $nums->flatten;
flatten_array($seq_of_array) : $seq
returns a new sequence that flattens a sequence of arrays into a single sequence.
# seq { 1 .. 9 }
my $nums = Seq->new([1,2,3], [4,5,6], [7,8,9])->flatten;
cartesian($seqA, $seqB) : $seq_of_array
returns a new sequence containing the cartesian product of both sequences. The cartesian product is every item in $seqA
combined with every item in $seqB
.
# cartesian product of two arrays
my @cartesian;
for my $a ( @as ) {
for my $b ( @bs ) {
push @cartesian, [$a, $b];
}
}
# cartesian product of two sequences
$a->cartesian($b)->iter(sub($pair) {
my ($a, $b) = @$pair;
printf "A=%s B=%s\n", $a, $b;
});
take($seq, $amount) : $seq
returns a new sequence that only takes $amount
from its previous sequence.
# seq { 1 .. 10 }
my $seq = Seq->range(1,100)->take(10);
take_while($seq, $predicate) : $seq
returns a new sequence and picks every element from the sequence as long $predicate
returns a truish value.
# seq { 20,10,3,70 }
my $seq =
Seq
->new(20,10,3,70,105,40,200)
->take_while(sub($x) { $x < 100 ? 1 : 0 });
skip($seq, $amount) : $seq
returns a new sequence, but skips $amount
items.
# seq { 4,5,6,7,8,9,10 }
my $seq = Seq->range(1,10)->skip(3);
skip_while($seq, $predicate) : $seq
returns a new sequence by skipping all values at the begging as long $predicate
returns a truish value. After that, returns all values as-is.
# seq { 70,105,40,200 }
my $seq =
Seq
->new(20,10,3,70,105,40,200)
->skip_while(sub($x) { $x < 50 ? 1 : 0 });
indexed($seq) : $seq_of_array
returns a new sequence that puts the index and on element together. If you just need to process an item with the index you should use mapi
or iteri
instead.
# seq { [0,10], [1,11], [2,12], [3,13], [4,14], [5,15] }
my $seq = Seq->range(10,15)->indexed;
distinct($seq) : $seq
returns a new sequence where every element will only appear once. Internally a hash is used for seen elements. That means it only works properly when elements can be converted to a string. Otherwise use distinct_by
.
# seq { 1,2,3,4,5,6,7,10,8 }
my $seq =
Seq
->new(1,2,3,3,4,2,1,5,6,5,4,7,10,8)
->distinct;
distinct_by($seq, $f) : $seq
Same as distinct
but uses function $f
to generate the key for an item to be used as the seen key.
my $data = Seq->new(
{id => 1, name => "Foo"},
{id => 2, name => "Bar"},
{id => 3, name => "Baz"},
{id => 1, name => "Foo"},
);
my $seq = $data->distinct_by(sub($x) { $x->{id} });
# seq {
# {id => 1, name => "Foo"},
# {id => 2, name => "Bar"},
# {id => 3, name => "Baz"},
# }
fsts($seq_of_array) : $seq
returns a new sequence that only picks the first element of each array.
# seq { "Hearth","Hearth","Spades" }
my $strings =
Seq->new(
["Hearth", 1],
["Hearth", 2],
["Spades", 3],
)
->fsts;
fsts($seq_of_array) : $seq
returns a new sequence that only picks the second element of each array.
# seq { 1,2,3 }
my $nums =
Seq->new(
["Hearth", 1],
["Hearth", 2],
["Spades", 3],
)
->snds;
zip($seqA, $seqB) : $seq_of_array
Combines two sequences into a single sequence by always combining the next element of both sequences. Stops as soon one sequence finish.
# seq { ["foo",1], ["bar",1], ["baz",1] }
my $mapping = Seq->zip(
Seq->new(qw/foo bar baz/),
Seq->always(1),
);
rev($seq) : $seq
returns a new sequence that reverses the sequence. Consider that reversing is only possible when all elements of a sequence are iterated. Internally this function builds a stack from a sequence and than pops every element until done. Calling this method on a infinite sequence will probably hang/crash your program/computer.
# seq { 10,9,8,7,6,5,4,3,2,1 }
my $seq = Seq->range(1,10)->rev;
sort($seq, $comparer) : $seq
Sorts a sequence by using $comparer
function. Consider that sorting is only possible when all elements of a sequence are known. Thus this method must build an array from all elements of a sequence and than sorts it.
# seq { 1,2,5,8,10,20,30 }
my $sorted_nums =
Seq
->new(1,30,5,2,8,10,20)
->sort(sub($x,$y) { $x <=> $y });
sort_by($seq, $comparer, $get_key) : $seq
Does a Schwartzian Transformation by first passing every element to the function $get_key
that returns the key used in sorting. Than uses $comparer
to sort by the generated keys. Returns the original entries sorted by its keys.
# seq { "a","as","hello","between" }
my $seq =
Seq
->new("hello", "a", "between", "as")
->sort_by(
sub($x,$y){ $x <=> $y }, # comparer, sort by number
sub($x) { length $x }, # get_key that uses string length
);
group_fold
TODO
group_by
TODO
cache($seq) : $seq
runs a sequence at this point and builts an array. Then returns a new sequence from that array. This can be good when you have a long chain of transformations and at certain points you want to compute the values instead of recomputing it again and again. It's also a good candidate when the sequence operates on mutable data. Calling cache
still only makes sense when you want to use the final sequence and do additional operations on it. Otherwise you just can call to_array
.
my $evens =
Seq
->range(1,10_000)
->filter(sub($x) { $x % 2 == 0 })
->cache;
# $evens is only computed once, but also uses more memory.
my $doubled = $evens->map(sub($x) { $x * 2 });
my $squared = $evens->map(sub($x) { $x * $x });
regex_match($seq_of_strings, $regex) : $seq_of_array
Matches a $regex
against every element of a sequence that is supposed to be a string. When a regex matches against the string it extracts all captures into an array.
my $lines = Seq->new(
'2023-11-25T15:10:00',
'2023-11-20T10:05:29',
'xxxx-xx-xxT00:00:00',
'1900-01-01T00:00:01',
'12345678901234567890',
);
my $matches = $lines->regex_match(qr/
\A
(\d\d\d\d) - (\d\d) - (\d\d) # Date
T # T
(\d\d) : (\d\d) : (\d\d) # Time
\z/xms
)->to_array;
# $matches
# [
# [qw/2023 11 25 15 10 00/],
# [qw/2023 11 20 10 05 29/],
# [qw/1900 01 01 00 00 01/],
# ],
windowed($seq, $window_size) : $seq_of_array
TODO
intersperse($seq, $separator) : $seq
returns a new sequence with $sepator
injected between every element.
# seq { 1,'+',2,'+',3,'+',4,'+',5 }
my $seq = Seq->range(1,5)->intersperse('+');
infinity($seq) : $seq
Repreats a sequence up to infinity.
# seq { 1,2,3,1,2,3,1,2,3,1,2,3,1, ... }
my $seq = Seq->range(1,3)->infinity;
repeat($seq, $cont) : $seq
Repeats a whole sequence a given amount.
# seq { 1,2,3, 1,2,3, 1,2,3 }
my $seq = Seq->range(1,3)->repeat(3);
CONVERTERS
Converts are normal methods that also can be called in a functional-style. They are called Converters because they usually transform a Sequence into something different than a sequence.
Converters usually needs to iterate some or all values of a sequence to produce its output. Calling some of them on an infinite sequence can maybe cause unwanted problems.
to_array($seq, $count=undef) : $array
Converts the whole sequence into an array. This causes the sequence to compute things. Optionally you can pass in a $count
of how many max items you want to take from the sequence. When no $count
or undef
or not a number is passed, then all items from the sequence are taken.
When you just want to iterate through all items, instead of calling to_array
and then iterate through that array just use iter
instead.
# those are the same
$seq->take(10)->to_array == $seq->to_array(10);
my $array = $seq->to_array;
my $array = $seq->to_array(10);
join($seq, $separator) : $string
Concatenates all elements of a sequence to a string with $separator
.
my $str = Seq->new("foo", "bar)->join("-"); # "foo-bar"
first($seq) : $opt
Returns first element of seq as an Option. Returns None when the sequence is empty.
my $opt = $seq->first;
my $opt = Seq->range(1,10)->first; # Some(1)
my $opt = Seq->empty ->first; # None
my $x = Seq->empty ->first->or(0) # 0
last($seq) : $opt
Returns last element of seq as an Option. Returns None when the sequence is empty.
my $opt = $seq->last;
my $opt = Seq->range(1,10)->last; # Some(10)
my $opt = Seq->empty ->last; # None
my $x = Seq->empty ->last->or(0) # 0
min($seq) : $opt_number
Returns the smallest number in a sequence as an optional. When the sequence is empty a None is returned.
min
uses numeric <
operator to compare items. So any object that overloads <
will also work.
my $opt = Seq->range(100,200)->min; # Some(100)
my $opt = Seq->empty->min; # None
min_by($seq_x, $f_number) : $opt_x
Iterates through a sequence to find the minimum element $x
. While iterating my $num = $f_number->($x)
is called to get the $num
that is used for comparision. The $x
with the minimum $num
is returned as a result.
Returns an optional because in the case of an empty sequence there can be no min value and None is being returned.
min_by
uses numeric <
operator to compare items. So any object that overloads <
will also work.
my $data = Seq->new(
{ id => 1, name => 'A' },
{ id => 2, name => 'B' },
{ id => 3, name => 'C' },
);
my $opt = $data->min_by(key 'id'); # Some({ id => 1, name => 'A' })
The function key
is automatically loaded/imported by loading Sq
. key
generates a function that selects a specific entry from a hash.
max($seq) : $opt_number
Returns the maximum number in a sequence as an optional. When the sequence is empty a None is returned.
max
uses numeric >
operator to compare items. So any object that overloads >
will also work.
my $opt = Seq->range(100,200)->max; # Some(200)
my $opt = Seq->empty->max; # None
max_by($seq_x, $f_number) : $opt_x
Iterates through a sequence to find the maximum element $x
. While iterating my $num = $f_number->($x)
is called to get the $num
that is used for comparision. The $x
with the maximum $num
is returned as a result.
Returns an optional because in the case of an empty sequence there can be no max value and None is being returned.
max_by
uses numeric >
operator to compare items. So any object that overloads >
will also work.
my $data = Seq->new(
{ id => 1, name => 'A' },
{ id => 2, name => 'B' },
{ id => 3, name => 'C' },
);
my $opt = $data->max_by(key 'id'); # Some({ id => 3, name => 'C' })
The function key
is automatically loaded/imported by loading Sq
. key
generates a function that selects a specific entry from a hash.
min_str($seq) : $opt
The same as min
but uses string comparison lt
to compare items. Any object that overloads lt
will also work.
my $opt = Seq->new(qw/c b a/)->min_str; # Some('a')
my $opt = Seq->empty->min_str; # None
min_str_by($seq_x, $f_str) : $opt_x
Same as min_by
but uses string comparison lt
to compare items. Any object that overloads lt
will also work.
my $data = Seq->new(
{ id => 1, name => 'A' },
{ id => 2, name => 'B' },
{ id => 3, name => 'C' },
);
my $opt = $data->min_str_by(key 'name'); # Some({ id => 1, name => 'A' })
max_str($seq) : $opt
Same as max
but uses string comparison gt
to compare items.
my $opt = Seq->new(qw/c b a/)->max_str; # Some('c')
my $opt = Seq->empty->max_str; # None
max_str_by($seq_x, $f_str) : $opt_x
Same as max_by
but uses string comparison gt
to compare items.
my $data = Seq->new(
{ id => 1, name => 'A' },
{ id => 2, name => 'B' },
{ id => 3, name => 'C' },
);
my $opt = $data->max_str_by(key 'name'); # Some({ id => 3, name => 'C' })
as_hash($seq) : $hash
Turns a sequence into a Sq Hash by expecting that the sequence contains key/value pairs. Similar as writing my %hash = @list
in Perl.
# { foo => 1, bar => 2 }
my $hash = Seq->new(qw/foo 1 bar 2/)->as_hash;
to_hash($seq, $mapper) : $hash
Calls $mapper
function for every element in the sequence. Expects that $mapper
returns the key and value used to build the final Hash.
# {"Hello" => 5, "World" => 5, "One" => 3, "Two" => 3}
my $hash = Seq->new(qw/Hello World One Two/)->to_hash(sub($key) {
$key => length $key
});
expand($seq) : ($x,$x,$x,$x,...)
Similar to to_array
as that it starts to turn a sequence into an array. But instead of returning it as a blessed Sq Array it returns it as a Perl List in List Context.
Only use this function if you need to pass a list of values to some other function, if you just want to iterate every element use iter
, iteri
instead. Prefer to_array
over expand
. When you want to turn a Sequence into a Hash, use as_hash
or to_hash
instead.
my @array = $seq->expand;
my $array = $seq->to_array();
for my $x ( $seq->expand ) {
...
}
# Better
$seq->iter(sub($x) {
...
});
fold($seq, $state, $f_state) : $state
fold is like a foreach-loop. You iterate through all items generating a new State. The $f_state
is passed one element from the sequence and the latest $state
. $f_state
then returns the next $state
that should be used. Once all elements of the sequence are processed the latest $state
is returned.
my $seq = Seq->range(1,1000);
my $sum = $seq->fold(0, sub($x,$state) {
$state + $x
});
Above you can see that 0
is passed as the starting State. On the first iteration the subroutine gets 0
as $state
passed and 1
as the first element of the sequence. It calculates 0 + 1
and returns 1
as the next State.
Then for the next iteration 2
for $x
and 1
for $state
is used. It returns 1 + 2
as the next $state
and continues until all elements of the sequence are iterated. The last calculated $state
is returned as the final result of fold
.
You can compare it to a foreach-loop because what you define as $state
is usually what you would define outside of a loop. Consider the following example with an Array as comparison to understand what fold
does.
my @seq = (1 .. 1000);
my $state = 0;
for my $x ( @seq ) {
$state = $state + $x;
}
return $state;
fold_mut($seq, $state, $f_state) : $state
fold_mut
is an optimization of fold
. fold
works by computing a new $state
on every call. Every time the $f_state
function is executed the result of this call is assigned to $state
. In a fully immutable programming environment this is usually fine and the only way to create immutable-data.
Perl on the other hand is not an immutable language, don't provide immutable data-types by default and because of the lack of a JIT or a compiler with a dozens of compiler optimizations is in general slower. Because of that it is sometimes helpful to operate on mutable data as an optimization.
fold_mut
works the same as fold
, but it expects that $state
is mutated during every iteration. After all operations are finished, $state
is still returned.
# this transforms a sequence into an array
my $array = Seq->range(1,1000)->fold_mut([], sub($x,$array) {
push @$array, $x;
return;
# the explicit return is not needed.It just shows that the return
# value of this lambda is not used.
});
reduce($seq, $default, $reducer) : $return_of_reducer
In a dynamic-typed language like Perl it is basically the same as fold
. The only difference is that no $state
is explicitly passed and it just begins calling $reducer
with the first two elements of the sequence.
When the sequence only has one element than this element is immediately returned without that $reducer
is ever called. It is compareable when you have an empty sequence and use fold
, then $state
is immediately returned.
But what should be returned when the sequence is empty? Because of this reduce
expects an argument $default
. Other possibilites would be to throw an exception, return an option or something else.
I highly encourage to better use fold
or fold_mut
instead of reduce
.
my $sum = Seq->range(1,1000)->reduce(0, sub($x,$y) { $x + $y });
my $sum = Seq->range(1,1000)->fold( 0, sub($state,$x) { $state + $x });
dump($seq, $inline=60, $depth=0) : $str
Recursivly traverses data-structures and creates a human readable dump from it. Only the first 20 entries are shown.
$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", $opt->dump;
dumpw($seq, $inline=60, $depth=0) : void
Same as dump
but instead of returning the dump as a string, automatically prints it using warn
.
$opt->dumpw;
warn $opt->dump, "\n";
SIDE-EFFECTS
iter($seq, $f) : void
Iterates through all elements of a sequence and calling $f
for every element. As iter
returns nothing it is just used for doing side-effects. Or maybe when you want to iterate through a sequence and build other data. But consider using fold
or fold_mut
instead. Or any other of the CONVERTER methods.
Seq->range(1,1000)->iter(sub($x) {
say $x;
});
foreach($seq, $f) : void
Same as iter
.
Seq->range(1,1000)->foreach(sub($x) {
say $x;
});
iteri($seq, $f) : void
Same as iter
but $f
additionally gets an index passed.
Seq->range(10,15)->iteri(sub($x,$idx) {
printf "%d: %d\n", $idx, $x;
});
# prints
# 0: 10
# 1: 11
# 2: 12
# 3: 13
# 4: 14
# 5: 15
foreachi($seq, $f) : void
Same as iteri
.
Seq->range(10,15)->foreachi(sub($x,$idx) {
printf "%d: %d\n", $idx, $x;
});
# prints
# 0: 10
# 1: 11
# 2: 12
# 3: 13
# 4: 14
# 5: 15
do($seq, $f) : $seq
A functions like iter
starts to iterate all elements of a sequence. do
does not. It just allows that a function is called before each element is returned. This is usually useful in debugging. You can easily intercept anywhere between in a sequence.
# [4,16,36,64,100]
my $array =
Seq
->range(1, 1000)
->filter(sub($x) { $x % 2 == 0 })
->do( sub($x) { printf "%d\n", $x })
->map( sub($x) { $x * $x })
->to_array(5);
The above code first filters out even numbers, then calculates the square of each number. It only returns the first 5 numbers as an array. But besides doing that, this code will print.
2
4
6
8
10
do
only executes for those elements you ask for. If you don't force evaluation somehow through any CONVERTER method, then nothing will happen. Use iter
when you want to force evaluation.
doi($seq, $f) : $seq
Same as do
but additionally passes an index to the function $f
.
# [4,16,36,64,100]
my $array =
Seq
->range(1, 1000)
->filter(sub($x) { $x % 2 == 0 })
->doi( sub($x,$idx) { printf "$d: %d\n", $idx, $x })
->map( sub($x) { $x * $x })
->to_array(5);
Besides returning the array, the code will also print.
0: 2
1: 4
2: 6
3: 8
4: 10
Github
Development project is on Github. https://github.com/DavidRaab/Seq
AUTHOR
David Raab, <davidraab83 at gmail.com>
LICENSE AND COPYRIGHT
This software is Copyright (c) 2023 by David Raab.
This is free software, licensed under:
The MIT (X11) License