Генерация всех перестановок комбинаций символов, когда количество массивов и длина каждого массива неизвестны
Я не уверен, как задать свой вопрос лаконично, поэтому я начну с примеров и расширяюсь оттуда. Я работаю с 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
, который содержит все ваше неизвестное количество массивов неизвестной длины. Тогда вам нужен еще один цикл, нет?