TIMTOWTDItime
On Discord flirora wished for a way to merge list elements conditional. In this instance the condition is that any element that starts with a space is part of a group.
{
my @a = ("apple", " banana", " peach", "blueberry", "pear", " plum", "kiwi");
multi sub merge-spacy([]) { () }
multi sub merge-spacy([$x is copy, *@xs]) {
if @xs[0].?starts-with(' ') {
$x ~= @xs.shift;
merge-spacy([|$x, |@xs])
} else {
$x, |merge-spacy(@xs)
}
}
dd merge-spacy(@a);
}
# OUTPUT: ("apple banana peach", "blueberry", "pear plum", "kiwi")
This functional version is neat but slow. Rakudo can’t inline recursion and doesn’t do any other optimisations yet.
my @a = ("apple", " banana", " peach", "blueberry", "pear", " plum", "kiwi");
sub merge-with(@a, &c) {
gather while @a.shift -> $e {
if @a && &c(@a.head) {
@a.unshift($e ~ @a.shift)
} else {
take $e;
}
}
}
dd @a.&merge-with(*.starts-with(' '));
# OUTPUT: ("apple banana peach", "blueberry", "pear plum", "kiwi").Seq
With gather/take we don’t have to worry about recursion and the returned Seq
is lazy be default. This can provide a big win if the list gets big and is not wholly consumed.
my @a = ("apple", " banana", " peach", "blueberry", "pear", " plum", "kiwi");
multi sub join(*@a, :&if!) {
class :: does Iterable {
method iterator {
class :: does Iterator {
has @.a;
has &.if;
method pull-one {
return IterationEnd unless @!a;
my $e = @!a.shift;
return $e unless @!a;
while &.if.(@!a.head) {
$e ~= @!a.shift;
}
return $e;
}
}.new(a => @a, if => &if)
}
}.new
}
.say for join(@a, if => *.starts-with(' '));
This version should please lizmat as it uses iterators. The conditional is also factored out and CORE
will use the Iterator lazily wherever possible. In production code I would get rid of the return
-statements and replace them with ternary operators to get a little extra performance.
The original question (that clearly got me carried away) asked for the groups to be join
. Once we lost a structure it can be difficult to reconstruct it.
my @a = ("apple", " banana", " peach", "blueberry", "pear", " plum", "kiwi");
#| &c decides if the group is finished
sub group-list(@a, &c) {
my @group;
gather while @a {
my $e = @a.shift;
my $next := +@a ?? @a.head !! Nil;
@group.push($e);
if !c($e, $next) {
take @group.clone;
@group = ();
}
}
}
dd @a.&group-list(-> $left, $right { $right && $right.starts-with(' ')});
Here the conditional gets two elements to decide if they belong to the same group. It is also the first time I used .clone
.
Thanks to a simple question I learned quite a bit. It forced me to think about the disadvantages of my first idea. Maybe code challenges should explicitly asked for more then one answer for the same question.
-
November 8, 2021 at 20:572021.45 Two Commas – Rakudo Weekly News