Генерация всех перестановок комбинаций символов, когда количество массивов и длина каждого массива неизвестны

Я не уверен, как задать свой вопрос лаконично, поэтому я начну с примеров и расширяюсь оттуда. Я работаю с VBA, но я думаю, что эта проблема не зависит от языка и требует только яркого ума, который может обеспечить фреймворк псевдокода. Заранее спасибо за помощь!

Пример: У меня есть 3 массива символов, например:

Arr_1 = [X,Y,Z] 
Arr_2 = [A,B]
Arr_3 = [1,2,3,4]

Я хотел бы генерировать ВСЕ возможные перестановки массивов символов так:

XA1
XA2
XA3
XA4
XB1
XB2
XB3
XB4
YA1
YA2
.
.
.
ZB3
ZB4

Это можно легко решить, используя три петли или петли. Мой вопрос: как я могу решить эту проблему, если количество массивов неизвестно и длина каждого массива неизвестна?

Итак, в качестве примера с 4 символьными массивами:

Arr_1 = [X,Y,Z]
Arr_2 = [A,B]
Arr_3 = [1,2,3,4]
Arr_4 = [a,b]

Мне нужно сгенерировать:

XA1a
XA1b
XA2a
XA2b
XA3a
XA3b
XA4a
XA4b
.
.
.
ZB4a
ZB4b  

Таким образом, обобщенный пример:

Arr_1 = [...]
Arr_2 = [...]
Arr_3 = [...]
.
.
.
Arr_x = [...]

Есть ли способ структурировать функцию, которая будет генерировать неизвестное число циклов и прокручивать длину каждого массива для генерации перестановок? Или, может быть, есть лучший способ подумать о проблеме?

Спасибо всем!

Ответ 1

Рекурсивное решение

Это на самом деле самое простое, самое простое решение. В Java должно быть указано следующее:

public class Main {
    public static void main(String[] args) {
        Object[][] arrs = {
            { "X", "Y", "Z" },
            { "A", "B" },
            { "1", "2" },
        };
        recurse("", arrs, 0);
    }
    static void recurse (String s, Object[][] arrs, int k) {
        if (k == arrs.length) {
            System.out.println(s);
        } else {
            for (Object o : arrs[k]) {
                recurse(s + o, arrs, k + 1);
            }
        }
    }
}

(см. полный вывод)

Примечание. Массивы Java основаны на 0, поэтому k переходит из 0..arrs.length-1 во время рекурсии, пока k == arrs.length не закончит рекурсию.


Нерекурсивное решение

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

public class Main {
    public static void main(String[] args) {
        Object[][] arrs = {
            { "X", "Y", "Z" },
            { "A", "B" },
            { "1", "2" },
        };
        int N = 1;
        for (Object[] arr : arrs) {
            N = N * arr.length;
        }
        for (int v = 0; v < N; v++) {
            System.out.println(decode(arrs, v));
        }
    }
    static String decode(Object[][] arrs, int v) {
        String s = "";
        for (Object[] arr : arrs) {
            int M = arr.length;
            s = s + arr[v % M];
            v = v / M;
        }
        return s;
    }
}

(см. полный вывод)

Это создает тупочки в другом порядке. Если вы хотите сгенерировать их в том же порядке, что и рекурсивное решение, то вы переходите через arrs "назад" во время decode следующим образом:

static String decode(Object[][] arrs, int v) {
    String s = "";
    for (int i = arrs.length - 1; i >= 0; i--) {
        int Ni = arrs[i].length;
        s = arrs[i][v % Ni] + s;
        v = v / Ni;
    }
    return s;
}

(см. полный вывод)

Ответ 2

Благодаря @polygenelubricants для отличного решения. Вот эквивалент Javascript:

var a=['0'];

var b=['Auto', 'Home'];

var c=['Good'];

var d=['Tommy', 'Hilfiger', '*'];

var attrs = [a, b, c, d];

