Trouver les séquences contiguës d'éléments égaux dans une liste Raku

9

Je voudrais trouver les séquences contiguës d'éléments égaux (par exemple de longueur 2) dans une liste

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

Ce code semble correct mais quand un 2 de plus est ajouté après la séquence de 2 2 2ou quand un 2 en est supprimé, il dit Too few positionals passed; expected 2 arguments but got 1comment le corriger? Veuillez noter que j'essaie de les trouver sans utiliser de forboucle, c'est-à-dire que j'essaie de les trouver en utilisant un code fonctionnel autant que possible.

Facultatif: dans la section imprimée en gras:

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

on voit plusieurs séquences de 2 2. Comment les imprimer le nombre de fois où ils sont vus? Comme:

((1 1) (2 2) (2 2) (4 4) (3 3))
Lars Malmsteen
la source

Réponses:

9

Il y a un nombre pair d'éléments dans votre entrée:

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

Votre grepbloc consomme à chaque fois deux éléments:

{$^a eq $^b}

Donc, si vous ajoutez ou supprimez un élément, vous obtiendrez l'erreur que vous obtenez lorsque le bloc est exécuté sur le seul élément restant à la fin.


Il existe de nombreuses façons de résoudre votre problème.

Mais vous avez également demandé l'option de permettre le chevauchement, par exemple, vous obtenez deux (2 2)sous-listes lorsque la séquence 2 2 2est rencontrée. Et, dans la même veine, vous voulez probablement voir deux correspondances, pas zéro, avec une entrée comme:

<1 2 2 3 3 4>

Je vais donc me concentrer sur les solutions qui traitent également de ces problèmes.

Malgré le rétrécissement de l'espace des solutions pour traiter les problèmes supplémentaires, il existe encore de nombreuses façons d'exprimer les solutions de manière fonctionnelle.


Une façon qui ajoute juste un peu plus de code à la fin de la vôtre:

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

La .rotorméthode convertit une liste en une liste de sous-listes, chacune de la même longueur. Par exemple, say <1 2 3 4> .rotor: 2s'affiche ((1 2) (3 4)). Si l'argument de longueur est une paire, la clé est la longueur et la valeur est un décalage pour démarrer la paire suivante. Si le décalage est négatif, vous obtenez un chevauchement de sous-liste. say <1 2 3 4> .rotor: 2 => -1Affiche ainsi ((1 2) (2 3) (3 4)).

La .flatméthode "aplatit" son invocant. Par exemple, say ((1,2),(2,3),(3,4)) .flats'affiche (1 2 2 3 3 4).

Une façon peut-être plus lisible d'écrire la solution ci-dessus serait d'omettre flatet d'utiliser .[0]et .[1]d'indexer dans les sous-listes renvoyées par rotor:

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

Voir aussi le commentaire d'Elizabeth Mattijsen pour une autre variation qui se généralise pour toute taille de sous-liste.


Si vous aviez besoin d'un modèle de codage plus général, vous pourriez écrire quelque chose comme:

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

La .pairsméthode sur une liste renvoie une liste de paires, chaque paire correspondant à chacun des éléments de sa liste invocante. Le .keyde chaque paire est l'indice de l'élément dans la liste invocante; l' .valueest la valeur de l'élément.

.value xx 2aurait pu être écrit .value, .value. (Voir xx.)

@s - 1est le nombre d'éléments en @smoins 1.

L' [eq]entrée [eq] listest une réduction .


Si vous avez besoin d'une correspondance de modèle de texte pour décider de ce qui constitue des éléments égaux contigus, vous pouvez convertir la liste d'entrée en chaîne, comparez-la à celle-ci en utilisant l'un des adverbes de correspondance qui génèrent une liste de correspondances, puis mappez la liste de correspondances résultante vers la liste souhaitée résultat. Pour correspondre aux chevauchements (par exemple, les 2 2 2résultats d' ((2 2) (2 2))utilisation :ov:

say @s .Str .match( / (.) ' ' $0 /, :ov ) .map: { .[0].Str xx 2 }
raiph
la source
Cela fonctionne très bien. Lorsque j'ajoute 2 2 s pour faire la séquence, 2 2 2 2il imprime 3 (2 2)s, ce qui est comme prévu. rotorJe n'ai jamais entendu parler de la méthode.J'ai initialement proposé cette squishméthode et vérifié si elle avait des fonctionnalités ou des arguments comme, @s.squish(:length 2, :multiple_instances yes)mais elle n'en avait pas et elle n'était pas adaptée à la tâche. Par rapport au squish, rotor semble tout à fait en forme. En fait, il pourrait même être le plus adapté à ce type d'opération.
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)) Il vous suffit de régler la $sizepour différentes longueurs de séquences.
Elizabeth Mattijsen
Salut encore @LarsMalmsteen. S'il vous plaît LMK si vous pensez que les deux alternatives à rotorcelles que j'ai ajoutées ont affaibli ou renforcé ma réponse.
raiph
La version raffinée de la rotorsolution say @s.rotor(2=>-1).grep:{.[0]eq.[1]}est donc la bienvenue car elle est à la fois plus courte (de 3 à 5 caractères selon la façon dont les espaces sont comptés) et semble toujours décente. Les versions généralisées sans la rotorméthode sont également les bienvenues car elles montrent comment certaines bizarreries comme le xxet :ovsont utilisées. Le problème est donc très bien résolu :)
Lars Malmsteen
5

TIMTOWDI!

Voici une approche itérative utilisant 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
la source
Merci pour la réponse. Cela semble assez bien en soi. La take ($last, $_)partie est un exemple décent sur l'utilisation du gather and takeduo.
Lars Malmsteen