Finden der zusammenhängenden Sequenzen gleicher Elemente in einer Liste Raku

9

Ich möchte die zusammenhängenden Folgen gleicher Elemente (z. B. Länge 2) in einer Liste finden

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))

Dieser Code sieht in Ordnung aus, aber wenn nach der Sequenz eine weitere 2 hinzugefügt wird 2 2 2oder wenn eine 2 daraus entfernt wird, wird angezeigt, Too few positionals passed; expected 2 arguments but got 1wie das Problem behoben werden kann. Bitte beachten Sie, dass ich versuche, sie ohne Verwendung einer forSchleife zu finden , dh ich versuche, sie so weit wie möglich mithilfe eines Funktionscodes zu finden.

Optional: Im fett gedruckten Bereich:

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

mehrere Sequenzen von 2 2sind zu sehen. Wie drucke ich sie so oft, wie sie gesehen werden? Mögen:

((1 1) (2 2) (2 2) (4 4) (3 3))
Lars Malmsteen
quelle

Antworten:

9

Ihre Eingabe enthält eine gerade Anzahl von Elementen:

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

Ihr grepBlock verbraucht jedes Mal zwei Elemente:

{$^a eq $^b}

Wenn Sie also ein Element hinzufügen oder entfernen, wird der Fehler angezeigt, den Sie erhalten, wenn der Block für das einzelne Element ausgeführt wird, das am Ende übrig bleibt.


Es gibt viele Möglichkeiten, Ihr Problem zu lösen.

Sie haben aber auch nach der Möglichkeit gefragt, Überlappungen zuzulassen, sodass Sie beispielsweise zwei (2 2)Unterlisten erhalten, wenn die Sequenz 2 2 2angetroffen wird. Und in ähnlicher Weise möchten Sie vermutlich zwei Übereinstimmungen sehen, nicht Null, mit Eingaben wie:

<1 2 2 3 3 4>

Daher werde ich mich auf Lösungen konzentrieren, die sich auch mit diesen Problemen befassen.

Trotz der Verengung des Lösungsraums, um die zusätzlichen Probleme zu lösen, gibt es immer noch viele Möglichkeiten, Lösungen funktional auszudrücken.


Eine Möglichkeit, die nur ein bisschen mehr Code an Ihr Ende anfügt:

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

Die .rotorMethode konvertiert eine Liste in eine Liste von Unterlisten mit derselben Länge. Zum Beispiel say <1 2 3 4> .rotor: 2Anzeigen ((1 2) (3 4)). Wenn das Längenargument ein Paar ist, ist der Schlüssel die Länge und der Wert ein Versatz zum Starten des nächsten Paares. Wenn der Versatz negativ ist, erhalten Sie eine Überlappung der Unterlisten. So wird say <1 2 3 4> .rotor: 2 => -1angezeigt ((1 2) (2 3) (3 4)).

Die .flatMethode "glättet" ihren Invokanten. Zum Beispiel say ((1,2),(2,3),(3,4)) .flatAnzeigen (1 2 2 3 3 4).

Eine vielleicht besser lesbare Möglichkeit, die obige Lösung zu schreiben, besteht darin, die Unterlisten wegzulassen flatund zu verwenden .[0]und in sie .[1]zu indizieren, die zurückgegeben werden von rotor:

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

Siehe auch Elizabeth Mattijsens Kommentar für eine weitere Variation, die für jede Unterlistengröße verallgemeinert wird.


Wenn Sie ein allgemeineres Codierungsmuster benötigen, können Sie Folgendes schreiben:

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

Die .pairsMethode in einer Liste gibt eine Liste von Paaren zurück, wobei jedes Paar jedem der Elemente in seiner Invokantenliste entspricht. Das .keyvon jedem Paar ist der Index des Elements in der Invocant-Liste; Das .valueist der Wert des Elements.

.value xx 2hätte geschrieben werden können .value, .value. (Siehe xx.)

@s - 1ist die Anzahl der Elemente in @sminus 1.

Das [eq]in [eq] listist eine Reduzierung .


Wenn Sie eine Textmusterübereinstimmung benötigen, um zu entscheiden, was zusammenhängende gleiche Elemente sind, können Sie die Eingabeliste in eine Zeichenfolge konvertieren, diese mit einem der Übereinstimmungsadverbien abgleichen, die eine Liste von Übereinstimmungen generieren, und dann aus der resultierenden Liste von Übereinstimmungen Ihrer gewünschten zuordnen Ergebnis. So passen Sie Überlappungen an (z. B. 2 2 2Ergebnisse bei der ((2 2) (2 2))Verwendung :ov:

say @s .Str .match( / (.) ' ' $0 /, :ov ) .map: { .[0].Str xx 2 }
Raiph
quelle
Es funktioniert ganz gut. Wenn ich 2 2 s addiere , um die Sequenz zu 2 2 2 2erstellen, werden 3 (2 2)s gedruckt, was wie erwartet ist. rotorIch habe noch nie von der Methode gehört. Ich habe die Methode ursprünglich squishentwickelt und überprüft, ob sie Funktionen oder Argumente wie enthält @s.squish(:length 2, :multiple_instances yes), aber keine solchen Funktionen hatte und nicht für die Aufgabe geeignet war. Verglichen mit dem squish, rotor scheint ziemlich fit. Tatsächlich könnte es sogar das geeignetste für diese Art von Operation sein.
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)) Sie müssen nur die $sizefür unterschiedliche Längen von Sequenzen anpassen .
Elizabeth Mattijsen
Hallo nochmal @LarsMalmsteen. Bitte LMK, wenn Sie der Meinung sind, dass die beiden Alternativen, rotordie ich hinzugefügt habe, meine Antwort geschwächt oder gestärkt haben.
Raiph
Die verfeinerte Version der rotorLösung say @s.rotor(2=>-1).grep:{.[0]eq.[1]}ist willkommen, da sie sowohl kürzer ist (um 3 bis 5 Zeichen, je nachdem, wie die Leerzeichen gezählt werden) als auch anständig aussieht. Die verallgemeinerten Versionen ohne die rotorMethode sind ebenfalls willkommen, da sie zeigen, wie einige Macken wie die xxund :ovverwendet werden. Das Problem ist also sehr gut gelöst :)
Lars Malmsteen
5

TIMTOWDI!

Hier ist ein iterativer Ansatz mit 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))
Holli
quelle
Danke für die Antwort. Das sieht an sich ganz gut aus. Der take ($last, $_)Teil ist ein anständiges Beispiel für die Verwendung des gather and takeDuos.
Lars Malmsteen