function recurse (s, attrs, k) {
    if(k==attrs.length) {
        console.log(s);
    } else {
        for(var i=0; i<attrs[k].length;i++) {
            recurse(s+attrs[k][i], attrs, k+1);
        }
    } 
}
recurse('', attrs, 0);

Ответ 3

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

% perm.rb ruby is cool

~/bin/perm.rb

#!/usr/bin/env ruby

def perm(args)
  peg = Hash[args.collect {|v| [v,0]}]

  nperms= 1
  args.each { |a| nperms  *=  a.length }

  perms = Array.new(nperms, "")

  nperms.times do |p|
    args.each { |a| perms[p] += a[peg[a]] }

    args.each do |a|
      peg[a] += 1
      break  if peg[a] < a.length
      peg[a] = 0
    end

  end
  perms
end

puts perm ARGV

OLD. У меня есть script, чтобы сделать это в MEL (майя Embedded Language). Я попытаюсь перевести что-то вроде C, но не ожидайте, что он будет работать без немного исправления;) Он работает в Maya.

Сначала - сбрасывайте все массивы в один длинный массив с разделителями. (Я оставлю это вам, потому что в моей системе он вырывает значения из пользовательского интерфейса). Таким образом, это означает, что разделители будут занимать дополнительные слоты. Чтобы использовать ваши данные примера выше:

 string delimitedArray[] = {"X","Y","Z","|","A","B","|","1","2","3","4","|"};

Конечно, вы можете объединить столько массивов, сколько захотите.

string[] getPerms( string delimitedArray[]) {

    string result[];
    string delimiter("|");
    string compactArray[]; // will be the same as delimitedArray, but without the "|" delimiters
    int arraySizes[]; // will hold number of vals for each array
    int offsets[]; // offsets will holds the indices where each new array starts.
    int counters[]; // the values that will increment in the following loops, like pegs in each array

    int nPemutations = 1; 
    int arrSize, offset, nArrays;

    // do a prepass to find some information about the structure, and to build the compact array
    for (s in delimitedArray) {
        if (s == delimiter) { 
            nPemutations *= arrSize; // arrSize will have been counting elements 
            arraySizes[nArrays] = arrSize; 
            counters[nArrays] = 0; // reset the counter
            nArrays ++; // nArrays goes up every time we find a new array
            offsets.append(offset - arrSize) ; //its here, at the end of an array that we store the offset of this array
            arrSize=0; 
        } else { // its one of the elements, not a delimiter
            compactArray.append(s);
            arrSize++;
            offset++;
        }       
    }

    // put a bail out here if you like
    if( nPemutations > 256) error("too many permutations " + nPemutations+". max is 256");


    // now figure out the permutations
    for (p=0;p<nPemutations;p++) {
        string perm ="";

        // In each array at the position of that array counter
        for (i=0;i<nArrays ;i++) {
            int delimitedArrayIndex = counters[i] + offsets[i] ;
            // build the string
            perm += (compactArray[delimitedArrayIndex]);

        }
        result.append(perm);

        // the interesting bit
        // increment the array counters, but in fact the program
        // will only get to increment a counter if the previous counter
        // reached the end of its array, otherwise we break
        for (i = 0; i < nArrays; ++i) {
            counters[i] += 1;
            if (counters[i] < arraySizes[i])
                break;
            counters[i] = 0;
        }
    }

    return result;
}

Ответ 4

Если я правильно понял вопрос, я думаю, вы могли бы поместить все свои массивы в другой массив, тем самым создав неровный массив.

Затем прокрутите все массивы в вашем массиве с зубцами, создавая все необходимые вам перестановки.

Это имеет смысл?

Ответ 5

похоже, что вы уже почти поняли.

Что делать, если вы добавили туда еще один массив, назовите его, скажем ArrayHolder, который содержит все ваше неизвестное количество массивов неизвестной длины. Тогда вам нужен еще один цикл, нет?