Уникальные перестановки с повторяющимися элементами в JavaScript - PullRequest
1 голос
/ 19 июня 2019

Предположим, у нас есть элементы 0 и 1, которые могут встречаться более одного раза, что-то вроде 00 00 11, 00 00 11 11 или 01 11 (разделены на группы по 2 для лучшей читаемости).

У меня уже есть функция для генерации всех уникальных перестановок:

class UniqueElement {
  constructor(value, occurrences) {
    this.value = value;
    this.occurrences = occurrences;
  }
}

function permUnique(elements) {
  let set = new Set(elements);
  let listunique = Array.from(set).map((i) => new UniqueElement(i, elements.filter((el) => el === i).length));
  let u = elements.length;
  return permUniqueHelper(listunique, "0".repeat(u).split("").map((i) => parseInt(i)), u - 1);
}
function* permUniqueHelper(listunique, result_list, d) {
  if (d < 0) {
    yield [...result_list];
  } else {
    for (const i of listunique) {
      if (i.occurrences > 0) {
        result_list[d] = i.value;
        i.occurrences--;
        for (const g of permUniqueHelper(listunique, result_list, d - 1)) yield g;
        i.occurrences++;
      }
    }
  }
}

// call like:
// permUnique([0,0,1,1])
console.log(Array.from(permUnique([0,0,1,1])).join('\n'));

Переведено на JavaScript здесь: https://stackoverflow.com/a/6285203/10362619

Теперь я намерен использовать эти сгенерированные перестановки в качестве индексов для других объектов. Затем они используются в коде, где порядок этих объектов не имеет значения:

10 10
01 01

такие же в конце, например, или

01 20 12
02 10 21
10 21 02
12 01 20
...

для базы с 0, 1 и 2, начиная с 00 11 22.

Они считаются одинаковыми, потому что равные числа находятся в одинаковом положении. Просто переключите два числа (например, 0 на 1), и вы получите другое.

Опять же, все эти примеры просто разбиты на группы по два для лучшей читаемости. 00 11 22 означает то же самое, что и 001122, где каждая цифра является отдельным элементом во входном массиве.

Является ли самый быстрый способ фильтрации элементов после создания перестановок или эти критерии могут быть реализованы в функции?

Редактировать: Не обязательно, чтобы это была функция генератора, которая переставляет массив

Редактировать 2: Чтобы сделать вещи еще более ясными: первый пример, который я привел, имеет шаблон abab, где a может быть либо 0, либо 1, а b - это напротив. Второй пример следует шаблону abcabc, где a, b и c могут быть либо 0, 1 или 2 (но не одинаковыми).

Ответы [ 3 ]

1 голос
/ 21 июня 2019

Имея критерии равенства между различными перестановками, нам нужно установить каноническую форму для каждого шаблона (класс равенства).Затем мы попытаемся только сгенерировать их.

В вашем случае, когда порядок 1 s, 2 s и 3 s не имеет значения, мы могли бы решить, что их соответствующие первые появления должны бытьв порядке 123 и 2 не могут появляться без 1 и 3 не без 2.Итак, является ли шаблон aabcbc, aacbcb, bbacac,… или ccbaba, единственная форма, которую мы хотим сгенерировать, это 112323.Или когда шаблон aaa / bbb / ccc, мы генерируем только 111, но не 222 или 333.

. Затем мы можем написать алгоритм, который следует этим правилам для каноническогоформы:

function patterns(values, len) {
    function* recurse(list, used, depth) {
        if (depth == len) {
            yield list.slice();
        } else {
            for (let j=0; j<used; j++) { // only put already used values at this position
                list[depth] = values[j];
                yield* recurse(list, used, depth+1);
            }
            if (used < values.length) { // or the single next unused value
                list[depth] = values[used];
                yield* recurse(list, used+1, depth+1); // which is counted as used here
            }
        }
    }
    return recurse([], 0, 0);
}

Это просто генерирует все комбинации определенной длины из отличных values, но вы можете использовать тот же подход в вашем алгоритме, который генерирует перестановки из определенного количества неуникальных значений.Это немного усложняется: каноническая форма 121 не может быть достигнута, если у нас есть только доступные значения 122, но шаблон 212 все еще должен быть сгенерирован.Нам нужно настроить наше правило так, чтобы требование к упорядочению было только среди элементов с одинаковым количеством вхождений.Таким образом, мы должны сохранять различные used значения для них.

