Znajdowanie ciągłych sekwencji równych elementów na liście Raku


9

Chciałbym znaleźć ciągłe sekwencje równych elementów (np. O długości 2) na liście

my @s = <1 1 0 2 0 2 1 2 2 2 4 4 3 3>;
say grep {$^a eq $^b}, @s;

# ==> ((1 1) (2 2) (4 4) (3 3))

Ten kod wygląda dobrze, ale po dodaniu kolejnych 2 po sekwencji 2 2 2lub po usunięciu jednego 2 z niego jest napisane: Too few positionals passed; expected 2 arguments but got 1Jak to naprawić? Pamiętaj, że próbuję je znaleźć bez użycia forpętli, tzn. Staram się je znaleźć przy użyciu kodu funkcjonalnego w jak największym stopniu.

Opcjonalnie: w pogrubionej części drukowanej:

<1 1 0 2 0 2 1 2 2 2 4 4 3 3>

2 2widać wiele sekwencji . Jak wydrukować je tyle razy, ile są widoczne? Lubić:

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

Odpowiedzi:


9

W danych wejściowych jest parzysta liczba elementów:

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

Twój grepblok zużywa za każdym razem dwa elementy:

{$^a eq $^b}

Więc jeśli dodasz lub usuniesz element, otrzymasz błąd, który pojawia się, gdy blok jest uruchamiany na pojedynczym elemencie pozostałym na końcu.


Istnieje wiele sposobów rozwiązania problemu.

Ale zapytałeś także o opcję pozwalającą na nakładanie się, więc na przykład, (2 2)gdy 2 2 2napotkasz sekwencję , otrzymujesz dwie listy podrzędne . I, w podobnym stylu, prawdopodobnie chcesz zobaczyć dwa dopasowania, a nie zero, z danymi wejściowymi takimi jak:

<1 2 2 3 3 4>

Dlatego skupię się na rozwiązaniach, które również zajmują się tymi problemami.

Pomimo zawężenia przestrzeni rozwiązań, aby poradzić sobie z dodatkowymi problemami, wciąż istnieje wiele sposobów funkcjonalnego wyrażania rozwiązań.


Jednym ze sposobów, który dodaje trochę więcej kodu na końcu twojego:

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

.rotorMetoda konwertuje listę do listy podlist, każdy o tej samej długości. Na przykład say <1 2 3 4> .rotor: 2wyświetla ((1 2) (3 4)). Jeśli argumentem długości jest para, kluczem jest długość, a wartość jest przesunięciem do rozpoczęcia następnej pary. Jeśli przesunięcie jest ujemne, nakłada się na siebie lista podrzędna. Tak say <1 2 3 4> .rotor: 2 => -1wyświetla się ((1 2) (2 3) (3 4)).

Że .flatmetoda „spłaszczony” jego invocant. Na przykład say ((1,2),(2,3),(3,4)) .flatwyświetla (1 2 2 3 3 4).

Być może bardziej czytelnym sposobem na napisanie powyższego rozwiązania byłoby pominięcie flati użycie .[0]oraz .[1]indeksowanie do podlist, zwracanych przez rotor:

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

Zobacz także komentarz Elizabeth Mattijsen na temat innej odmiany, która uogólnia dla dowolnej wielkości podlisty.


Jeśli potrzebujesz bardziej ogólnego wzorca kodowania, możesz napisać coś takiego:

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

.pairsSposób na liście zwraca listę parach, każda para odpowiadających każdej z tych elementów w ramach invocant listy. .keyKażdej pary jest indeksem element listy invocant; .valuejest wartość tego elementu.

.value xx 2mógł zostać napisany .value, .value. (Patrz xx.)

@s - 1to liczba elementów w @sminus 1.

[eq]W [eq] listto ograniczenie .


Jeśli potrzebujesz dopasowania wzorca tekstowego, aby zdecydować, co stanowi ciągłe równe elementy, możesz przekonwertować listę wejściową na ciąg, dopasuj do tego za pomocą jednego z przysłówków dopasowujących, które generują listę dopasowań, a następnie odwzoruj z wynikowej listy dopasowań na żądany wynik. Aby dopasować z nakładaniem się (np. 2 2 2Wyniki ((2 2) (2 2))użycia :ov:

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

Działa całkiem dobrze. Kiedy dodam 2 2 s, aby utworzyć sekwencję 2 2 2 2, drukuje 3 (2 2)s, co jest zgodne z oczekiwaniami. Nigdy nie słyszałem o metodzie rotorPoczątkowo wymyśliłem tę squishmetodę i sprawdziłem, czy ma ona cechy lub argumenty podobne, @s.squish(:length 2, :multiple_instances yes)ale nie miała takich cech i nie nadawała się do tego zadania. W porównaniu z squish, rotor wydaje się dość sprawny. W rzeczywistości może być nawet najodpowiedniejszy dla tego typu operacji.
Lars Malmsteen,

3
my $size = 2; say <1 1 0 2 0 2 1 2 2 2 4 4 3 3>.rotor( $size => -$size + 1).grep: { [eq] $_ }# ((1 1) (2 2) (2 2) (4 4) (3 3)) Trzeba tylko dostosować $sizesekwencje dla różnych długości.
Elizabeth Mattijsen,

Cześć {@} znowu LarsMalmsteen. Proszę, LMK, jeśli uważasz, że dwie alternatywy rotor, które dodałem, osłabiły lub wzmocniły moją odpowiedź.
raiph

Udoskonalona wersja rotorrozwiązania say @s.rotor(2=>-1).grep:{.[0]eq.[1]}jest mile widziana, ponieważ jest zarówno krótsza (o 3 do 5 znaków w zależności od sposobu liczenia spacji) i nadal wygląda przyzwoicie. Uogólnione wersje bez tej rotormetody są również mile widziane, ponieważ pokazują, jak niektóre dziwactwa lubią xxi :ovsą używane. Problem jest więc bardzo dobrze rozwiązany :)
Lars Malmsteen,

5

TIMTOWDI!

Oto iteracyjne podejście z użyciem 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))

Dziękuję za Twoją odpowiedź. To samo w sobie wygląda całkiem dobrze. Ta take ($last, $_)część jest dobrym przykładem użycia gather and takeduetu.
Lars Malmsteen,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.