В Perl, как я могу получить Декартово пересечение нескольких множеств?

Я хочу сделать перестановку в Perl. Например, у меня есть три массива: ["big", "tiny", "small"] и затем я имею ["red", "yellow", "green"] и также ["apple", "pear", "banana"].

Как делают я добираюсь:

["big", "red", "apple"]
["big", "red", "pear"]

..etc..

["small", "green", "banana"]

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

13
задан cxw 30 July 2019 в 14:50
поделиться

4 ответа

На самом деле это не перестановка, а декартово произведение. See Math::Cartesian::Product.

#!/usr/bin/perl

use strict; use warnings;

use Math::Cartesian::Product;

cartesian { print "@_\n" }
    ["big", "tiny", "small"],
    ["red", "yellow", "green"],
    ["apple", "pear", "banana"];

Output:

C:\Temp> uu
big red apple
big red pear
big red banana
big yellow apple
big yellow pear
big yellow banana
big green apple
big green pear
big green banana
tiny red apple
tiny red pear
tiny red banana
tiny yellow apple
tiny yellow pear
tiny yellow banana
tiny green apple
tiny green pear
tiny green banana
small red apple
small red pear
small red banana
small yellow apple
small yellow pear
small yellow banana
small green apple
small green pear
small green banana
15
ответ дан 1 December 2019 в 19:49
поделиться

Несколько лет назад мне пришлось решить именно эту проблему. Я не смог придумать собственное решение, но вместо этого наткнулся на этот замечательный фрагмент кода, который включает в себя умное и разумное использование map вместе с рекурсией:

#!/usr/bin/perl

print "permute:\n";
print "[", join(", ", @$_), "]\n" for permute([1,2,3], [4,5,6], [7,8,9]);

sub permute {

    my $last = pop @_;

    unless(@_) {
           return map([$_], @$last);
    }

    return map { 
                 my $left = $_; 
                 map([@$left, $_], @$last)
               } 
               permute(@_);
}

Да, это выглядит безумно, но позвольте мне объяснить! Функция будет повторяться до тех пор, пока @_ не станет пустым, после чего она вернет ([1], [2], [3]) (список из трех ссылок на массив) к предыдущему уровень рекурсии. На этом уровне $ last - это ссылка на массив, содержащий [4, 5, 6] .

Затем тело внешней карты запускается три раза с $ _ , установленным на [1], затем [2] и, наконец, [3]. Затем внутренняя карта запускается по (4, 5, 6) для каждой итерации внешней карты, и это возвращает ([1, 4], [1, 5], [1, 6 ]) , ([2, 4], [2, 5], [2, 6]) и, наконец, ([3, 4], [3, 5], [3, 6]) .

Предпоследний рекурсивный вызов возвращает ([1, 4], [1, 5], [1, 6], [2, 4], [2, 5], [2, 6] , [3, 4], [3, 5], [3, 6]) .

Затем , этот результат сравнивается с [7,8,9] , что дает вам [1, 4, 7], [1, 4, 8], [ 1, 4, 9], [1, 5, 7], [1, 5, 8], [1, 5, 9], [1, 6, 7], [1, 6, 8], [1, 6, 9], [2, 4, 7], [2, 4, 8], [2, 4, 9], [2, 5, 7], [2, 5, 8], [2, 5, 9], [2, 6, 7], [2, 6, 8], [2, 6, 9], [3, 4, 7], [3, 4, 8], [3, 4, 9] , [3, 5, 7], [3, 5, 8], [3, 5, 9], [3, 6, 7], [3, 6, 8], [3, 6, 9]

Я помню, как задавал вопрос на perlmonks.org, прося кого-нибудь объяснить мне это.

Вы можете легко адаптировать это решение к своей проблеме.

6
ответ дан 1 December 2019 в 19:49
поделиться

Теперь в твиттер-форме:

sub prod {reduce {[map {my $ i = $ _; map [@ $ _, $ i], @ $ a} @ $ b]} [[]], @_}

use strict;
use warnings;
use List::Util qw(reduce);

sub cartesian_product {
  reduce {
    [ map {
      my $item = $_;
      map [ @$_, $item ], @$a
    } @$b ]
  } [[]], @_
}
6
ответ дан 1 December 2019 в 19:49
поделиться

Вы можете использовать мой Set :: CrossProduct модуль, если хотите. Вам не нужно перемещаться по всему пространству, поскольку он дает вам итератор, поэтому вы все контролируете.

6
ответ дан 1 December 2019 в 19:49
поделиться
Другие вопросы по тегам:

Похожие вопросы: