### ----------------------------------------------------
### -- List::MoreUtils
### -- Licenses: Artistic-2.0
### -- Authors: Elizabeth Mattijsen
### -- File: lib/List/MoreUtils.rakumod
### ----------------------------------------------------
### -- Chunk 1 of 2
module List::MoreUtils {
    our sub any(&code, @values --> Bool:D) is export(:all) {
        return True if code($_) for @values;
        False
    }
    our sub any_u(&code, @values --> Bool:D) is export(:all) {
        @values ?? any(&code,@values) !! Nil
    }

    our sub all(&code, @values --> Bool:D) is export(:all) {
        return False unless code($_) for @values;
        True
    }
    our sub all_u(&code, @values --> Bool:D) is export(:all) {
        @values ?? all(&code,@values) !! Nil
    }

    our sub none(&code, @values --> Bool:D) is export(:all) {
        return False if code($_) for @values;
        True
    }
    our sub none_u(&code, @values --> Bool:D) is export(:all) {
        @values ?? none(&code,@values) !! Nil
    }

    our sub notall(&code, @values --> Bool:D) is export(:all) {
        return True unless code($_) for @values;
        False
    }
    our sub notall_u(&code, @values --> Bool:D) is export(:all) {
        @values ?? notall(&code,@values) !! Nil
    }

    our sub one(&code, @values --> Bool:D) is export(:all) {
        my Int $seen = 0;
        return False if code($_) && $seen++ for @values;
        so $seen
    }
    our sub one_u(&code, @values --> Bool:D) is export(:all) {
        @values ?? one(&code,@values) !! Nil
    }

    our proto sub apply(|) is export(:all) {*}
    multi sub apply(Scalar:U, &code, @values) {
        _apply(&code, @values).tail
    }
    multi sub apply(&code, @values) {
        _apply(&code, @values).List
    }
    sub _apply(&code, @values) {
        @values.map( -> $_ is copy { code($_); $_ } )
    }

    our proto sub insert_after(|) is export(:all) {*}
    multi sub insert_after(&code, \insertee, @values --> Nil) {
        for @values.kv -> $key, $value {
            if code($value) {
                @values.splice($key + 1, 0, insertee);
                return
            }
        }
    }
    multi sub insert_after(&code, Pair:D $pair) {
        insert_after(&code, $pair.key, $pair.value)
    }
    multi sub insert_after(&code, *%_ --> Nil) {
        %_.elems > 1
          ?? die "Can only specify one named parameter to 'insert_after'"
          !! insert_after(&code, .key, .value) with %_.head
    }

    our proto sub insert_after_string(|) is export(:all) {*}
    multi sub insert_after_string(Str() $string, \insertee, @values --> Nil) {
        for @values.kv -> $key, $value {
            if $value.defined && $value eq $string {
                @values.splice($key + 1, 0, insertee);
                return
            }
        }
    }
    multi sub insert_after_string(Str() $string, Pair:D $pair) {
        insert_after_string($string, $pair.key, $pair.value)
    }
    multi sub insert_after_string(Str() $string , *%_ --> Nil) {
        %_.elems > 1
          ?? die "Can only specify one named parameter to 'insert_after_string'"
          !! insert_after_string($string, .key, .value) with %_.head
    }

    our sub pairwise(&code, @a, @b) is export(:all) {
        my $elems = @a.elems max @b.elems;
        my @pairwise;
        @pairwise.append(code(@a.AT-POS($_), @b.AT-POS($_)).Slip) for ^$elems;
        @pairwise
    }

    our sub mesh(**@arrays, :$DONTSLIP) is export(:all) {
        my @iterators = @arrays.map: *.iterator;
        my $nr_values = +@iterators;
        my @mesh;

        loop {
            my $seen = $nr_values;
            my @values = @iterators.map: {
                my $pulled := .pull-one;
                if $pulled =:= IterationEnd {
                    --$seen;
                    Nil
                }
                else {
                    $pulled
                }
            }
            last unless $seen;
            @mesh.append( $DONTSLIP ?? @values !! @values.Slip )
        }
        @mesh
    }
    our constant &zip is export(:all) = &mesh;

    our sub zip6(|c) is export(:all) { mesh(|c, :DONTSLIP) }
    our constant &zip_unflatten is export(:all) = &zip6;

