NAME
Raku port of Perl's List::UtilsBy module 0.11
SYNOPSIS
use List::UtilsBy <nsort_by min_by>;
my @files_by_age = nsort_by { .IO.modified }, @files;
my $shortest_name = min_by { .chars }, @names;
DESCRIPTION
This module tries to mimic the behaviour of Perl's List::UtilsBy
module as closely as possible in the Raku Programming Language.
List::UtilsBy provides some trivial but commonly needed functionality on lists which is not going to go into List::Util
.
Porting Caveats
Raku does not have the concept of scalar
and list
context. Usually, the effect of a scalar context can be achieved by prefixing +
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.
Many functions take a &code
parameter of a Block
to be called by the function. Many of these assume $_ 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 $_
inside the Block. If you want to be able to change $_
inside the block without changing the source array, you can use the is copy
trait thus:
-> $_ is copy { ... code changing $_ ... }
Raku also doesn't have a single undef
value, but instead has Type Objects
, which could be considered undef values, but with a type annotation. In this module, Nil
(a special value denoting the absence of a value where there should have been one) is used instead of undef
.
Also note there are no special parsing rules with regards to blocks in Raku. So a comma is always required after having specified a block.
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 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.
FUNCTIONS
sort_by BLOCK, LIST
Returns the list of values sorted according to the string values returned by the BLOCK. A typical use of this may be to sort objects according to the string value of some accessor, such as:
my @sorted = sort_by { .name }, @people;
The key function is being passed each value in turn, The values are then sorted according to string comparisons on the values returned. This is equivalent to:
my @sorted = sort -> $a, $b { $a.name cmp $b.name }, @people;
except that it guarantees the name
accessor will be executed only once per value. One interesting use-case is to sort strings which may have numbers embedded in them "naturally", rather than lexically:
my @sorted = sort_by { S:g/ (\d+) / { sprintf "%09d", $0 } / }, @strings;
This sorts strings by generating sort keys which zero-pad the embedded numbers to some level (9 digits in this case), helping to ensure the lexical sort puts them in the correct order.
Idiomatic Raku ways
my @sorted = @people.sort: *.name;
nsort_by BLOCK, LIST
Similar to /sort_by
but compares its key values numerically.
Idiomatic Raku ways
my @sorted = <10 1 20 42>.sort: +*;
rev_sort_by BLOCK, LIST
rev_nsort_by BLOCK, LIST
my @sorted = rev_sort_by { KEYFUNC }, @values;
my @sorted = rev_nsort_by { KEYFUNC }, @values;
Similar to L<sort_by> and L<nsort_by> but returns the list in the reversei
order.
max_by BLOCK, LIST
my @optimal = max_by { KEYFUNC }, @values;
my $optimal = max_by Scalar, { KEYFUNC }, @values;
Returns the (first) value(s) from @vals
that give the numerically largest result from the key function.
my $tallest = max_by Scalar, { $_->height }, @people;
my $newest = max_by Scalar, { .IO.modified }, @files;
If the Scalar
positional parameter is specified, then only the first maximal value is returned. Otherwise a list of all the maximal values is returned. This may be used to obtain positions other than the first, if order is significant.
If called on an empty list, an empty list is returned.
For symmetry with the /nsort_by function, this is also provided under the name nmax_by
since it behaves numerically.
Idiomatic Raku ways
my @tallest = @people.max( *.height ); # all tallest people
my $tallest = @people.max( *.height ).head; # only the first
min_by BLOCK, LIST
my @optimal = min_by { KEYFUNC }, @values;
my $optimal = min_by Scalar, { KEYFUNC }, @values;
Similar to /max_by but returns values which give the numerically smallest result from the key function. Also provided as nmin_by
Idiomatic Raku ways
my @smallest = @people.min: *.height; # all smallest people
my $smallest = @people.min( *.height ).head; # only the first
minmax_by
my ($minimal, $maximal) = minmax_by { KEYFUNC }, @values;
Similar to calling both /min_by and /max_by with the same key function on the same list. This version is more efficient than calling the two other functions individually, as it has less work to perform overall. Also provided as nminmax_by
.
Idiomatic Raku ways
my ($smallest,$tallest) = @people.minmax: *.height;
uniq_by BLOCK, LIST
my @unique = uniq_by { KEYFUNC }, @values;
Returns a list of the subset of values for which the key function block returns unique values. The first value yielding a particular key is chosen, subsequent values are rejected.
my @some_fruit = uniq_by { $_->colour }, @fruit;
To select instead the last value per key, reverse the input list. If the order of the results is significant, don't forget to reverse the result as well:
my @some_fruit = reverse uniq_by { $_->colour }, reverse @fruit;
Because the values returned by the key function are used as hash keys, they ought to either be strings, or at least stringify in an identifying manner.
Idiomatic Raku ways
my @some_fruit = @fruit.uniq: *.colour;
partition_by BLOCK, LIST
my %parts = partition_by { KEYFUNC }, @values;
Returns a Hash of Arrays containing all the original values distributed according to the result of the key function block. Each value will be an Array containing all the values which returned the string from the key function, in their original order.
my %balls_by_colour = partition_by { $_->colour }, @balls;
Because the values returned by the key function are used as hash keys, they ought to either be strings, or at least stringify in an identifying manner.
Idiomatic Raku ways
my %balls_by_colour = @balls.classify: *.colour;
count_by BLOCK, LIST
my %counts = count_by { KEYFUNC }, @values;
Returns a Hash giving the number of times the key function block returned the key, for each value in the list.
my %count_of_balls = count_by { $_->colour }, @balls;
Because the values returned by the key function are used as hash keys, they ought to either be strings, or at least stringify in an identifying manner.
Idiomatic Raku ways
my %count_of_balls = @balls.map( *.colour ).Bag;
zip_by BLOCK, ARRAYS
my @vals = zip_by { ITEMFUNC }, @arr0, @arr1, @arr2, ... ;
Returns a list of each of the values returned by the function block, when invoked with values from across each each of the given Arrays. Each value in the returned list will be the result of the function having been invoked with arguments at that position, from across each of the arrays given.
my @transposition = zip_by { [ @_ ] }, @matrix;
my @names = zip_by { "$_[1], $_[0]" }, @firstnames, @surnames;
print zip_by { "$_[0] => $_[1]\n" }, %hash.keys, %hash.values;
If some of the arrays are shorter than others, the function will behave as if they had Any
in the trailing positions. The following two lines are equivalent:
zip_by { f(@_) }, [ 1, 2, 3 ], [ "a", "b" ];
f( 1, "a" ), f( 2, "b" ), f( 3, Any );
If the item function returns a list, and you want to have the separate entries of that list to be included in the result, you need to return that slip that list. This can be useful for example, for generating a hash from two separate lists of keys and values:
my %nums = zip_by { |@_ }, <one two three>, (1, 2, 3);
# %nums = ( one => 1, two => 2, three => 3 )
(A function having this behaviour is sometimes called zipWith
, e.g. in Haskell, but that name would not fit the naming scheme used by this module).
Idiomatic Raku ways
my @names = zip @firstnames, @surnames, :with({ "$^b, $^a" });
zip [1,2,3], [<a b>], :with(&f);
my %nums = zip <one two three>, (1, 2, 3);
unzip_by BLOCK, LIST
my (@arr0, @arr1, @arr2, ...) = unzip_by { ITEMFUNC }, @vals
Returns a list of Arrays containing the values returned by the function block, when invoked for each of the values given in the input list. Each of the returned Arrays will contain the values returned at that corresponding position by the function block. That is, the first returned Array will contain all the values returned in the first position by the function block, the second will contain all the values from the second position, and so on.
my (@firstnames, @lastnames) = unzip_by { .split(" ",2) }, @names;
If the function returns lists of differing lengths, the result will be padded with Any
in the missing elements.
This function is an inverse of /zip_by, if given a corresponding inverse function.
my @vals = extract_by { SELECTFUNC }, @array;
Removes elements from the referenced array on which the selection function returns true, and returns a list containing those elements. This function is similar to grep
, except that it modifies the referenced array to remove the selected values from it, leaving only the unselected ones.
my @red_balls = extract_by { .color eq "red" }, @balls;
# Now there are no red balls in the @balls array
This function modifies a real array, unlike most of the other functions in this module. Because of this, it requires a real array, not just a list.
This function is implemented by invoking splice
on the array, not by constructing a new list and assigning it.
my $value = extract_first_by { SELECTFUNC }, @array;
A hybrid between /extract_by and List::Util::first
. Removes the first element from the referenced array on which the selection function returns true, returning it.
As with /extract_by, this function requires a real array and not just a list, and is also implemented using splice
.
If this function fails to find a matching element, it will return an empty list unless called with the Scalar
positional parameter: in that case it will return Nil
.
weighted_shuffle_by BLOCK, LIST
my @shuffled = weighted_shuffle_by { WEIGHTFUNC }, @values;
Returns the list of values shuffled into a random order. The randomisation is not uniform, but weighted by the value returned by the WEIGHTFUNC
. The probability of each item being returned first will be distributed with the distribution of the weights, and so on recursively for the remaining items.
bundle_by BLOCK, NUMBER, LIST
my @bundled = bundle_by { BLOCKFUNC }, $number, @values;
Similar to a regular map
functional, returns a list of the values returned by BLOCKFUNC
. Values from the input list are given to the block function in bundles of $number
.
If given a list of values whose length does not evenly divide by $number
, the final call will be passed fewer elements than the others.
Idiomatic Raku ways
my @bundled = @values.batch(3).map: -> @_ { ... };
AUTHOR
Elizabeth Mattijsen liz@raku.rocks
If you like this module, or what I’m doing more generally, committing to a small sponsorship would mean a great deal to me!
Source can be located at: https://github.com/lizmat/List-UtilsBy . Comments and Pull Requests are welcome.
COPYRIGHT AND LICENSE
Copyright 2018, 2019, 2020, 2021, 2023 Elizabeth Mattijsen
This library is free software; you can redistribute it and/or modify it under the Artistic License 2.0.
Re-imagined from the Perl version as part of the CPAN Butterfly Plan. Perl version developed by Paul Evans.