Нахождение смежных последовательностей равных элементов в списке Раку

9

Я хотел бы найти смежные последовательности равных элементов (например, длины 2) в списке

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

Этот код выглядит нормально, но когда еще одна 2 добавляется после последовательности 2 2 2или когда одна 2 удаляется из нее, он говорит Too few positionals passed; expected 2 arguments but got 1Как это исправить? Обратите внимание, что я пытаюсь найти их без использования forцикла, то есть я пытаюсь найти их, используя как можно больше функционального кода.

Необязательно: в жирном шрифте

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

несколько последовательностей 2 2видны. Как распечатать их, сколько раз их видели? Подобно:

((1 1) (2 2) (2 2) (4 4) (3 3))
Ларс Мальмстин
источник

Ответы:

9

В вашем входе четное количество элементов:

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

Ваш grepблок потребляет два элемента каждый раз:

{$^a eq $^b}

Поэтому, если вы добавите или удалите элемент, вы получите ошибку, которую вы получите, когда блок будет запущен для одного элемента, оставшегося в конце.


Есть много способов решить вашу проблему.

Но вы также спросили о возможности разрешения наложения, так что, например, вы получаете два (2 2)подсписка, когда 2 2 2встречается последовательность . И в аналогичном ключе вы, вероятно, хотите видеть два совпадения, а не ноль, с такими входными данными:

<1 2 2 3 3 4>

Поэтому я сосредоточусь на решениях, которые также касаются этих проблем.

Несмотря на сужение пространства решений для решения дополнительных проблем, есть еще много способов выразить решения функционально.


Один способ, который просто добавляет немного кода в конец вашего:

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

.rotorМетод преобразует список в список подсписков, каждый из одной и той же длины. Например, say <1 2 3 4> .rotor: 2отображает ((1 2) (3 4)). Если аргумент длины является парой, то ключ является длиной, а значение является смещением для начала следующей пары. Если смещение отрицательное, вы получаете перекрытие подсписка. Таким образом say <1 2 3 4> .rotor: 2 => -1отображает ((1 2) (2 3) (3 4)).

В .flatметоде «» его более плоский invocant. Например, say ((1,2),(2,3),(3,4)) .flatотображает (1 2 2 3 3 4).

Возможно, более читаемый способ написания вышеприведенного решения состоит в том, чтобы опустить flatи использовать .[0]и .[1]индексировать в подсписки, возвращаемые rotor:

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

Смотрите также комментарий Элизабет Маттийсен для другого варианта, который обобщает для любого размера подсписка.


Если вам нужен более общий шаблон кодирования, вы можете написать что-то вроде:

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

.pairsМетод в списке возвращает список пара, каждую пару , соответствующую каждый из элементов в его invocant списка. Значение .keyкаждой пары - это индекс элемента в списке инвокантов; .valueявляется значением элемента.

.value xx 2мог быть написан .value, .value. (См xx.)

@s - 1количество элементов в @sминус 1.

[eq]В [eq] listэто сокращение .


Если вам нужно сопоставление с текстовым шаблоном, чтобы решить, из чего состоят смежные равные элементы, вы можете преобразовать входной список в строку, сопоставьте ее с помощью одного из наречий совпадений, которые генерируют список совпадений, а затем сопоставьте полученный итоговый список совпадений с вашим желаемым результат. Чтобы соответствовать с перекрытиями (например, 2 2 2результаты в ((2 2) (2 2))использовании :ov:

say @s .Str .match( / (.) ' ' $0 /, :ov ) .map: { .[0].Str xx 2 }
raiph
источник
Работает вполне нормально. Когда я добавляю 2 2 с, чтобы сделать последовательность, 2 2 2 2она печатает 3 (2 2)с, как и ожидалось. Никогда не слышал о методе, rotorя изначально придумал этот squishметод и проверил, есть ли у него такие особенности или аргументы, как у него, @s.squish(:length 2, :multiple_instances yes)но у него не было таких функций, и он не подходит для этой задачи. По сравнению с squish, rotor кажется, вполне подходит. На самом деле это может быть даже самый подходящий для этого типа операции.
Ларс Мальмстин
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)) Вам нужно только отрегулировать $sizeдля разных длин последовательностей.
Элизабет Маттийсен
Привет снова @LarsMalmsteen. Пожалуйста, LMK, если вы думаете, что две альтернативы, rotorкоторые я добавил, ослабили или усилили мой ответ.
raiph
Уточненная версия rotorрешения, т. say @s.rotor(2=>-1).grep:{.[0]eq.[1]}Е. Приветствуется, потому что она и короче (на 3–5 символов в зависимости от того, как подсчитаны пробелы) и все еще выглядит прилично. Также rotorприветствуются обобщенные версии без метода, потому что они показывают, как используются некоторые причуды, подобные xxи :ov. Так что проблема очень хорошо решена :)
Ларс Мальмстин
5

TIMTOWDI!

Вот итерационный подход с использованием 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
источник
Спасибо за ответ. Это выглядит довольно хорошо само по себе. take ($last, $_)Часть представляет собой достойный пример на использование этого gather and takeдуэта.
Ларс Мальмстин