    our sub listcmp(**@arrays --> Hash:D) is export(:all) {
        my %result;
        for @arrays.kv -> $index, @array {
            my %seen;
            for @array -> \value {
                if value.defined {
                    with %result{value} {
                        .push($index) unless %seen{value}++;
                    }
                    else {
                        %result.BIND-KEY(value,[$index]);
                    }
                }
            }
        }
        %result
    }

    our sub arrayify(**@values) is export(:all) {
        my @arrayify;
        multi sub flatten(@values) { flatten($_) for @values }
        multi sub flatten(\value)  { @arrayify.push(value) }

        flatten($_) for @values;
        @arrayify
    }

    our proto sub uniq(|) is export(:all) {*}
    multi sub uniq(Scalar:U, @values) { _count_uniq(@values) }
    multi sub uniq(@values) { _uniq(@values) }
    sub _count_uniq(@values) {
        my %seen;
        my Int $unique = 0;
        ++$unique unless %seen{.defined ?? .Str !! .^name}++ for @values;
        $unique
    }
    sub _uniq(@values) {
        my %seen;
        my @uniq;
        @uniq.push($_) unless %seen{.defined ?? .Str !! .^name}++ for @values;
        @uniq
    }
    our constant &distinct is export(:all) = &uniq;

    our proto sub singleton(|) is export(:all) {*}
    multi sub singleton(Scalar:U, @values) { _singleton(@values).elems }
    multi sub singleton(@values) { _singleton(@values).List }
    sub _singleton(@values is copy) {
        my %once;
        my %duplicates;

        for @values.kv -> $index, $_ {
            my $key = .defined ?? .Str !! .^name;
            if %duplicates{$key} {
                @values[$index]:delete;
            }
            elsif %once{$key}:exists {
                @values[%once{$key}:delete]:delete;
                @values[$index]:delete;
                %duplicates{$key} = 1;
            }
            else {
                %once{$key} = $index
            }
        }
        @values[]:v
    }

    our proto sub duplicates(|) is export(:all) {*}
    multi sub duplicates(Scalar:U, @values) { _count_duplicates(@values) }
    multi sub duplicates(@values) { _duplicates(@values) }
    sub _count_duplicates(@values) {
        my %seen;
        my Int $duplicates = 0;
        ++$duplicates if %seen{.defined ?? .Str !! .^name}++ == 1 for @values;
        $duplicates
    }
    sub _duplicates(@values) {
        my %seen;
        my @duplicates;
        @duplicates.push($_) if %seen{.defined ?? .Str !! .^name}++ == 1
          for @values;
        @duplicates
    }

    our proto sub frequency(|) is export(:all) {*}
    multi sub frequency(Scalar:U, @values) { _frequency(@values).elems }
    multi sub frequency(@values) { _frequency(@values).kv.List }
    sub _frequency(@values) {
        my %seen;
        %seen{.defined ?? .Str !! .^name}++ for @values;
        %seen
    }

    our proto sub occurrences(|) is export(:all) {*}
    multi sub occurrences(Scalar:U, @values) { occurrences(@values).elems }
    multi sub occurrences(@values) {
        my @occurrences;
        @occurrences[.value].push(.key) for @values.Bag.pairs;
        @occurrences
    }

    our proto sub mode(|) is export(:all) {*}
    multi sub mode(Scalar:U, @values) { @values.Bag.values.max }
    multi sub mode(@values) {
        my $seen = @values.Bag;
        my $max  = $seen.values.max;
        my @mode = $seen.map: { .key if .value == $max };
        @mode.unshift($max)
    }

    our sub after(&code, @values) is export(:all) {
        my $found = False;
        @values.toggle( { $found || code($_) && $found++ }, :off ).List
    }

    our sub after_incl(&code, @values) is export(:all) {
        @values.toggle( &code, :off ).List
    }

    our sub before(&code, @values) is export(:all) {
        @values.toggle( { !code($_) } ).List
    }

    our sub before_incl(&code, @values) is export(:all) {
        my $looking = True;
        @values.toggle( { $looking && !code($_) || $looking-- } ).List
    }

    our sub part(&code, @values) is export(:all) {
        my Array @part;
        for @values {
            my $index = code($_);
            @part[ $index < 0 ?? @part + $index !! $index].push($_)
        }
        @part
    }

