В Perl, как я могу перебрать декартово произведение нескольких множеств? - PullRequest
4 голосов
/ 10 августа 2009

Учитывая x количество массивов, каждое из которых может иметь разное количество элементов, как я могу перебрать все комбинации, где я выбираю один элемент из каждого массива?

Пример:

[   ]   [   ]   [   ]
 foo     cat      1
 bar     dog      2
 baz              3
                  4

Возвращает

[foo]   [cat]   [ 1 ]
[foo]   [cat]   [ 2 ]
  ...
[baz]   [dog]   [ 4 ]

Я делаю это в Perl, кстати.

Ответы [ 5 ]

21 голосов
/ 10 августа 2009

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

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

use Set::Crossproduct;

my $iterator = Set::CrossProduct->new(
    [
        [qw( foo bar baz )],
        [qw( cat dog     )],
        [qw( 1 2 3 4     )],
    ]
    );

while( my $tuple = $iterator->get ) {
    say join ' ', $tuple->@*;
    }
2 голосов
/ 10 августа 2009

Простое рекурсивное решение для произвольного числа списков:

sub permute {
  my ($first_list, @remain) = @_;

  unless (defined($first_list)) {
    return []; # only possibility is the null set
  }

  my @accum;
  for my $elem (@$first_list) {
    push @accum, (map { [$elem, @$_] } permute(@remain));
  }

  return @accum;
}

Не очень простое нерекурсивное решение для произвольного числа списков:

sub make_generator {
  my @lists = reverse @_;

  my @state = map { 0 } @lists;

  return sub {
    my $i = 0;

    return undef unless defined $state[0];

    while ($i < @lists) {
      $state[$i]++;
      last if $state[$i] < scalar @{$lists[$i]};
      $state[$i] = 0;
      $i++;
    }

    if ($i >= @state) {
      ## Sabotage things so we don't produce any more values
      $state[0] = undef;
      return undef;
    }

    my @out;
    for (0..$#state) {
      push @out, $lists[$_][$state[$_]];
    }

    return [reverse @out];
  };
}

my $gen = make_generator([qw/foo bar baz/], [qw/cat dog/], [1..4]);
while ($_ = $gen->()) {
  print join(", ", @$_), "\n";
}
1 голос
/ 10 августа 2009

Рекурсивные и более беглые примеры Perl (с комментариями и документацией) для создания декартового произведения можно найти по адресу http://www.perlmonks.org/?node_id=7366

Пример:

sub cartesian {
    my @C = map { [ $_ ] } @{ shift @_ };

    foreach (@_) {
        my @A = @$_;

        @C = map { my $n = $_; map { [ $n, @$_ ] } @C } @A;
    }

    return @C;
}
0 голосов
/ 26 июля 2018

Вы можете использовать вложенные циклы.

for my $e1 (qw( foo bar baz )) {
for my $e2 (qw( cat dog )) {
for my $e3 (qw( 1 2 3 4 )) {
   my @choice = ($e1, $e2, $e3); 
   ...
}}}

Если вам нужно произвольное количество вложенных циклов, вы можете использовать Algorithm :: Loops 'NestedLoops.

use Algorithm::Loops qw( NestedLoops );

my @lists = (
   [qw( foo bar baz )],
   [qw( cat dog )],
   [qw( 1 2 3 4 )],
);

my $iter = NestedLoops(\@lists);
while ( my @choice = $iter->() ) {
   ...
}
0 голосов
/ 10 августа 2009

Сначала я подумал об одном методе, который использует пару для циклов и без рекурсии.

  1. найти общее количество перестановок
  2. цикл от 0 до total_permutations-1
  3. Обратите внимание, что, взяв модуль индекса цикла число элементов в массиве, вы можете получить каждую перестановку

Пример:

Учитывая A [3], B [2], C [3],

for (index = 0..totalpermutations) {
    print A[index % 3];
    print B[(index / 3) % 2];
    print C[(index / 6) % 3];
}

где, конечно, цикл for можно заменить на цикл над [A B C ...], а небольшую часть можно запомнить. Конечно, рекурсия лучше, но это может быть полезно для языков, в которых рекурсия строго ограничена размером стека.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...