Localizando as seqüências contíguas de elementos iguais em uma lista Raku


9

Eu gostaria de encontrar as seqüências contíguas de elementos iguais (por exemplo, comprimento 2) em uma lista

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

Esse código parece bom, mas quando mais 2 são adicionados após a sequência 2 2 2ou quando um 2 é removido, ele diz Too few positionals passed; expected 2 arguments but got 1Como corrigi-lo? Observe que estou tentando encontrá-los sem usar o forloop, ou seja, estou tentando encontrá-los usando um código funcional o máximo possível.

Opcional: Na seção impressa em negrito:

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

múltiplas seqüências de 2 2são vistas. Como imprimi-los o número de vezes que são vistos? Gostar:

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

Respostas:


9

Há um número par de elementos em sua entrada:

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

Seu grepbloco consome dois elementos de cada vez:

{$^a eq $^b}

Portanto, se você adicionar ou remover um elemento, receberá o erro que ocorrerá quando o bloco for executado no único elemento restante no final.


Existem muitas maneiras de resolver seu problema.

Mas você também perguntou sobre a opção de permitir sobreposição, por exemplo, você recebe duas (2 2)sub-listas quando a sequência 2 2 2é encontrada. E, de maneira semelhante, você presumivelmente deseja ver duas correspondências, não zero, com entradas como:

<1 2 2 3 3 4>

Então, vou me concentrar em soluções que lidam com esses problemas também.

Apesar do estreitamento do espaço da solução para lidar com os problemas extras, ainda existem muitas maneiras de expressar soluções funcionalmente.


Uma maneira de adicionar um pouco mais de código ao final do seu:

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

O .rotormétodo converte uma lista em uma lista de sub-listas, cada uma com o mesmo comprimento. Por exemplo, é say <1 2 3 4> .rotor: 2exibido ((1 2) (3 4)). Se o argumento do comprimento for um par, a chave será o comprimento e o valor será um deslocamento para iniciar o próximo par. Se o deslocamento for negativo, você terá sobreposição da sub-lista. Assim é say <1 2 3 4> .rotor: 2 => -1exibido ((1 2) (2 3) (3 4)).

O .flatmétodo "nivela" seu invocante. Por exemplo, é say ((1,2),(2,3),(3,4)) .flatexibido (1 2 2 3 3 4).

Uma maneira talvez mais legível de escrever a solução acima seria omitir flate usar .[0]e .[1]indexar nas sub-listas retornadas por rotor:

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

Veja também o comentário de Elizabeth Mattijsen para outra variação generalizada para qualquer tamanho de sub-lista.


Se você precisava de um padrão de codificação mais geral, escreva algo como:

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

O .pairsmétodo em uma lista retorna uma lista de pares, cada par correspondendo a cada um dos elementos em sua lista invocante. O .keyde cada par é o índice do elemento na lista invocante; o .valueé o valor do elemento.

.value xx 2poderia ter sido escrito .value, .value. (Veja xx.)

@s - 1é o número de elementos em @smenos 1.

A [eq]na [eq] listé uma redução .


Se você precisar de correspondência de padrão de texto para decidir o que constitui elementos iguais contíguos, poderá converter a lista de entrada em uma sequência, faça a correspondência com essa usando um dos advérbios de correspondência que geram uma lista de correspondências e mapeie a partir da lista de correspondências resultante para a desejada resultado. Para combinar com sobreposições (por exemplo, 2 2 2resultados em ((2 2) (2 2))uso :ov:

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

Funciona muito bem. Quando adiciono 2 2 s para fazer a sequência, 2 2 2 2ele imprime 3 (2 2)s, conforme o esperado. Nunca ouvi falar do método rotor. Inicialmente, inventei o squishmétodo e verifiquei se ele possui características ou argumentos semelhantes, @s.squish(:length 2, :multiple_instances yes)mas não possuía essas características e não era adequado para a tarefa. Comparado com o squish, rotor parece bastante adequado. Na verdade, pode até ser o mais adequado para esse tipo de operação.
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)) Você só precisa ajustar $sizepara diferentes comprimentos de sequência.
Elizabeth Mattijsen

Olá novamente @LarsMalmsteen. Por favor, LMK, se você acha que as duas alternativas rotoradicionadas enfraqueceram ou reforçaram minha resposta.
raiph

A versão refinada da rotorsolução, ou seja, say @s.rotor(2=>-1).grep:{.[0]eq.[1]}é bem-vinda porque é mais curta (de 3 a 5 caracteres, dependendo de como os espaços são contados) e ainda parece decente. As versões generalizadas sem o rotormétodo também são bem-vindas, porque mostram como algumas peculiaridades como a xxe :ovsão usadas. Portanto, o problema é muito bem resolvido :)
Lars Malmsteen

5

TIMTOWDI!

Aqui está uma abordagem iterativa usando 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))

Obrigado pela resposta. Isso parece bem por si só. A take ($last, $_)parte é um exemplo decente sobre o uso da gather and takedupla.
Lars Malmsteen
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.