    our sub samples(Int() $count, @values) is export(:all) {
        @values.pick($count).List
    }

    our sub natatime($n, @values) is export(:all) {
        my $iterator := @values.rotor($n, :partial).iterator;
        return {
            (my $pulled := $iterator.pull-one) =:= IterationEnd
              ?? ()
              !! $pulled
        }
    }

    our sub firstval(&code, @values) is export(:all) {
        @values.first: &code
    }
    our constant &first_value is export(:all) = &firstval;

    our sub onlyval(&code, @values) is export(:all) {
        my $iterator := @values.grep(&code).iterator;
        my $onlyval := $iterator.pull-one;
        $onlyval =:= IterationEnd
          ?? Nil
          !! $iterator.pull-one =:= IterationEnd
            ?? $onlyval
            !! Nil
    }
    our constant &only_value is export(:all) = &onlyval;

    our sub lastval(&code, @values) is export(:all) {
        @values.first(&code, :end)
    }
    our constant &last_value is export(:all) = &lastval;

    our sub firstres(&code, @values) is export(:all) {
        my $firstres :=
          @values.map({ if code($_) -> \val { val } }).iterator.pull-one;
        $firstres =:= IterationEnd ?? Nil !! $firstres
    }
    our constant &first_result is export(:all) = &firstres;

    our sub onlyres(&code, @values) is export(:all) {
        my $iterator := @values.map({ if code($_) -> \val { val } }).iterator;
        my $onlyres := $iterator.pull-one;

        $onlyres =:= IterationEnd
          ?? Nil
          !! $iterator.pull-one =:= IterationEnd
            ?? $onlyres
            !! Nil
    }
    our constant &only_result is export(:all) = &onlyres;

    our sub lastres(&code, @values) is export(:all) {
        @values.map({ if code($_) -> \val { val } }).tail
    }
    our constant &last_result is export(:all) = &lastres;

    our sub indexes(&code, @values) is export(:all) {
        @values.grep( &code, :k ).List
    }

    our sub firstidx(&code, @values) is export(:all) {
        @values.first( &code, :k ) // -1
    }
    our constant &first_index is export(:all) = &firstidx;

    our sub onlyidx(&code, @values) is export(:all) {
        my $iterator := @values.grep( &code, :k ).iterator;
        my $onlyidx := $iterator.pull-one;
        $onlyidx =:= IterationEnd
          ?? -1
          !! $iterator.pull-one =:= IterationEnd
            ?? $onlyidx
            !! -1
    }
    our constant &only_index is export(:all) = &onlyidx;

    our sub lastidx(&code, @values) is export(:all) {
        @values.first( &code, :k, :end ) // -1
    }
    our constant &last_index is export(:all) = &lastidx;

    our sub sort_by(&code, @values) is export(:all) {
        @values.sort( { ~code($_) } ).List
    }

    our sub nsort_by(&code, @values) is export(:all) {
        @values.sort( { +code($_) } ).List
    }

    our sub qsort(&code, @values) is export(:all) {
        (@values .= sort( &code )).List
    }

    our sub true(&code, @values) is export(:all) {
        my $true = 0;
        ++$true if code($_) for @values;
        $true
    }

    our sub false(&code, @values) is export(:all) {
        my $false = 0;
        ++$false unless code($_) for @values;
        $false
    }

    our sub each_array(**@arrays) is export(:all) {
        each_arrayref(@arrays)
    }

    our sub each_arrayref(@arrays) is export(:all) {
        my $elems = @arrays>>.elems.max;
        my $index = -1;

        return {
            if $_ && $_ eq "index" {
                $index
            }
            elsif ++$index < $elems {
                @arrays.map( { $_[$index] } ).List
            }
            else {
                ()
            }
        }
    }

    our sub minmax(@values) is export(:all) {
        @values
          ?? ((.min,.max) with @values.minmax)
          !! ()
    }
    our constant &minmaxstr is export(:all) = &minmax;

    sub REDUCE($result is copy, &code, @values) {
        $result = code($result,$_) for @values;
        $result
    }
    our sub reduce_0(&code,@values) is export(:all) {
        REDUCE( 0, &code, @values )
    }
    our sub reduce_1(&code,@values) is export(:all) {
        REDUCE( 1, &code, @values )
    }
    our sub reduce_u(&code,@values) is export(:all) {
        REDUCE( Any, &code, @values )
    }