function permutationPatterns(elements) {
    const len = elements.length;
    const unique = new Map(elements.map(el => [el, {value: el, occurrences: 0}]));
    let max = 0;
    for (const el of elements) {
        const o = unique.get(el);
        max = Math.max(max, ++o.occurrences);
    }
    const byOccurrences = Array.from({length: max+1}, () => ({elements: [], used: 0}));
    for (const o of unique.values()) {
        byOccurrences[o.occurrences].elements.push(o);
    }

    function* generate(list, depth) {
        if (depth == len) {
            yield list.slice();
        } else {
            for (const options of byOccurrences) {
                options.used++;
                for (const option of options.elements.slice(0, options.used)) {
                    if (option.occurrences == 0) continue;
                    option.occurrences--;
                    list[depth] = option.value;
                    yield* generate(list, depth+1);
                    option.occurrences++;
                }
                options.used--;
            }
        }
    }
    return generate([], 0);
}
1 голос
/ 20 июня 2019

Если 12 и 21 одинаковы, как, например, 10 и 01 одинаковы, то не создавайте все из них в первую очередь.Ключевая идея здесь заключается в том, чтобы иметь каноническую форму для каждого из значений, что может, например, заключаться в том, что цифры сортируются по возрастанию.Таким образом, у вас есть только значения 00, 01, 02, 11, 12 и 22.

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

После этого вы можете легко создавать только восходящие формы и использовать один и тот же генератор один раз для своих частей и один раз для всей последовательности:

function* generate(values, len) {
    if (len == 0)
        yield [];
    else
        for (const [i, val] of values.entries())
            for (const rest of generate(values.slice(i), len-1))
                yield [val, ...rest];
}

const duos = Array.from(generate([0, 1, 2], 2), x => x.join(""));
for (const x of generate(duos, 3))
    console.log(x.join(" "))
0 голосов
/ 21 июня 2019

Я теперь обернул функцию генератора в функцию генератора фильтра как @ PatrickRoberts , предложенный в комментариях к вопросу.

class UniqueElement {
	constructor(value, occurrences) {
		this.value = value;
		this.occurrences = occurrences;
	}
}

function uniquePermutations(elements) {
	let set = new Set(elements);
	let listunique = Array.from(set).map((i) => new UniqueElement(i, elements.filter((el) => el === i).length));
	let u = elements.length;
	return uniquePermutationsHelper(listunique, "0".repeat(u).split("").map((i) => parseInt(i)), u - 1);
}
function* uniquePermutationsHelper(listunique, result_list, d) {
	if (d < 0) {
		yield [...result_list];
	} else {
		for (const i of listunique) {
			if (i.occurrences > 0) {
				result_list[d] = i.value;
				i.occurrences--;
				for (const g of uniquePermutationsHelper(listunique, result_list, d - 1)) yield g;
				i.occurrences++;
			}
		}
	}
}
function* uniquePermutationPatterns(elements) {
	let patterns = [];
	for (const perm of uniquePermutations(elements)) {
		// contains all unique elements in the order they appear. call patternVars.indexOf(element) and use its index for the patterns array
		let patternVars = Array.from(new Set(perm));

		let pattern = perm.map((p) => patternVars.indexOf(p));
    // convert the pattern to a number to avoid comparing array references and use the faster number comparison than the slower string comparison
		pattern = parseInt(pattern.join(""), patternVars.length);
		if (!patterns.length || !patterns.includes(pattern)) {
			patterns.push(pattern);
			yield perm;
		}
	}
}

let elements = [0,0,1,1];
let all = [...uniquePermutations(elements)];
let filtered = [...uniquePermutationPatterns(elements)];

console.log(`all permutations: (${all.length})`);
console.log(all.map((i) => i.join("")).join("\n"));
console.log(`\nfiltered permutations: (${filtered.length})`);
console.log(filtered.map((i) => i.join("")).join("\n"));

Но это примерно в 3,2 раза медленнее, чем просто генерация всех перестановок. Что не имеет значения в моем случае. Я только протестировал производительность для массива элементов [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2] и только несколько раз (менее 10)

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