JavaScript中的排列?

Posted

技术标签:

【中文标题】JavaScript中的排列?【英文标题】:Permutations in JavaScript? 【发布时间】:2012-04-15 04:43:59 【问题描述】:

我正在尝试编写一个执行以下操作的函数:

将整数数组作为参数(例如 [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 中做到这一点

【问题讨论】:

如果您正在寻找排列的“连接”变体,即从数组[ 1, 2, 3, 4 ] 生成[ "1234", "1243", "1324",...],请参阅Finding all permutations of array elements as concatenated strings。 【参考方案1】:

我认为下面我的解决方案的唯一区别可能是我在 null 情况之前停止了递归。希望嵌入的 cmets 足以解释。

function Permutations (A) // computes all possible ordered sequences of the entries in array A and returns them as an array of arrays

var perms = [];

for (var i = 0 ; i < A.length ; i++)
    
    var rem = A.slice (0); // copy input array to retain remainder of elements after removing i'th element
    var el = rem.splice (i,1);
    if (A.length == 2) perms.push ([el [0],rem [0]]) // recursion end case
    else 
        
        var sub = Permutations (rem); // recursive call
        for (var s = 0 ; s < sub.length ; s++) // process recursive response, adding el to the start of each returned sequence
            
            sub [s].splice (0,0,el [0]);
            perms.push (sub [s]);
            ;
        ;
    ;

return perms ;

;// end of Permutations function

【讨论】:

【参考方案2】:

这是一个很酷的解决方案

const rotations = ([l, ...ls], right=[]) =>
  l !== void 0 ? [[l, ...ls, ...right], ...rotations(ls, [...right, l])] : []

const permutations = ([x, ...xs]) =>
  x !== void 0 ? permutations(xs).flatMap((p) => rotations([x, ...p])) : [[]]
  
console.log(permutations("cat"))

【讨论】:

【参考方案3】:

一些受 Haskell 启发的版本:

perms [] = [[]]
perms xs = [ x:ps | x <- xs , ps <- perms ( xs\\[x] ) ]

function perms(xs) 
  if (!xs.length) return [[]];
  return xs.flatMap(x => 
    // get permutations of xs without x, then prepend x to each
    return perms(xs.filter(v => v!==x)).map(vs => [x, ...vs]);
  );
  // or this duplicate-safe way, suggested by @M.Charbonnier in the comments
  // return xs.flatMap((x, i) => 
  //   return perms(xs.filter((v, j) => i!==j)).map(vs => [x, ...vs]);
  // );
  // or @user3658510's variant
  // return xs.flatMap((x, i) => 
  //   return perms([...xs.slice(0,i),...xs.slice(i+1)]).map(vs => [x,...vs]);
  // );

document.write(JSON.stringify(perms([1,2,3])));

【讨论】:

最优雅、最简洁的解决方案 但为了避免 xs 包含重复值的问题,我宁愿按索引过滤 @M.Charbonnier 哦,好建议,让我更新一下,谢谢 不错。以下是我的一些内容,可能会对后来的人有所帮助。这时候,对于每一次递归,当被x(第一个元素)过滤时,数组必须在他的所有元素中迭代。这可以通过以下方式避免:perms([...xs.slice(0, i), ...xs.slice(i + 1)]).map(vs =&gt; [x, ...vs])。我真的不知道按照我的建议重新创建带有扩展的数组是否比过滤器更高效,所以我在网上搜索并发现这个测试表明它可能更糟measurethat.net/Benchmarks/ShowResult/213878。所以对于一个非常大的数据集可能是个坏主意:(【参考方案4】:

这只是 delimited 的更简洁的版本

function permutator (inputArr) 
  const result = []

  function permute (arr, m = []) 
    if (arr.length) 
      arr.forEach((item, i) => 
        const restArr = [...arr.slice(0, i), ...arr.slice(i + 1)]
        permute(restArr, [...m, item])
      )
     else 
      result.push(m)
    
  

  permute(inputArr)

  return result

【讨论】:

【参考方案5】:

这是另一个“更递归”的解决方案。

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);

var output = window.document.getElementById('output');
output.innerhtml = result;
&lt;div id="output"&gt;&lt;/div&gt;

输出:

[ 'ABC', 'BAC', 'BCA', 'ACB', 'CAB', 'CBA' ]

【讨论】:

你可以为它做一个组合吗? ***.com/questions/53555563/…【参考方案6】:

我对网站的第一个贡献。另外,根据我做过的测试,这段代码运行速度比之前提到的所有其他方法都快,当然如果值很少,它是最小的,但是当添加太多时,时间会成倍增加。

var result = permutations([1,2,3,4]);

var output = window.document.getElementById('output');
output.innerHTML = JSON.stringify(result);

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;
;
&lt;div id="output"&gt;&lt;/div&gt;

【讨论】:

我添加了性能比较***.com/a/37580979/1647737 - 随时更新。 这个问题的惊人解决方案!!!我花了一段时间来理解这段代码,它非常干净。干得好!【参考方案7】:

这是一个非常简洁和递归的解决方案,它允许您输入类似于统计运算符 nPr 的输出排列的大小。 “5 排列 3”。这允许您获得具有特定大小的所有可能排列。

function generatePermutations(list, size=list.length) 
    if (size > list.length) return [];
    else if (size == 1) return list.map(d=>[d]); 
    return list.flatMap(d => generatePermutations(list.filter(a => a !== d), size - 1).map(item => [d, ...item]));

generatePermutations([1,2,3])

[[1, 2, 3],[1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]

generatePermutations([1,2,3],2)

[[1, 2], [1, 3], [2, 1], [2, 3], [3, 1], [3, 2]]

【讨论】:

【参考方案8】:

当今最快、最(资源)有效和最优雅的版本(2020 年)

function getArrayMutations (arr, perms = [], len = arr.length) 
  if (len === 1) perms.push(arr.slice(0))

  for (let i = 0; i < len; i++) 
    getArrayMutations(arr, perms, len - 1)

    len % 2 // parity dependent adjacent elements swap
      ? [arr[0], arr[len - 1]] = [arr[len - 1], arr[0]]
      : [arr[i], arr[len - 1]] = [arr[len - 1], arr[i]]
  

  return perms


const arrayToMutate = [1, 2, 3, 4, 5, 6, 7, 8, 9]

const startTime = performance.now()
const arrayOfMutations = getArrayMutations(arrayToMutate)
const stopTime = performance.now()
const duration = (stopTime - startTime) / 1000

console.log(`$arrayOfMutations.length.toLocaleString('en-US') permutations found in $duration.toLocaleString('en-US')s`)

【讨论】:

嗨,您介意解释一下len % 2 // parity dependent adjacent elements swap 的含义以及为什么使用它吗? 我的代码使用“堆算法”来生成数组排列。所以,如果你想知道我的代码是如何工作的,请阅读堆算法的解释:en.m.wikipedia.org/wiki/Heap%27s_algorithm 您是否尝试打印结果?数组元素超过10个如何控制最大值?【参考方案9】:

我使用字符串而不是数组,我的算法似乎消耗的时间非常少。我在这里发布我的算法,我测量的时间是否正确?

console.time('process');

var result = []

function swapper(toSwap)
    let start = toSwap[0]
    let end = toSwap.slice(1)
    return end + start



function perm(str)
    let i = str.length
    let filling = i - 1

    let buckets = i*filling
    let tmpSwap = ''
    for(let j=0; j<filling; j++)
        if(j===0)
            result.push(str)
        else
          if(j === 1)
              tmpSwap = swapper(str.slice(1))
              result.push(str[0]+ tmpSwap)
              if(j === filling-1 && result.length < buckets)
                  perm(swapper(str))
              

          else
              tmpSwap = swapper(tmpSwap)
              result.push(str[0]+ tmpSwap)
              if(j === filling-1 && result.length < buckets)
                  perm(swapper(str))
              
          
        
    

    if(result.length = buckets)
      return result
    else
      return 'something went wrong'
    







console.log(perm('abcdefghijk'))

console.timeEnd('process');

【讨论】:

【参考方案10】:

使用 flatMap 的函数式答案:

const getPermutationsFor = (arr, permutation = []) =>
  arr.length === 0
    ? [permutation]
    : arr.flatMap((item, i, arr) =>
        getPermutationsFor(
          arr.filter((_,j) => j !== i),
          [...permutation, item]
        )
      );

【讨论】:

【参考方案11】:

以下非常高效的算法使用Heap's method 生成运行时复杂度为 O(N!) 的 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]));

以generator 实现的相同算法,空间复杂度为 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])];

