Перестановки в 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; }
некоторые версии вдохновлены Хаскелл:
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])));
это интересная задача, и вот мой вклад. Это очень просто и быстро. Если интересно, пожалуйста, потерпите меня и читайте дальше.
если вы хотите, чтобы эта работа быстро, вы определенно должны получить себя в динамическое программирование. Это означает, что вы должны забыть о рекурсивных подходах. Это уж точно...
ОК код le_m который использует метод кучи, кажется, самый быстрый до сих пор. Ну у меня нет названия для моего алгоритма, я не знаю, если это уже реализовано или нет, но это очень просто и быстро. Как и во всех подходах к динамическому программированию, мы начнем с самой простой задачи и перейдем к конечному результату.
предположим, что у нас есть массив
a = [1,2,3]
начнем сr = [[1]]; // result t = []; // interim result
затем выполните следующие три шага;
- для каждого элемента нашего
r
(result) array мы добавим следующий элемент входного массива.- мы будем вращать каждый пункт его длина во много раз и будет хранить каждый экземпляр на промежуточный результат массиве
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)
Я написал post чтобы продемонстрировать, как переставлять массив в 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>
выводит лексикографически упорядоченные перестановки. Работает только с цифрами. В другом случае необходимо изменить метод подкачки в строке 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/