Какой хороший, нерекурсивный алгоритм для вычисления декартова произведения?

StackOverflow https://stackoverflow.com/questions/215908

Вопрос

Примечание

Это не вопрос, связанный конкретно с REBOL.Вы можете ответить на него на любом языке.

Предыстория

Тот Самый РЕБОЛ language поддерживает создание языков, специфичных для конкретной предметной области, известных в REBOL как "диалекты" жаргон.Я создал такой диалект для понимания списков, которые изначально не поддерживаются в REBOL.

Для понимания списка необходим хороший алгоритм декартова произведения.

В Чем Проблема

Я использовал метапрограммирование, чтобы решить эту проблему, путем динамического создания и последующего выполнения последовательности вложенных foreach заявления.Это прекрасно работает.Однако, поскольку он динамический, код не очень удобочитаем.REBOL плохо выполняет рекурсию.В нем быстро заканчивается место в стеке и происходит сбой.Таким образом, о рекурсивном решении не может быть и речи.

Таким образом, я хочу заменить свое метапрограммирование читаемым, нерекурсивным, "встроенным" алгоритмом, если это возможно.Решение может быть на любом языке, если я могу воспроизвести его в REBOL.(Я могу читать практически на любом языке программирования:C #, C, C ++, Perl, Oz, Haskell, Erlang, что угодно.)

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

Это было полезно?

Решение

Как насчет чего-то вроде этого:

#!/usr/bin/perl

use strict;
use warnings;

my @list1 = qw(1 2);
my @list2 = qw(3 4);
my @list3 = qw(5 6);

# Calculate the Cartesian Product
my @cp = cart_prod(\@list1, \@list2, \@list3);

# Print the result
foreach my $elem (@cp) {
  print join(' ', @$elem), "\n";
}

sub cart_prod {
  my @sets = @_;
  my @result;
  my $result_elems = 1;

  # Calculate the number of elements needed in the result
  map { $result_elems *= scalar @

Как насчет чего-то вроде этого:

1 3 5
1 3 6
1 4 5
1 4 6
2 3 5
2 3 6
2 4 5
2 4 6

Который производит следующий вывод:

<*> } @sets; return undef if $result_elems == 0; # Go through each set and add the appropriate element # to each element of the result my $scale_factor = $result_elems; foreach my $set (@sets) { my $set_elems = scalar @$set; # Elements in this set $scale_factor /= $set_elems; foreach my $i (0 .. $result_elems - 1) { # Calculate the set element to place in this position # of the result set. my $pos = $i / $scale_factor % $set_elems; push @{$result[$i]}, $set[ $pos ]; } } return @result; }

Который производит следующий вывод:

<*>

Другие советы

в 3 раза быстрее и меньше используется памяти (меньше перерабатывается).

cartesian: func [
 d [block! ] 
 /local len set i res

][
 d: copy d
 len: 1
 res: make block! foreach d d [len: len * length? d]
 len: length? d
 until [
  set: clear []
  loop i: len [insert set d/:i/1   i: i - 1]
  res: change/only res copy set
  loop i: len [
   unless tail? d/:i: next d/:i [break]
   if i = 1 [break]
   d/:i: head d/:i
   i: i - 1
  ]
  tail? d/1
 ]
 head res
]

Для полноты, вот ответ Роберта Гэмбла, переведенный на REBOL:

REBOL []

cartesian: func [
    {Given a block of sets, returns the Cartesian product of said sets.}
    sets [block!] {A block containing one or more series! values}
    /local
        elems
        result
        row
][
    result: copy []

    elems: 1
    foreach set sets [
        elems: elems * (length? set)
    ]

    for n 0 (elems - 1) 1 [
        row: copy []
        skip: elems
        foreach set sets [
            skip: skip / length? set
            index: (mod to-integer (n / skip) length? set) + 1 ; REBOL is 1-based, not 0-based
            append row set/(index)
        ]
        append/only result row
    ]

    result
]

foreach set cartesian [[1 2] [3 4] [5 6]] [
    print set
]

; This returns the same thing Robert Gamble's solution did:

1 3 5
1 3 6
1 4 5
1 4 6
2 3 5
2 3 6
2 4 5
2 4 6

Вот код Java для генерации декартового произведения для произвольного числа множеств с произвольным числом элементов.

в этом примере список < ls " содержит 4 набора (ls1, ls2, ls3 и ls4), как вы можете видеть в "ls" может содержать любое количество наборов с любым количеством элементов.

import java.util.*;

public class CartesianProduct {

    private List <List <String>> ls = new ArrayList <List <String>> ();
    private List <String> ls1 = new ArrayList <String> ();
    private List <String> ls2 = new ArrayList <String> ();
    private List <String> ls3 = new ArrayList <String> ();
    private List <String> ls4 = new ArrayList <String> ();

    public List <String> generateCartesianProduct () {
        List <String> set1 = null;
        List <String> set2 = null;

        ls1.add ("a");
        ls1.add ("b");
        ls1.add ("c");

        ls2.add ("a2");
        ls2.add ("b2");
        ls2.add ("c2");

        ls3.add ("a3");
        ls3.add ("b3");
        ls3.add ("c3");
        ls3.add ("d3");

        ls4.add ("a4");
        ls4.add ("b4");

        ls.add (ls1);
        ls.add (ls2);
        ls.add (ls3);
        ls.add (ls4);

        boolean subsetAvailabe = true;
        int setCount = 0;

        try{    
            set1 = augmentSet (ls.get (setCount++), ls.get (setCount));
        } catch (IndexOutOfBoundsException ex) {
            if (set1 == null) {
                set1 = ls.get(0);
            }
            return set1;
        }

        do {
            try {
                setCount++;      
                set1 = augmentSet(set1,ls.get(setCount));
            } catch (IndexOutOfBoundsException ex) {
                subsetAvailabe = false;
            }
        } while (subsetAvailabe);
        return set1;
    }

    public List <String> augmentSet (List <String> set1, List <String> set2) {

        List <String> augmentedSet = new ArrayList <String> (set1.size () * set2.size ());
        for (String elem1 : set1) {
            for(String elem2 : set2) {
                augmentedSet.add (elem1 + "," + elem2);
            }
        }
        set1 = null; set2 = null;
        return augmentedSet;
    }

    public static void main (String [] arg) {
        CartesianProduct cp = new CartesianProduct ();
        List<String> cartesionProduct = cp.generateCartesianProduct ();
        for (String val : cartesionProduct) {
            System.out.println (val);
        }
    }
}
use strict;

print "@$_\n" for getCartesian(
  [qw(1 2)],
  [qw(3 4)],
  [qw(5 6)],
);

sub getCartesian {
#
  my @input = @_;
  my @ret = map [$_], @{ shift @input };

  for my $a2 (@input) {
    @ret = map {
      my $v = $_;
      map [@$v, $_], @$a2;
    }
    @ret;
  }
  return @ret;
}

выходной сигнал

1 3 5
1 3 6
1 4 5
1 4 6
2 3 5
2 3 6
2 4 5
2 4 6

РЕДАКТИРОВАТЬ: Это решение не работает. Роберт Гэмбл - правильное решение.

Я немного подумал и придумал это решение:

(я знаю, что большинство из вас не знают REBOL, но это довольно читаемый язык.)

REBOL []

sets: [[1 2 3] [4 5] [6]] ; Here's a set of sets
elems: 1
result: copy []
foreach set sets [elems: elems * (length? set)]
for n 1 elems 1 [
    row: copy []
    foreach set sets [
        index: 1 + (mod (n - 1) length? set)
        append row set/(index)
    ]
    append/only result row
]

foreach row result [
    print result
]

Этот код производит:

1 4 6
2 5 6
3 4 6
1 5 6
2 4 6
3 5 6

(Прочитав цифры выше, вы можете подумать, что есть дубликаты. Я сделал. Но их нет.)

Интересно, что этот код использует почти тот же алгоритм (1 + ((n - 1)% 9), который торпедировал мой цифровой корень вопрос.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top