性能对比

随时将您的实现添加到以下benchmark.js 测试套件:

function permute_SiGanteng(input) 
  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
  
  return permute(input);


function permute_delimited(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);


function permute_monkey(inputArray) 
  return 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);
  , []);


function permute_Oriol(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_MarkusT(input) 
  function permutate(array, callback) 
      function p(array, index, callback) 
          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);
  
  var result = [];
  permutate(input, function(a) 
      result.push(a.slice(0));
  );
  return result;


function permute_le_m(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;


function permute_Urielzen(arr) 
    var finalArr = [];
    var iterator = function (arrayTaken, tree) 
        for (var i = 0; i < tree; i++) 
            var 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;


function permute_Taylor_Hakes(arr) 
  var permutations = [];
  if (arr.length === 1) 
    return [ arr ];
  

  for (var i = 0; i <  arr.length; i++)  
    var subPerms = permute_Taylor_Hakes(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;


var Combinatorics = (function () 
    'use strict';
    var version = "0.5.2";
    /* combinatory arithmetics */
    var P = function(m, n) 
        var p = 1;
        while (n--) p *= m--;
        return p;
    ;
    var C = function(m, n) 
        if (n > m) 
            return 0;
        
        return P(m, n) / P(n, n);
    ;
    var factorial = function(n) 
        return P(n, n);
    ;
    var factoradic = function(n, d) 
        var f = 1;
        if (!d) 
            for (d = 1; f < n; f *= ++d);
            if (f > n) f /= d--;
         else 
            f = factorial(d);
        
        var result = [0];
        for (; d; f /= d--) 
            result[d] = Math.floor(n / f);
            n %= f;
        
        return result;
    ;
    /* common methods */
    var addProperties = function(dst, src) 
        Object.keys(src).forEach(function(p) 
            Object.defineProperty(dst, p, 
                value: src[p],
                configurable: p == 'next'
            );
        );
    ;
    var hideProperty = function(o, p) 
        Object.defineProperty(o, p, 
            writable: true
        );
    ;
    var toArray = function(f) 
        var e, result = [];
        this.init();
        while (e = this.next()) result.push(f ? f(e) : e);
        this.init();
        return result;
    ;
    var common = 
        toArray: toArray,
        map: toArray,
        forEach: function(f) 
            var e;
            this.init();
            while (e = this.next()) f(e);
            this.init();
        ,
        filter: function(f) 
            var e, result = [];
            this.init();
            while (e = this.next()) if (f(e)) result.push(e);
            this.init();
            return result;
        ,
        lazyMap: function(f) 
            this._lazyMap = f;
            return this;
        ,
        lazyFilter: function(f) 
            Object.defineProperty(this, 'next', 
                writable: true
            );
            if (typeof f !== 'function') 
                this.next = this._next;
             else 
                if (typeof (this._next) !== 'function') 
                    this._next = this.next;
                
                var _next = this._next.bind(this);
                this.next = (function() 
                    var e;
                    while (e = _next()) 
                        if (f(e))
                            return e;
                    
                    return e;
                ).bind(this);
            
            Object.defineProperty(this, 'next', 
                writable: false
            );
            return this;
        

    ;
    /* power set */
    var power = function(ary, fun) 
        var size = 1 << ary.length,
            sizeOf = function() 
                return size;
            ,
            that = Object.create(ary.slice(), 
                length: 
                    get: sizeOf
                
            );
        hideProperty(that, 'index');
        addProperties(that, 
            valueOf: sizeOf,
            init: function() 
                that.index = 0;
            ,
            nth: function(n) 
                if (n >= size) return;
                var i = 0,
                    result = [];
                for (; n; n >>>= 1, i++) if (n & 1) result.push(this[i]);
                return (typeof (that._lazyMap) === 'function')?that._lazyMap(result):result;
            ,
            next: function() 
                return this.nth(this.index++);
            
        );
        addProperties(that, common);
        that.init();
        return (typeof (fun) === 'function') ? that.map(fun) : that;
    ;
    /* combination */
    var nextIndex = function(n) 
        var smallest = n & -n,
            ripple = n + smallest,
            new_smallest = ripple & -ripple,
            ones = ((new_smallest / smallest) >> 1) - 1;
        return ripple | ones;
    ;
    var combination = function(ary, nelem, fun) 
        if (!nelem) nelem = ary.length;
        if (nelem < 1) throw new RangeError;
        if (nelem > ary.length) throw new RangeError;
        var first = (1 << nelem) - 1,
            size = C(ary.length, nelem),
            maxIndex = 1 << ary.length,
            sizeOf = function() 
                return size;
            ,
            that = Object.create(ary.slice(), 
                length: 
                    get: sizeOf
                
            );
        hideProperty(that, 'index');
        addProperties(that, 
            valueOf: sizeOf,
            init: function() 
                this.index = first;
            ,
            next: function() 
                if (this.index >= maxIndex) return;
                var i = 0,
                    n = this.index,
                    result = [];
                for (; n; n >>>= 1, i++) 
                    if (n & 1) result[result.length] = this[i];
                

                this.index = nextIndex(this.index);
                return (typeof (that._lazyMap) === 'function')?that._lazyMap(result):result;
            
        );
        addProperties(that, common);
        that.init();
        return (typeof (fun) === 'function') ? that.map(fun) : that;
    ;
    /* permutation */
    var _permutation = function(ary) 
        var that = ary.slice(),
            size = factorial(that.length);
        that.index = 0;
        that.next = function() 
            if (this.index >= size) return;
            var copy = this.slice(),
                digits = factoradic(this.index, this.length),
                result = [],
                i = this.length - 1;
            for (; i >= 0; --i) result.push(copy.splice(digits[i], 1)[0]);
            this.index++;
            return (typeof (that._lazyMap) === 'function')?that._lazyMap(result):result;
        ;
        return that;
    ;
    // which is really a permutation of combination
    var permutation = function(ary, nelem, fun) 
        if (!nelem) nelem = ary.length;
        if (nelem < 1) throw new RangeError;
        if (nelem > ary.length) throw new RangeError;
        var size = P(ary.length, nelem),
            sizeOf = function() 
                return size;
            ,
            that = Object.create(ary.slice(), 
                length: 
                    get: sizeOf
                
            );
        hideProperty(that, 'cmb');
        hideProperty(that, 'per');
        addProperties(that, 
            valueOf: function() 
                return size;
            ,
            init: function() 
                this.cmb = combination(ary, nelem);
                this.per = _permutation(this.cmb.next());
            ,
            next: function() 
                var result = this.per.next();
                if (!result) 
                    var cmb = this.cmb.next();
                    if (!cmb) return;
                    this.per = _permutation(cmb);
                    return this.next();
                
                return (typeof (that._lazyMap) === 'function')?that._lazyMap(result):result;
            
        );
        addProperties(that, common);
        that.init();
        return (typeof (fun) === 'function') ? that.map(fun) : that;
    ;

    /* export */
    var Combinatorics = Object.create(null);
    addProperties(Combinatorics, 
        C: C,
        P: P,
        factorial: factorial,
        factoradic: factoradic,
        permutation: permutation,
    );
    return Combinatorics;
)();

function permute_Technicalbloke(inputArray) 
  if (inputArray.length === 1) return inputArray;
  return inputArray.reduce( function(accumulator,_,index)
    permute_Technicalbloke([...inputArray.slice(0,index),...inputArray.slice(index+1)])
    .map(value=>accumulator.push([inputArray[index],value]));
    return accumulator;
  ,[]);


var suite = new Benchmark.Suite;
var input = [0, 1, 2, 3, 4];

suite.add('permute_SiGanteng', function() 
    permute_SiGanteng(input);
  )
  .add('permute_delimited', function() 
    permute_delimited(input);
  )
  .add('permute_monkey', function() 
    permute_monkey(input);
  )
  .add('permute_Oriol', function() 
    permute_Oriol(input);
  )
  .add('permute_MarkusT', function() 
    permute_MarkusT(input);
  )
  .add('permute_le_m', function() 
    permute_le_m(input);
  )
  .add('permute_Urielzen', function() 
    permute_Urielzen(input);
  )
  .add('permute_Taylor_Hakes', function() 
    permute_Taylor_Hakes(input);
  )
  .add('permute_Combinatorics', function() 
    Combinatorics.permutation(input).toArray();
  )
  .add('permute_Technicalbloke', function() 
    permute_Technicalbloke(input);
  )
  .on('cycle', function(event) 
    console.log(String(event.target));
  )
  .on('complete', function() 
    console.log('Fastest is ' + this.filter('fastest').map('name'));
  )
  .run(async: true);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/platform/1.3.4/platform.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/benchmark/2.1.4/benchmark.min.js"></script>

Chrome 48 的运行时结果:

  99.152ms 这个算法  153.115ms MarkusT  401.255ms js-combinatorics  497.037ms Urielzen  925.669ms Oriol 1026.571ms SiGanteng 2529.841ms delimited 3992.622ms monkey 4514.665ms Oriol

【讨论】:

如何更改此代码以提供固定 n = 2 的结果?例如,假设我们有一组三个字母:A、B 和 C。我们可能会问有多少种方法可以排列该集合中的 2 个字母。每个可能的排列都是排列的一个例子。可能排列的完整列表是:AB、AC、BA、BC、CA 和 CB。 @a4xrbj1 参见例如此问题中的代码示例:***.com/questions/37892738/… - 或者您是否专门询问修改此(堆)方法? @le_m 是的,特别是使用这个(堆的)方法,因为它太快了 @a4xrbj1 我将使用与上面给出的链接类似的策略来计算固定长度 n 的所有组合(例如 AB、AC、BC 用于 n = 2)(另请参见 ***.com/questions/127704/…),然后计算每个组合使用 Heap 方法计算其所有排列。 n = 2等特殊情况当然可以优化。 生成器版本不能正常工作,你应该做yield permutation.slice()如果你不切片你只会得到最后一个排列的计算。【参考方案12】:

这是我做的...

const permute = (ar) =>
  ar.length === 1 ? ar : ar.reduce( (ac,_,i) =>
    permute([...ar.slice(0,i),...ar.slice(i+1)]).map(v=>ac.push([].concat(ar[i],v))); return ac;,[]);

又来了,但写得不那么简洁了!...

function permute(inputArray) 
  if (inputArray.length === 1) return inputArray;
  return inputArray.reduce( function(accumulator,_,index)
    permute([...inputArray.slice(0,index),...inputArray.slice(index+1)])
      .map(value=>accumulator.push([].concat(inputArray[index],value)));
    return accumulator;
  ,[]);

它是如何工作的:如果数组比一个元素长,它会遍历每个元素,并通过对自身的递归调用将其连接起来,其余元素作为它的参数。它不会改变原始数组。

【讨论】:

【参考方案13】:

这是 Heap 算法的一个实现(类似于 @le_m 的),除了它是递归的。

function permute_kingzee(arr,n=arr.length,out=[]) 
    if(n == 1) 
        return out.push(arr.slice());
     else 
        for(let i=0; i<n; i++) 
            permute_kingzee(arr,n-1, out);
            let j = ( n % 2 == 0 ) ? i : 0;
            let t = arr[n-1];
            arr[n-1] = arr[j];
            arr[j] = t;
        
        return out;
    

它看起来也相当快:https://jsfiddle.net/3brqzaLe/

【讨论】:

【参考方案14】:

我在制作一个试图简洁但易读的纯函数式编程的版本方面很擅长。

function stringPermutations ([...input]) 
  if (input.length === 1) return input;

  return input
    .map((thisChar, index) => 
      const remainingChars = [...input.slice(0, index), ...input.slice(index + 1)];
      return stringPermutations(remainingChars)
        .map(remainder => thisChar + remainder);
    )
    .reduce((acc, cur) => [...acc, ...cur]);

请注意,参数格式化会将输入字符串转换为数组。不确定这是否有点太神奇.. 不确定我在野外见过它。为了真正的可读性,我可能会在函数的第一行使用input = [...input]

【讨论】:

【参考方案15】:

很晚了。还是以防万一这对任何人都有帮助。

function permute(arr) 
  if (arr.length == 1) return arr

  let res = arr.map((d, i) => permute([...arr.slice(0, i),...arr.slice(i + 1)])
                              .map(v => [d,v].join(''))).flat()

  return res


console.log(permute([1,2,3,4]))

【讨论】:

【参考方案16】:

const permutations = array => 
  let permut = [];
  helperFunction(0, array, permut);
  return permut;
;

const helperFunction = (i, array, permut) => 
  if (i === array.length - 1) 
    permut.push(array.slice());
   else 
    for (let j = i; j < array.length; j++) 
      swapElements(i, j, array);
      helperFunction(i + 1, array, permut);
      swapElements(i, j, array);
    
  
;

function swapElements(a, b, array) 
  let temp = array[a];
  array[a] = array[b];
  array[b] = temp;


console.log(permutations([1, 2, 3]));

【讨论】:

【参考方案17】:

这是一项有趣的任务,这是我的贡献。它非常简单快捷。如果有兴趣,请耐心等待并继续阅读。

如果您想快速完成这项工作,您肯定必须让自己进入动态编程。这意味着您应该忘记递归方法。那是肯定的……

OK le_m's code 使用堆的方法似乎是迄今为止最快的。好吧,我的算法没有名字,我不知道它是否已经实现,但它非常简单和快速。与所有动态规划方法一样,我们将从最简单的问题开始,并寻求最终结果。

假设我们有一个a = [1,2,3] 数组,我们将从

r = [[1]]; // result
t = [];    // interim result

然后按照这三个步骤进行;

    对于r(结果)数组的每一项,我们将添加输入数组的下一项。 我们将多次旋转每个项目的长度并将每个实例存储在中间结果数组t 中。 (好吧,除了第一个不要在 0 旋转上浪费时间)

所以以下是我们的步骤;

r array   | push next item to |  get length many rotations
          |  each sub array   |       of each subarray
-----------------------------------------------------------
[[1]]     |     [[1,2]]       |     [[1,2],[2,1]]
----------|-------------------|----------------------------
[[1,2],   |     [[1,2,3],     |     [[1,2,3],[2,3,1],[3,1,2],
 [2,1]]   |      [2,1,3]]     |      [2,1,3],[1,3,2],[3,2,1]]
----------|-------------------|----------------------------
previous t|                   |
-----------------------------------------------------------

这里是代码

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");

在多次测试中,我看到它在 25~35 毫秒内解决了 2000 次 [0,1,2,3,4] 的 120 次排列。

【讨论】:

对于不同的长度/预热迭代等,它似乎运行得非常快,有时更快,有时比 FF/Ubuntu 上的 Heap 方法慢。需要一个 jsperf 来查看不同引擎的结果。 @le_m 好的,我已经完成了some test @JSBen 在 Ubuntu 和 AMD CPU 上:使用 Chrome rotatePerm(上面的那个)始终快 1.2。 FF 没有一致性。经过多次测试,有时heapPerm 快 2 倍,有时 rotatePerm 快 1.1 倍。使用其他 web-kit 浏览器,例如 Opera 或 Epiphany rotatePerm,速度始终是 1.1 倍。但是,使用 Edge heapPerm 每次都始终快 1.2 倍。 不错!似乎——至少在 FF/Ubuntu 上——堆方法的性能主要取决于数组复制的性能。我修改了您的基准以比较切片与推送:jsben.ch/#/x7mYh - 在 FF 和小型输入数组上,推送似乎更快 如果堆方法可以在性能方面被击败,那就太好了。顺便说一句,您的方法生成的输出与我在 1977 年用作 Heap 方法参考的同一篇论文中的 Langdon 算法(第 16 页)相同:homepage.math.uiowa.edu/~goodman/22m150.dir/2007/… @le_m 我刚刚检查过,似乎是一回事。我似乎像他实施的那样进行轮换。只是延迟了40年。正如我在回答中提到的,这实际上是一种非常简单的方法。仅当快速旋转可用时才被提及。目前我正在使用 Haskell,它有一个内置的方法可以无限期地制作列表(比如说数组)cycle(惰性求值使得无限重复没问题),这可能会派上用场。然而,Haskell 已经有了一个标准的permutations 函数:)【参考方案18】:
perm = x => x[0] ?  x.reduce((a, n) => (perm(x.filter(m => m!=n)).forEach(y => a.push([n,...y])), a), []): [[]]

【讨论】:

你能补充说明吗? 虽然这个答案可能解决了这个问题,但它没有解释它如何或为什么这样做。【参考方案19】:

这是一个最小的 ES6 版本。可以从 Lodash 中拉取 flatten 和 without 函数。

const flatten = xs =>
    xs.reduce((cum, next) => [...cum, ...next], []);

const without = (xs, x) =>
    xs.filter(y => y !== x);

const permutations = xs =>
    flatten(xs.map(x =>
        xs.length < 2
            ? [xs]
            : permutations(without(xs, x)).map(perm => [x, ...perm])
    ));

结果:

permutations([1,2,3])
// [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]

【讨论】:

【参考方案20】:
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"]

【讨论】:

【参考方案21】:

这是一个非常好的 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)));
    , []);