    our proto sub bsearch(|) is export(:all) {*}
    multi sub bsearch(Scalar:U, &code, @values) {
        _bsearch(&code, @values, False, True)
    }
    multi sub bsearch(&code, @values) {
        _bsearch(&code, @values, False)
    }
    sub _bsearch(&code, @values, $index, $scalar?) {
        my $elems = @values.elems;
        my $i = 0;
        my $j = $elems;

        until $i > $j {
            my $k = ($i + $j) div 2;
            if $k >= $elems {
                return $index
                  ?? -1
                  !! $scalar ?? False !! []
            }

            if code(@values[$k]) -> $rc {
                $rc < 0
                  ?? ($i = $k + 1)
                  !! ($j = $k - 1)
            }
            else {
                return $index
                  ?? $k
                  !! $scalar ?? True !! [@values[$k]]
            }
        }
        $index
          ?? -1
          !! $scalar ?? False !! []
    }
    our sub bsearchidx(&code, @values) is export(:all) {
        _bsearch(&code, @values, True)
    }
    our constant &bsearch_index is export(:all) = &bsearchidx;

    our sub lower_bound(&code,@values) is export(:all) {
        my $count = +@values;
        my $lower = 0;

        while $count > 0 {
            my $step = $count +> 1;

            if code(@values[$lower + $step]) < 0 {
                $lower += $step + 1;
                $count -= $step + 1;
            }
            else {
                $count = $step;
            }
        }
        $lower
    }

    our sub upper_bound(&code,@values) is export(:all) {
        my $count = +@values;
        my $upper = 0;

        while $count > 0 {
            my $step = $count +> 1;

            if code(@values[$upper + $step]) <= 0 {
                $upper += $step + 1;
                $count -= $step + 1;
            }
            else {
                $count = $step;
            }
        }
        $upper
    }

    our sub equal_range(&code,@values) is export(:all) {
        ( lower_bound(&code,@values), upper_bound(&code,@values) )
    }

    our sub binsert(&code,\item,@values) is export(:all) {
        my $lb = lower_bound(&code,@values);
        @values.splice($lb, 0, item);
        $lb
    }
    our constant &bsearch_insert is export(:all) = &binsert;

    our sub bremove(&code,@values) is export(:all) {
        my $lb = lower_bound(&code,@values);
        $lb == @values ?? Nil !! @values.splice($lb, 1)
    }
    our constant &bsearch_remove is export(:all) = &bremove;
}

sub EXPORT(*@args, *%_) {

    if @args {
        my $imports := Map.new( |(EXPORT::all::{ @args.map: '&' ~ * }:p) );
        if $imports != @args {
            die "List::MoreUtils doesn't know how to export: "
              ~ @args.grep( { !$imports{$_} } ).join(', ')
        }
        $imports
    }
    else {
        Map.new
    }
}

=begin pod

=head1 NAME

Raku port of Perl's List::MoreUtils module 0.430

=head1 SYNOPSIS

    # import specific functions
    use List::MoreUtils <any uniq>;

    if any { /foo/ }, uniq @has_duplicates {
        # do stuff
    }

    # import everything
    use List::MoreUtils ':all';

=head1 DESCRIPTION

This module tries to mimic the behaviour of Perl's C<List::MoreUtils> module
as closely as possible in the Raku Programming Language.

List::MoreUtils provides some trivial but commonly needed functionality on
lists which is not going to go into C<List::Util>.

=head1 EXPORTS

Nothing by default. To import all of this module's symbols use the C<:all>
tag. Otherwise functions can be imported by name as usual:

    use List::MoreUtils :all;

    use List::MoreUtils <any firstidx>;

=head1 Porting Caveats

Raku does not have the concept of C<scalar> and C<list> context.  Usually,
the effect of a scalar context can be achieved by prefixing C<+> to the
result, which would effectively return the number of elements in the result,
which usually is the same as the scalar context of Perl of these functions.

