Finding the contiguous sequences of equal elements in a list Raku

There are an even number of elements in your input:

say elems <1 1 0 2 0 2 1 2 2 2 4 4 3 3>; # 14

Your grep block consumes two elements each time:

{$^a eq $^b}

So if you add or remove an element you'll get the error you're getting when the block is run on the single element left over at the end.


There are many ways to solve your problem.

But you also asked about the option of allowing for overlapping so, for example, you get two (2 2) sub-lists when the sequence 2 2 2 is encountered. And, in a similar vein, you presumably want to see two matches, not zero, with input like:

<1 2 2 3 3 4>

So I'll focus on solutions that deal with those issues too.

Despite the narrowing of solution space to deal with the extra issues, there are still many ways to express solutions functionally.


One way that just appends a bit more code to the end of yours:

my @s = <1 1 0 2 0 2 1 2 2 2 4 4 3 3>;
say grep {$^a eq $^b}, @s .rotor( 2 => -1 ) .flat

The .rotor method converts a list into a list of sub-lists, each of the same length. For example, say <1 2 3 4> .rotor: 2 displays ((1 2) (3 4)). If the length argument is a pair, then the key is the length and the value is an offset for starting the next pair. If the offset is negative you get sub-list overlap. Thus say <1 2 3 4> .rotor: 2 => -1 displays ((1 2) (2 3) (3 4)).

The .flat method "flattens" its invocant. For example, say ((1,2),(2,3),(3,4)) .flat displays (1 2 2 3 3 4).

A perhaps more readable way to write the above solution would be to omit the flat and use .[0] and .[1] to index into the sub-lists returned by rotor:

say @s .rotor( 2 => -1 ) .grep: { .[0] eq .[1] }

See also Elizabeth Mattijsen's comment for another variation that generalizes for any sub-list size.


If you needed a more general coding pattern you might write something like:

say @s .pairs .map: { .value xx 2 if .key < @s - 1 and [eq] @s[.key,.key+1] }

The .pairs method on a list returns a list of pairs, each pair corresponding to each of the elements in its invocant list. The .key of each pair is the index of the element in the invocant list; the .value is the value of the element.

.value xx 2 could have been written .value, .value. (See xx.)

@s - 1 is the number of elements in @s minus 1.

The [eq] in [eq] list is a reduction.


If you need text pattern matching to decide what constitutes contiguous equal elements you might convert the input list into a string, match against that using one of the match adverbs that generate a list of matches, then map from the resulting list of matches to your desired result. To match with overlaps (eg 2 2 2 results in ((2 2) (2 2)) use :ov:

say @s .Str .match( / (.) ' ' $0 /, :ov ) .map: { .[0].Str xx 2 }

TIMTOWDI!

Here's an iterative approach using gather/take.

say gather for <1 1 0 2 0 2 1 2 2 2 4 4 3 3> { 
    state $last = ''; 
    take ($last, $_) if $last == $_; 
    $last = $_; 
};

# ((1 1) (2 2) (2 2) (4 4) (3 3))

Tags:

Sequence

Raku