首先,我们处理基本情况,如果其中只有 on 项,则简单地返回数组 在所有其他情况下 我们创建一个空数组 循环输入数组 并添加当前值的数组和剩余数组[].concat(cv,a)的所有排列

【讨论】:

【参考方案22】:
#!/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]));

【讨论】:

【参考方案23】:

大多数其他答案都没有使用新的 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)`);

【讨论】:

【参考方案24】:
  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' ] 
]

【讨论】:

【参考方案25】:

这是一个非常简短的解决方案,仅适用于 1 或 2 个长字符串。它是一个单线器,而且速度非常快,使用 ES6 而不是依赖于 jQuery。享受:

var p = l => l.length<2 ? [l] : l.length==2 ? [l[0]+l[1],l[1]+l[0]] : Function('throw Error("unimplemented")')();

【讨论】:

您的意思是“字符串一到两个字符长”,因为“1 或 2 个长字符串”还有其他含义。【参考方案26】:

有点晚了,但想在这里添加一个更优雅的版本。可以是任意数组...

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 ] ]

【讨论】:

如果您有一个方便的阶乘函数(考虑到您正在处理排列很可能),您可以通过将外部范围初始化更改为 var results = new Array(factorial(inputArr.length)), length=0 来加快它,然后将 results.push(…) 替换为results[length++]=… var cur, memo = memo || [];做什么? @user2965967 声明了 cur 和 memo,并且初始化 memo 为 memo 的值,除非它是 false(包括 undefined),在这种情况下它将是一个空数组。换句话说,为函数参数提供默认值是一种不太理想的方式。 permute(curr.slice(), m.concat(next)) 中的slice() 真的有必要吗? 我们如何设置排列的长度?【参考方案27】:

在精神上类似于 @crl 的 Haskell 风格解决方案,但使用 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
    ))
  ,[])    

【讨论】:

【参考方案28】:
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/

【讨论】:

【参考方案29】:
   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])));

【讨论】:

【参考方案30】:

"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;
&lt;textarea id='myTxtArr'&gt;&lt;/textarea&gt;

输出按字典顺序排列的排列。仅适用于数字。在其他情况下,您必须在第 34 行更改交换方法。

【讨论】:

以上是关于JavaScript中的排列?的主要内容,如果未能解决你的问题,请参考以下文章

用 Javascript 中的堆算法解决排列问题

javascript javascipt中的排列

javascript reverse()反序排列数组中的元素

Javascript中的堆算法a.k.a. all permutations

递归打印字符串的所有排列(Javascript)

堆的算法排列 JavaScript 和递归的堆栈?