Raku does not have a magic C<$a> and C<$b>.  But they can be made to exist
by specifying the correct signature to blocks, specifically "-> $a, $b".
These have been used in all examples that needed them.  Just using the
signature auto-generating C<$^a> and C<$^b> would be more Raku like.  But
since we want to keep the documentation as close to the original as possible,
it was decided to specifically specify the "-> $a, $b" signatures.

Many functions take a C<&code> parameter of a C<Block> to be called by the
function.  Many of these assume B<$_> will be set.  In Raku, this happens
automagically if you create a block without a definite or implicit signature:

  say { $_ == 4 }.signature;   # (;; $_? is raw)

which indicates the Block takes an optional parameter that will be aliased
as C<$_> inside the Block.

Raku also doesn't have a single C<undef> value, but instead has
C<Type Objects>, which could be considered undef values, but with a type
annotation.  In this module, C<Nil> (a special value denoting the absence
of a value where there should have been one) is used instead of C<undef>.

Also note there are no special parsing rules with regards to blocks in Raku.
So a comma is B<always> required after having specified a block.

The following functions are actually built-ins in Raku.

  any all none minmax uniq zip

They mostly provide the same or similar semantics, but there may be subtle
differences, so it was decided to not just use the built-ins.  If these
functions are imported from this library in a scope, they will used instead
of the Raku builtins.  The easiest way to use both the functions of this
library and the Raku builtins in the same scope, is to use the method syntax
for the Raku versions.

    my @a = 42,5,2,98792,88;
    {  # Note: imports in Raku are always lexically scoped
        use List::Util <minmax>;
        say minmax @a;  # Ported Perl version
        say @a.minmax;  # Raku version
    }
    say minmax @a;  # Raku version again

Many functions returns either C<True> or C<False>.  These are C<Bool>ean
objects in Raku, rather than just C<0> or C<1>.  However, if you use
a Boolean value in a numeric context, they are silently coerced to 0 and 1.
So you can still use them in numeric calculations as if they are 0 and 1.

Some functions return something different in scalar context than in list
context.  Raku doesn't have those concepts.  Functions that are supposed
to return something different in scalar context also the C<Scalar> type as
the first positional parameter to indicate the result like the result of a
scalar context, is required. It will be noted with the function in question
if that feature is available.

=head1 FUNCTIONS

=head2 Junctions

=head3 I<Treatment of an empty list>

There are two schools of thought for how to evaluate a junction on an
empty list:

=item Reduction to an identity (boolean)

=item Result is undefined (three-valued)

In the first case, the result of the junction applied to the empty list is
determined by a mathematical reduction to an identity depending on whether
the underlying comparison is "or" or "and".  Conceptually:

                    "any are true"      "all are true"
                    --------------      --------------
    2 elements:     A || B || 0         A && B && 1
    1 element:      A || 0              A && 1
    0 elements:     0                   1

In the second case, three-value logic is desired, in which a junction
applied to an empty list returns C<Nil> rather than C<True> or C<False>.

Junctions with a C<_u> suffix implement three-valued logic.  Those
without are boolean.

=head3 all BLOCK, LIST

=head3 all_u BLOCK, LIST

Returns True if all items in LIST meet the criterion given through
BLOCK. Passes each element in LIST to the BLOCK in turn:

    say "All values are non-negative"
      if all { $_ >= 0 }, ($x, $y, $z);

For an empty LIST, C<all> returns True (i.e. no values failed the condition)
and C<all_u> returns C<Nil>.

Thus, C<< all_u(@list) >> is equivalent to C<< @list ?? all(@list) !! Nil >>.

B<Note>: because Raku treats C<Nil> as false, you must check the return value
of C<all_u> with C<defined> or you will get the opposite result of what you
expect.

=head4 Idiomatic Raku ways

    say "All values are non-negative"
      if $x & $y & $z >= 0;

    say "All values are non-negative"
      if all(@list) >= 0;

=head3 any BLOCK, LIST

=head3 any_u BLOCK, LIST

Returns True if any item in LIST meets the criterion given through
BLOCK. Passes each element in LIST to the BLOCK in turn:

    say "At least one non-negative value"
      if any { $_ >= 0 }, ($x, $y, $z);

For an empty LIST, C<any> returns False and C<any_u> returns C<Nil>.

Thus, C<< any_u(@list) >> is equivalent to C<< @list ?? any(@list) !! undef >>.

