Как я могу полностью сгладить список Perl 6 (списков (списков)...)

Мне было интересно, как я могу полностью сгладить списки и вещи, которые их содержат. Среди прочего, я придумал это решение, которое проскальзывает вещи, которые имеют более одного элемента, и откладывает их назад, или принимает вещи с одним элементом после его проскальзывания.

Это немного отличается от Как "сгладить" список списков в perl 6?, который не является полностью плоским, потому что задача заключается в реструктуризации,

Но, может быть, лучший способ.

my @a  = 'a', ('b', 'c' );
my @b  = ('d',), 'e', 'f', @a;
my @c  = 'x', $( 'y', 'z' ), 'w';

my @ab = @a, @b, @c;
say "ab: ", @ab;

my @f = @ab;

@f = gather {
    while @f {
        @f[0].elems == 1 ??
            take @f.shift.Slip
                !!
            @f.unshift( @f.shift.Slip )
        }
    }

say "f: ", @f;

Это дает:

ab: [[a (b c)] [(d) e f [a (b c)]] [x (y z) w]]
f: [a b c d e f a b c x y z w]

Любопытно, что я также прочитал несколько ответов на python:

itertools.chain(*sublist) выглядят интересными, но ответы были либо рекурсивными, либо ограниченными двумя уровнями от жесткого кодирования. Функциональные языки были рекурсивными в исходном коде, но я ожидал этого.

Ответ 1

К сожалению, нет прямого встроенного модуля, который полностью сглаживает структуру данных, даже если суб-списки обернуты в контейнеры элементов.

Некоторые возможные решения:

Собирают/взять

У вас уже появилось такое решение, но deepmap может позаботиться обо всей логике итераций дерева, чтобы упростить его. Его обратный вызов вызывается один раз для каждого листа node структуры данных, поэтому, используя take в качестве обратного вызова, gather будет собирать плоский список значений листа:

sub reallyflat ([email protected]) { gather @list.deepmap: *.take }

Пользовательская рекурсивная функция

Вы можете использовать подпрограмму, подобную этой, для рекурсивно slip списков в свой родитель:

multi reallyflat (@list) { @list.map: { slip reallyflat $_ } }
multi reallyflat (\leaf) { leaf }

Другим подходом было бы рекурсивно применить <> к подписям, чтобы освободить их от любых контейнеров элементов, в которые они были завернуты, а затем вызывать flat по результату:

sub reallyflat ([email protected]) {
    flat do for @list {
        when Iterable { reallyflat $_<> }
        default       { $_               }
    }
}

Индексирование многомерных массивов

Оператор postcircumfix [ ] может использоваться с многомерным индексом, чтобы получить плоский список листовых узлов до определенной глубины, но, к сожалению, версия "бесконечной глубины" еще не реализована:

say @ab[*;*];     # (a (b c) (d) e f [a (b c)] x (y z) w)
say @ab[*;*;*];   # (a b c d e f a (b c) x y z w)
say @ab[*;*;*;*]; # (a b c d e f a b c x y z w)
say @ab[**];      # HyperWhatever in array index not yet implemented. Sorry.

Тем не менее, если вы знаете максимальную глубину своей структуры данных, это жизнеспособное решение.

Предотвращение контейнеризации

Встроенная функция flat позволяет легко сгладить глубоко вложенные списки списков. Проблема в том, что она не спускается в контейнеры элементов (Scalar s). Общие источники непреднамеренных контейнеров товаров во вложенных списках:

  • An Array (но не List) обертывает каждый из своих элементов в контейнере нового элемента, независимо от того, имел ли он его раньше.

    • Как избежать: используйте списки списков вместо массивов массивов, если вам не нужна изменчивость, предоставляемая массивом. Вместо привязки можно использовать привязку :=, чтобы сохранить List в переменной @, не превращая ее в Array:
      my @a  := 'a', ('b', 'c' );
      my @b  := ('d',), 'e', 'f', @a;
      say flat @b; # (d e f a b c)
  • $ переменные являются контейнерами элементов.

    • Как избежать: сохраняя список в переменной $, а затем вставляя его как элемент в другой список, используйте <> для его деконфигурации. Контейнер родительского списка также можно обойти с помощью | при передаче его на flat:
      my $a = (3, 4, 5);
      my $b = (1, 2, $a<>, 6);
      say flat |$b; # (1 2 3 4 5 6)

Ответ 2

Я не знаю о встроенном способе сделать это, хотя там очень хорошо может быть (и если нет, вероятно, должно быть).

Лучшее, что я могу придумать в кратчайшие сроки, следующее:

gather @ab.deepmap(*.take)

Я не уверен, как сбор/принятие взаимодействует с потенциально распараллеливаемой оценкой гипероператоров, поэтому следующая альтернатива может быть небезопасной в использовании, особенно если вам нужен порядок элементов:

gather @ab>>.take

Вы можете поместить код в квадратные скобки, если вам нужен массив или перевести его в список через .list.

Наконец, это первое решение, переписанное как подпрограмма ретро-стиля:

sub deepflat { gather deepmap &take, @_ }