Перестановки в JavaScript?
Я пытаюсь написать функцию, которая делает следующее:
- принимает массив чисел в качестве аргумента (например, [1,2,3,4])
- создает массив всех возможных перестановок [1,2,3,4], причем каждая перестановка имеет длину 4
функция ниже (я нашел ее в интернете) делает это, принимая строку в качестве аргумента и возвращая все перестановки этой строки
Я не мог понять, как изменить его на заставьте его работать с массивом целых чисел (я думаю, что это как-то связано с тем, как некоторые из методов работают по-разному на строках, чем на целых числах, но я не уверен...)
var permArr = [], usedChars = [];
function permute(input) {
var i, ch, chars = input.split("");
for (i = 0; i < chars.length; i++) {
ch = chars.splice(i, 1);
usedChars.push(ch);
if (chars.length == 0)
permArr[permArr.length] = usedChars.join("");
permute(chars.join(""));
chars.splice(i, 0, ch);
usedChars.pop();
}
return permArr
};
примечание: Я ищу, чтобы функция возвращала массивы чисел, не массив строки.
Мне действительно нужно, чтобы решение было в JavaScript. Я уже понял, как это сделать в python
24 ответов
Если вы заметили, код фактически разбивает символы на массив до выполнения любой перестановки, поэтому вы просто удаляете операцию соединения и разделения
var permArr = [],
usedChars = [];
function permute(input) {
var i, ch;
for (i = 0; i < input.length; i++) {
ch = input.splice(i, 1)[0];
usedChars.push(ch);
if (input.length == 0) {
permArr.push(usedChars.slice());
}
permute(input);
input.splice(i, 0, ch);
usedChars.pop();
}
return permArr
};
document.write(JSON.stringify(permute([5, 3, 7, 1])));
немного поздно, но хотелось бы добавить немного более элегантную версию здесь. Может быть любой массив...
function permutator(inputArr) {
var results = [];
function permute(arr, memo) {
var cur, memo = memo || [];
for (var i = 0; i < arr.length; i++) {
cur = arr.splice(i, 1);
if (arr.length === 0) {
results.push(memo.concat(cur));
}
permute(arr.slice(), memo.concat(cur));
arr.splice(i, 0, cur[0]);
}
return results;
}
return permute(inputArr);
}
добавление версии ES6 (2015). Также не изменяет исходный массив. Работает в консоли в Chrome...
const permutator = (inputArr) => {
let result = [];
const permute = (arr, m = []) => {
if (arr.length === 0) {
result.push(m)
} else {
for (let i = 0; i < arr.length; i++) {
let curr = arr.slice();
let next = curr.splice(i, 1);
permute(curr.slice(), m.concat(next))
}
}
}
permute(inputArr)
return result;
}
так...
permutator(['c','a','t']);
дает...
[ [ 'c', 'a', 't' ],
[ 'c', 't', 'a' ],
[ 'a', 'c', 't' ],
[ 'a', 't', 'c' ],
[ 't', 'c', 'a' ],
[ 't', 'a', 'c' ] ]
и...
permutator([1,2,3]);
дает...
[ [ 1, 2, 3 ],
[ 1, 3, 2 ],
[ 2, 1, 3 ],
[ 2, 3, 1 ],
[ 3, 1, 2 ],
[ 3, 2, 1 ] ]
следующий очень эффективный алгоритм использует кучи для генерации всех перестановок N элементов со сложностью выполнения в O (N!):
function permute(permutation) {
var length = permutation.length,
result = [permutation.slice()],
c = new Array(length).fill(0),
i = 1, k, p;
while (i < length) {
if (c[i] < i) {
k = i % 2 && c[i];
p = permutation[i];
permutation[i] = permutation[k];
permutation[k] = p;
++c[i];
i = 1;
result.push(permutation.slice());
} else {
c[i] = 0;
++i;
}
}
return result;
}
console.log(permute([1, 2, 3]));
тот же алгоритм, реализованный как генератор со сложностью пространства в O (N):
function* permute(permutation) {
var length = permutation.length,
c = Array(length).fill(0),
i = 1, k, p;
yield permutation.slice();
while (i < length) {
if (c[i] < i) {
k = i % 2 && c[i];
p = permutation[i];
permutation[i] = permutation[k];
permutation[k] = p;
++c[i];
i = 1;
yield permutation.slice();
} else {
c[i] = 0;
++i;
}
}
}
// Memory efficient iteration through permutations:
for (var permutation of permute([1, 2, 3])) console.log(permutation);
// Simple array conversion:
var permutations = [...permute([1, 2, 3])];
сравнение производительности
не стесняйтесь добавлять свою реализацию к следующему эталоном.js
var inputArray = [1, 2, 3];
var result = inputArray.reduce(function permute(res, item, key, arr) {
return res.concat(arr.length > 1 && arr.slice(0, key).concat(arr.slice(key + 1)).reduce(permute, []).map(function(perm) { return [item].concat(perm); }) || item);
}, []);
alert(JSON.stringify(result));
я улучшил SiGanteng ' s ответ.
теперь можно назвать permute
более одного раза, потому что permArr
и usedChars
очищаются каждый раз.
function permute(input) {
var permArr = [],
usedChars = [];
return (function main() {
for (var i = 0; i < input.length; i++) {
var ch = input.splice(i, 1)[0];
usedChars.push(ch);
if (input.length == 0) {
permArr.push(usedChars.slice());
}
main();
input.splice(i, 0, ch);
usedChars.pop();
}
return permArr;
})();
}
function permute(input) {
var permArr = [],
usedChars = [];
return (function main() {
for (var i = 0; i < input.length; i++) {
var ch = input.splice(i, 1)[0];
usedChars.push(ch);
if (input.length == 0) {
permArr.push(usedChars.slice());
}
main();
input.splice(i, 0, ch);
usedChars.pop();
}
return permArr;
})();
}
document.write(JSON.stringify(permute([5, 3, 7, 1])));
следующая функция переставляет массив любого типа и вызывает указанную функцию обратного вызова для каждой найденной перестановки:
/*
Permutate the elements in the specified array by swapping them
in-place and calling the specified callback function on the array
for each permutation.
Return the number of permutations.
If array is undefined, null or empty, return 0.
NOTE: when permutation succeeds, the array should be in the original state
on exit!
*/
function permutate(array, callback) {
// Do the actual permuation work on array[], starting at index
function p(array, index, callback) {
// Swap elements i1 and i2 in array a[]
function swap(a, i1, i2) {
var t = a[i1];
a[i1] = a[i2];
a[i2] = t;
}
if (index == array.length - 1) {
callback(array);
return 1;
} else {
var count = p(array, index + 1, callback);
for (var i = index + 1; i < array.length; i++) {
swap(array, i, index);
count += p(array, index + 1, callback);
swap(array, i, index);
}
return count;
}
}
if (!array || array.length == 0) {
return 0;
}
return p(array, 0, callback);
}
если вы называете это так:
// Empty array to hold results
var result = [];
// Permutate [1, 2, 3], pushing every permutation onto result[]
permutate([1, 2, 3], function (a) {
// Create a copy of a[] and add that to result[]
result.push(a.slice(0));
});
// Show result[]
document.write(result);
Я думаю, что это будет делать именно то, что вам нужно - заполнить массив под названием result
С перестановками массива [1, 2, 3]. В результате:
[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,2,1],[3,1,2]]
немного более четкий код на JSFiddle:http://jsfiddle.net/MgmMg/6/
большинство ответов на этот вопрос используют дорогостоящие операции, такие как непрерывные вставки и удаления элементов в массиве или повторное копирование массивов.
вместо этого это типичное решение для отступления:
function permute(arr) {
var results = [],
l = arr.length,
used = Array(l), // Array of bools. Keeps track of used items
data = Array(l); // Stores items of the current permutation
(function backtracking(pos) {
if(pos == l) return results.push(data.slice());
for(var i=0; i<l; ++i) if(!used[i]) { // Iterate unused items
used[i] = true; // Mark item as used
data[pos] = arr[i]; // Assign item at the current position
backtracking(pos+1); // Recursive call
used[i] = false; // Mark item as not used
}
})(0);
return results;
}
permute([1,2,3,4]); // [ [1,2,3,4], [1,2,4,3], /* ... , */ [4,3,2,1] ]
поскольку массив результатов будет огромным, было бы неплохо перебирать результаты по одному, а не распределять все данные одновременно. В ES6 это можно сделать с помощью генераторов:
function permute(arr) {
var l = arr.length,
used = Array(l),
data = Array(l);
return function* backtracking(pos) {
if(pos == l) yield data.slice();
else for(var i=0; i<l; ++i) if(!used[i]) {
used[i] = true;
data[pos] = arr[i];
yield* backtracking(pos+1);
used[i] = false;
}
}(0);
}
var p = permute([1,2,3,4]);
p.next(); // {value: [1,2,3,4], done: false}
p.next(); // {value: [1,2,4,3], done: false}
// ...
p.next(); // {value: [4,3,2,1], done: false}
p.next(); // {value: undefined, done: true}
ответ без необходимости внешнего массива или дополнительной функции
function permutator (arr) {
var permutations = [];
if (arr.length === 1) {
return [ arr ];
}
for (var i = 0; i < arr.length; i++) {
var subPerms = permutator(arr.slice(0, i).concat(arr.slice(i + 1)));
for (var j = 0; j < subPerms.length; j++) {
subPerms[j].unshift(arr[i]);
permutations.push(subPerms[j]);
}
}
return permutations;
}
некоторая версия, вдохновленная Haskell:
perms [] = [[]]
perms xs = [ x:ps | x <- xs , ps <- perms ( xs\[x] ) ]
function perms(xs){
if (!xs.length)
return [[]];
var r=[];
for (var i=0;i<xs.length;i++){
var xs_ = xs.slice(),
x = xs_.splice(i, 1),
ps = perms(xs_);
r.push(...ps.map(p=>x.concat(p)))
}
return r;
}
document.write(JSON.stringify(perms([1,2,3])));
вот еще одно "более рекурсивное" решение.
function perms(input) {
var data = input.slice();
var permutations = [];
var n = data.length;
if (n === 0) {
return [
[]
];
} else {
var first = data.shift();
var words = perms(data);
words.forEach(function(word) {
for (var i = 0; i < n; ++i) {
var tmp = word.slice();
tmp.splice(i, 0, first)
permutations.push(tmp);
}
});
}
return permutations;
}
var str = 'ABC';
var chars = str.split('');
var result = perms(chars).map(function(p) {
return p.join('');
});
console.log(result);
выход:
[ 'ABC', 'BAC', 'BCA', 'ACB', 'CAB', 'CBA' ]
function perm(xs) {
return xs.length === 0 ? [[]] : perm(xs.slice(1)).reduce(function (acc, ys) {
for (var i = 0; i < xs.length; i++) {
acc.push([].concat(ys.slice(0, i), xs[0], ys.slice(i)));
}
return acc;
}, []);
}
проверьте его с помощью:
console.log(JSON.stringify(perm([1, 2, 3,4])));
это интересная задача, и вот мой вклад. Это очень просто и быстро. Если интересно, пожалуйста, потерпите и читайте дальше.
если вы хотите, чтобы эта работа быстро, вы определенно должны получить себя в динамическое программирование. Это означает, что вы должны забыть о рекурсивных подходах. Это точно...
OK код le_m который использует метод кучи, кажется, самый быстрый до сих пор. Что ж, у меня нет названия для моего алгоритма., я не знаю, если это уже реализовано или нет, но это очень просто и быстро. Как и все подходы динамического программирования, мы начнем с простейшей задачи и перейдем к конечному результату.
предположим, что у нас есть массив a = [1,2,3]
начнем с
r = [[1]]; // result
t = []; // interim result
затем выполните эти три шага;
- для каждого пункта нашего
r
(result) array добавим следующий элемент входного массива. - мы будем вращать каждый item it's length many times и будет хранить каждый экземпляр в промежуточном массиве результатов
t
. (ну, за исключением первого, чтобы не тратить время на вращение 0) - как только мы закончим со всеми предметами
r
промежуточный массивt
следует провести следующий уровень результатов, чтобы мы сделалиr = t; t = [];
и продолжить до длины входного массиваa
.
Итак, следующие наши шаги;
r array t array
[[1,2]] [[1,2],
[2,1]]
[[1,2],[2,1]] [[1,2,3],[2,3,1],[3,1,2],
[2,1,3],[1,3,2],[3,2,1]]
так вот код
function perm(a){
var r = [[a[0]]],
t = [],
s = [];
if (a.length <= 1) return a;
for (var i = 1, la = a.length; i < la; i++){
for (var j = 0, lr = r.length; j < lr; j++){
r[j].push(a[i]);
t.push(r[j]);
for(var k = 1, lrj = r[j].length; k < lrj; k++){
for (var l = 0; l < lrj; l++) s[l] = r[j][(k+l)%lrj];
t[t.length] = s;
s = [];
}
}
r = t;
t = [];
}
return r;
}
var arr = [0,1,2,4,5];
console.log("The length of the permutation is:",perm(arr).length);
console.time("Permutation test");
for (var z = 0; z < 2000; z++) perm(arr);
console.timeEnd("Permutation test");
в множественном тесте я видел, как он разрешал 120 перестановок [0,1,2,3,4] для 2000 раз в 25~35 мс.
#!/usr/bin/env node
"use strict";
function perm(arr) {
if(arr.length<2) return [arr];
var res = [];
arr.forEach(function(x, i) {
perm(arr.slice(0,i).concat(arr.slice(i+1))).forEach(function(a) {
res.push([x].concat(a));
});
});
return res;
}
console.log(perm([1,2,3,4]));
похоже по духу на решение в стиле Haskell от @crl, но работает с reduce
:
function permutations( base ) {
if (base.length == 0) return [[]]
return permutations( base.slice(1) ).reduce( function(acc,perm) {
return acc.concat( base.map( function(e,pos) {
var new_perm = perm.slice()
new_perm.splice(pos,0,base[0])
return new_perm
}))
},[])
}
большинство других ответов не используют новые функции генератора javascript, что является идеальным решением для этого типа проблемы. Вероятно, вам нужна только одна перестановка во времени в памяти. Кроме того, я предпочитаю генерировать перестановку диапазона индексов, поскольку это позволяет мне индексировать каждую перестановку и переходить прямо к любой конкретной перестановке, а также использоваться для перестановки любой другой коллекции.
// ES6 generator version of python itertools [permutations and combinations]
const range = function*(l) { for (let i = 0; i < l; i+=1) yield i; }
const isEmpty = arr => arr.length === 0;
const permutations = function*(a) {
const r = arguments[1] || [];
if (isEmpty(a)) yield r;
for (let i of range(a.length)) {
const aa = [...a];
const rr = [...r, ...aa.splice(i, 1)];
yield* permutations(aa, rr);
}
}
console.log('permutations of ABC');
console.log(JSON.stringify([...permutations([...'ABC'])]));
const combinations = function*(a, count) {
const r = arguments[2] || [];
if (count) {
count = count - 1;
for (let i of range(a.length - count)) {
const aa = a.slice(i);
const rr = [...r, ...aa.splice(0, 1)];
yield* combinations(aa, count, rr);
}
} else {
yield r;
}
}
console.log('combinations of 2 of ABC');
console.log(JSON.stringify([...combinations([...'ABC'], 2)]));
const permutator = function() {
const range = function*(args) {
let {begin = 0, count} = args;
for (let i = begin; count; count--, i+=1) {
yield i;
}
}
const factorial = fact => fact ? fact * factorial(fact - 1) : 1;
return {
perm: function(n, permutationId) {
const indexCount = factorial(n);
permutationId = ((permutationId%indexCount)+indexCount)%indexCount;
let permutation = [0];
for (const choiceCount of range({begin: 2, count: n-1})) {
const choice = permutationId % choiceCount;
const lastIndex = permutation.length;
permutation.push(choice);
permutation = permutation.map((cv, i, orig) =>
(cv < choice || i == lastIndex) ? cv : cv + 1
);
permutationId = Math.floor(permutationId / choiceCount);
}
return permutation.reverse();
},
perms: function*(n) {
for (let i of range({count: factorial(n)})) {
yield this.perm(n, i);
}
}
};
}();
console.log('indexing type permutator');
let i = 0;
for (let elem of permutator.perms(3)) {
console.log(`${i}: ${elem}`);
i+=1;
}
console.log();
console.log(`3: ${permutator.perm(3,3)}`);
Это очень хороший вариант использования для map / reduce:
function permutations(arr) {
return (arr.length === 1) ? arr :
arr.reduce((acc, cv, index) => {
let remaining = [...arr];
remaining.splice(index, 1);
return acc.concat(permutations(remaining).map(a => [].concat(cv,a)));
}, []);
}
- во-первых, мы обрабатываем базовый случай и просто возвращаем массив, если в нем есть только элемент
- во всех остальных случаях
- мы создаем пустой массив
- петля над входным массивом
- и добавьте массив текущего значения и всех перестановок оставшегося массива
[].concat(cv,a)
Я написал в должности чтобы продемонстрировать, как переставлять массив в JavaScript. Вот код, который это делает.
var count=0;
function permute(pre,cur){
var len=cur.length;
for(var i=0;i<len;i++){
var p=clone(pre);
var c=clone(cur);
p.push(cur[i]);
remove(c,cur[i]);
if(len>1){
permute(p,c);
}else{
print(p);
count++;
}
}
}
function print(arr){
var len=arr.length;
for(var i=0;i<len;i++){
document.write(arr[i]+" ");
}
document.write("<br />");
}
function remove(arr,item){
if(contains(arr,item)){
var len=arr.length;
for(var i = len-1; i >= 0; i--){ // STEP 1
if(arr[i] == item){ // STEP 2
arr.splice(i,1); // STEP 3
}
}
}
}
function contains(arr,value){
for(var i=0;i<arr.length;i++){
if(arr[i]==value){
return true;
}
}
return false;
}
function clone(arr){
var a=new Array();
var len=arr.length;
for(var i=0;i<len;i++){
a.push(arr[i]);
}
return a;
}
просто позвони
переупорядочивание([], [1,2,3,4])
будет работать. Подробнее о том, как это работает, пожалуйста, обратитесь к объяснению в этот пост.
function nPr(xs, r) {
if (!r) return [];
return xs.reduce(function(memo, cur, i) {
var others = xs.slice(0,i).concat(xs.slice(i+1)),
perms = nPr(others, r-1),
newElms = !perms.length ? [[cur]] :
perms.map(function(perm) { return [cur].concat(perm) });
return memo.concat(newElms);
}, []);
}
"use strict";
function getPermutations(arrP) {
var results = [];
var arr = arrP;
arr.unshift(null);
var length = arr.length;
while (arr[0] === null) {
results.push(arr.slice(1).join(''));
let less = null;
let lessIndex = null;
for (let i = length - 1; i > 0; i--) {
if(arr[i - 1] < arr[i]){
less = arr[i - 1];
lessIndex = i - 1;
break;
}
}
for (let i = length - 1; i > lessIndex; i--) {
if(arr[i] > less){
arr[lessIndex] = arr[i];
arr[i] = less;
break;
}
}
for(let i = lessIndex + 1; i<length; i++){
for(let j = i + 1; j < length; j++){
if(arr[i] > arr[j] ){
arr[i] = arr[i] + arr[j];
arr[j] = arr[i] - arr[j];
arr[i] = arr[i] - arr[j];
}
}
}
}
return results;
}
var res = getPermutations([1,2,3,4,5]);
var out = document.getElementById('myTxtArr');
res.forEach(function(i){ out.value+=i+', '});
textarea{
height:500px;
width:500px;
}
<textarea id='myTxtArr'></textarea>
выводит лексикографически упорядоченные перестановки. Работает только с числами. В другом случае необходимо изменить метод swap в строке 34.
let permutations = []
permutate([], {
color: ['red', 'green'],
size: ['big', 'small', 'medium'],
type: ['saison', 'oldtimer']
})
function permutate (currentVals, remainingAttrs) {
remainingAttrs[Object.keys(remainingAttrs)[0]].forEach(attrVal => {
let currentValsNew = currentVals.slice(0)
currentValsNew.push(attrVal)
if (Object.keys(remainingAttrs).length > 1) {
let remainingAttrsNew = JSON.parse(JSON.stringify(remainingAttrs))
delete remainingAttrsNew[Object.keys(remainingAttrs)[0]]
permutate(currentValsNew, remainingAttrsNew)
} else {
permutations.push(currentValsNew)
}
})
}
результат:
[
[ 'red', 'big', 'saison' ],
[ 'red', 'big', 'oldtimer' ],
[ 'red', 'small', 'saison' ],
[ 'red', 'small', 'oldtimer' ],
[ 'red', 'medium', 'saison' ],
[ 'red', 'medium', 'oldtimer' ],
[ 'green', 'big', 'saison' ],
[ 'green', 'big', 'oldtimer' ],
[ 'green', 'small', 'saison' ],
[ 'green', 'small', 'oldtimer' ],
[ 'green', 'medium', 'saison' ],
[ 'green', 'medium', 'oldtimer' ]
]
мой первый вклад на сайте. См.здесь для некоторых чертежей объяснения алгоритма за кодом. Кроме того, согласно тестам, которые я сделал, этот код работает быстрее, чем все другие методы, упомянутые здесь до этой даты, конечно, он минимален, если есть несколько значений, но время увеличивается экспоненциально при добавлении слишком много.
function permutations(arr) {
var finalArr = [];
function iterator(arrayTaken, tree) {
var temp;
for (var i = 0; i < tree; i++) {
temp = arrayTaken.slice();
temp.splice(tree - 1 - i, 0, temp.splice(tree - 1, 1)[0]);
if (tree >= arr.length) {
finalArr.push(temp);
} else {
iterator(temp, tree + 1);
}
}
}
iterator(arr, 1);
return finalArr;
};
const removeItem = (arr, i) => {
return arr.slice(0, i).concat(arr.slice(i+1));
}
const makePermutations = (strArr) => {
const doPermutation = (strArr, pairArr) => {
return strArr.reduce((result, permutItem, i) => {
const currentPair = removeItem(pairArr, i);
const tempResult = currentPair.map((item) => permutItem+item);
return tempResult.length === 1 ? result.concat(tempResult) :
result.concat(doPermutation(tempResult, currentPair));
}, []);
}
return strArr.length === 1 ? strArr :
doPermutation(strArr, strArr);
}
makePermutations(["a", "b", "c", "d"]);
//result: ["abcd", "abdc", "acbd", "acdb", "adbc", "adcb", "bacd", "badc", "bcad", "bcda", "bdac", "bdca", "cabd", "cadb", "cbad", "cbda", "cdab", "cdba", "dabc", "dacb", "dbac", "dbca", "dcab", "dcba"]
здесь очень короткое решение, которое работает только для 1 или 2 длинных строк. Это oneliner, и он пылает быстро, используя ES6 и не зависит от jQuery. Наслаждайтесь:
var p = l => l.length<2 ? [l] : l.length==2 ? [l[0]+l[1],l[1]+l[0]] : Function('throw Error("unimplemented")')();
function swap(array1, index1, index2) {
var temp;
temp = array1[index1];
array1[index1] = array1[index2];
array1[index2] = temp;
}
function permute(a, l, r) {
var i;
if (l == r) {
console.log(a.join(''));
} else {
for (i = l; i <= r; i++) {
swap(a, l, i);
permute(a, l + 1, r);
swap(a, l, i);
}
}
}
permute(["A","B","C", "D"],0,3);
// выполнения образца //более подробную информацию см. По этой ссылке
// http://www.geeksforgeeks.org/write-a-c-program-to-print-all-permutations-of-a-given-string/