=head4 Idiomatic Raku ways

    say "At least one non-negative value"
      if $x | $y | $z >= 0;

    say "At least one non-negative value"
      if any(@list) >= 0;

=head3 none BLOCK, LIST

=head3 none_u BLOCK, LIST

Logically the negation of C<any>. Returns True if no item in LIST meets
the criterion given through BLOCK. Passes each element in LIST to the BLOCK
in turn:

    say "No non-negative values"
      if none { $_ >= 0 }, ($x, $y, $z);

For an empty LIST, C<none> returns True (i.e. no values failed the condition)
and C<none_u> returns C<Nil>.

Thus, C<< none_u(@list) >> is equivalent to C<< @list ?? none(@list) !! Nil >>.

B<Note>: because Raku treats C<Nil> as false, you must check the return value
of C<none_u> with C<defined> or you will get the opposite result of what you
expect.

=head4 Idiomatic Raku ways

    say "No non-negative values"
      if none($x,$y,$z) >= 0;

    say "No non-negative values"
      if none(@list) >= 0;

=head3 notall BLOCK, LIST

=head3 notall_u BLOCK, LIST

Logically the negation of C<all>. Returns True if not all items in LIST meet
the criterion given through BLOCK. Passes each element in LIST to the BLOCK
in turn:

    say "Not all values are non-negative"
      if notall { $_ >= 0 }, ($x, $y, $z);

For an empty LIST, C<notall> returns False and C<notall_u> returns C<Nil>.

Thus, C<< notall_u(@list) >> is equivalent to C<< @list ?? notall(@list) !! Nil >>.

=head4 Idiomatic Raku ways

    say "Not all values are non-negative"
      if not all($x,$y,$z) >= 0;

    say "Not all values are non-negative"
      if not all(@list) >= 0;

=head3 one BLOCK, LIST

=head3 one_u BLOCK, LIST

Returns True if precisely one item in LIST meets the criterion given through
BLOCK. Passes each element in LIST to the BLOCK in turn:

    say "Precisely one value defined"
      if one { defined($_) }, @list;

Returns False otherwise.

For an empty LIST, C<one> returns False and C<one_u> returns C<Nil>.

The expression C<one BLOCK, LIST> is almost equivalent to
C<1 == True BLOCK, LIST>, except for short-cutting.  Evaluation of BLOCK will
immediately stop at the second true value seen.

=head4 Idiomatic Raku ways

    say "Precisely one value defined"
      if ($x ^ $y ^ $z).defined;

    say "Precisely one value defined"
      if one(@list>>.defined);

=head2 Transformation

=head3 apply BLOCK, LIST

Applies BLOCK to each item in LIST and returns a list of the values after
BLOCK has been applied. Returns the last element if C<Scalar> has been
specified.  This function is similar to C<map> but will not modify the
elements of the input list:

    my @list = 1 .. 4;
    my @mult = apply { $_ *= 2 }, @list;
    print "@list = @list[]\n";
    print "@mult = @mult[]\n";
    =====================================
    @list = 1 2 3 4
    @mult = 2 4 6 8

With the C<Scalar> positional parameter:

    my @list = 1 .. 4;
    my $last = apply Scalar, { $_ *= 2 }, @list;
    print "@list = @list[]\n";
    print "\$last = $last\n";
    =====================================
    @list = 1 2 3 4
    $last = 8

=head4 Idiomatic Raku ways

    my @mult = @list.map: -> $_ is copy { $_ *= 2 };

    my $last = @list.map( -> $_ is copy { $_ *= 2 }).tail;

=head3 insert_after BLOCK, VALUE, LIST

Inserts VALUE after the first item in LIST for which the criterion in BLOCK
is true. Sets C<$_> for each item in LIST in turn.

    my @list = <This is a list>;
    insert_after { $_ eq "a" }, "longer" => @list;
    say "@list[]";
    ===================================
    This is a longer list

=head3 insert_after_string STRING, VALUE, LIST

Inserts VALUE after the first item in LIST which is equal to STRING.

    my @list = <This is a list>;
    insert_after_string "a", "longer" => @list;
    say "@list[]";
    ===================================
    This is a longer list

=head3 pairwise BLOCK, ARRAY1, ARRAY2

Evaluates BLOCK for each pair of elements in ARRAY1 and ARRAY2 and returns a
new list consisting of BLOCK's return values. The two elements are passed as
parameters to BLOCK.

    my @a = 1 .. 5;
    my @b = 11 .. 15;
    my @x = pairwise -> $a, $b { $a + $b }, @a, @b; # returns 12, 14, 16, 18, 20

    # mesh with pairwise
    my @a = <a b c>;
    my @b = <1 2 3>;
    my @x = pairwise -> $a, $b { $a, $b }, @a, @b;    # returns a, 1, b, 2, c, 3

=head4 Idiomatic Raku ways

    my @x = zip(@a,@b).map: -> ($a,$b) { $a + $b };

    my @x = zip(@a,@b).flat;

=head3 mesh ARRAY1, ARRAY2 [ , ARRAY3 ... ]

=head3 zip ARRAY1, ARRAY2 [ , ARRAY3 ... ]

Returns a list consisting of the first elements of each array, then
the second, then the third, etc, until all arrays are exhausted.

Examples:

    my @x = <a b c d>;
    my @y = <1 2 3 4>;
    my @z = mesh @x, @y;       # returns a, 1, b, 2, c, 3, d, 4

    my Str @a = 'x';
    my Int @b = 1, 2;
    my @c = <zip zap zot>;
    my @d = mesh @a, @b, @c;   # x, 1, zip, Str, 2, zap, Str, Int, zot

C<zip> is an alias for C<mesh>.

=head4 Idiomatic Raku ways

    my @x = zip(@a,@b).flat;

    my @x = zip(@a,@b,@c).flat;

=head3 zip6 ARRAY1, ARRAY2 [ , ARRAY3 ... ]

=head3 zip_unflatten ARRAY1, ARRAY2 [ , ARRAY3 ... ]

Returns a list of arrays consisting of the first elements of each array,
then the second, then the third, etc, until all arrays are exhausted.

    my @x = <a b c d>;
    my @y = <1 2 3 4>;
    my @z = zip6 @x, @y;     # returns [a, 1], [b, 2], [c, 3], [d, 4]

    my Str @a = 'x';
    my Int @b = 1, 2;
    my @c = <zip zap zot>;
    my @d = zip6 @a, @b, @c; # [x, 1, zip], [Str, 2, zap], [Str, Int, zot]

C<zip_unflatten> is an alias for C<zip6>.

=head4 Idiomatic Raku ways

    my @x = zip(@a,@b);

    my @x = zip(@a,@b,@c);

=head3 listcmp ARRAY0 ARRAY1 [ ARRAY2 ... ]

Returns an associative list of elements and every I<id> of the list it
was found in. Allows easy implementation of @a & @b, @a | @b, @a ^ @b and
so on.  Undefined entries in any given array are skipped.

    my @a = <one two four five six seven eight nine ten>;
    my @b = <two five seven eleven thirteen seventeen>;
    my @c = <one one two five eight thirteen twentyone>;

    my %cmp := listcmp @a, @b, @c;
    # (one => [0, 2], two => [0, 1, 2], four => [0], ...)

    my @seq = 1, 2, 3;
    my @prim = Int, 2, 3, 5;
    my @fib = 1, 1, 2;
    my $cmp = listcmp @seq, @prim, @fib;
    # { 1 => [0, 2], 2 => [0, 1, 2], 3 => [0, 1], 5 => [1] }

=head4 Idiomatic Raku ways

    my @x = zip(@a,@b);

    my @x = zip(@a,@b,@c);

=head3 arrayify LIST [,LIST [,LIST...]]

Returns a list costisting of each element of the given arrays. Recursive arrays
are flattened, too.

    my @a = 1, [[2], 3], 4, [5], 6, [7], 8, 9;
    my @l = arrayify @a;   # returns 1, 2, 3, 4, 5, 6, 7, 8, 9

=head3 uniq LIST

=head3 distinct LIST

Returns a new list by stripping duplicate values in LIST by comparing
the values as hash keys, except that type objects are considered separate
from ''.  The order of elements in the returned list is the same as in LIST.
Returns the number of unique elements in LIST if the C<Scalar> positional
parameter has been specified.

    my @x = uniq (1, 1, 2, 2, 3, 5, 3, 4);          # returns (