¿Permutaciones en JavaScript?


138

Estoy tratando de escribir una función que haga lo siguiente:

  • toma una matriz de enteros como argumento (por ejemplo, [1,2,3,4])
  • crea una matriz de todas las permutaciones posibles de [1,2,3,4], con cada permutación con una longitud de 4

la función a continuación (la encontré en línea) hace esto tomando una cadena como argumento y devolviendo todas las permutaciones de esa cadena

No pude descubrir cómo modificarlo para que funcione con una variedad de enteros (creo que esto tiene algo que ver con la forma en que algunos de los métodos funcionan de manera diferente en cadenas que en enteros, pero no estoy seguro. ..)

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

Nota: Estoy buscando hacer que la función devuelva matrices de enteros , no una matriz de cadenas .

Realmente necesito la solución para estar en JavaScript. Ya he descubierto cómo hacer esto en Python

Respuestas:


106

Si observa, el código divide los caracteres en una matriz antes de realizar cualquier permutación, por lo que simplemente elimina la operación de unión y división

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


@SiGanteng. Algo extraño me está pasando tratando de usar tu función. Lo guardo en un .js donde tengo toda mi "función de manipulación de listas". Si lo uso con permute ([1,2,3]), y luego permute ([4,5,6]), la salida de la última todavía tiene el resultado, salida de la primera. Alguna idea de como arreglarlo ? Muchas gracias !
500


15
Accediendo a globales en su función, ¡mala forma!
Shmiddty

123

Poco tarde, pero me gusta agregar una versión un poco más elegante aquí. Puede ser cualquier conjunto ...

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

Agregar una versión ES6 (2015). Tampoco muta la matriz de entrada original. Funciona en la consola en 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;
}

Entonces...

permutator(['c','a','t']);

Rendimientos ...

[ [ 'c', 'a', 't' ],
  [ 'c', 't', 'a' ],
  [ 'a', 'c', 't' ],
  [ 'a', 't', 'c' ],
  [ 't', 'c', 'a' ],
  [ 't', 'a', 'c' ] ]

Y...

permutator([1,2,3]);

Rendimientos ...

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

1
Si tiene una función factorial a mano (como es bastante probable teniendo en cuenta que está lidiando con permutaciones), puede acelerarla cambiando la inicialización del alcance externo a var results = new Array(factorial(inputArr.length)), length=0, luego reemplácela results.push(…)conresults[length++]=…
Cyoce

1
¿Qué hace la línea var cur, memo = memo || [];?
Ricevind

2
@ user2965967 Declara cur y memo, e inicializa memo para que sea el valor de memo, a menos que sea falsey (incluso indefinido), en cuyo caso será una matriz vacía. En otras palabras, es una forma menos que ideal de proporcionar el parámetro de función con un valor predeterminado.
Sr. Lavalamp

Esto modifica la matriz original.
Shmiddty

2
es el slice()de permute(curr.slice(), m.concat(next))realmente necesario?
Yoav

82

El siguiente algoritmo muy eficiente utiliza el método de Heap para generar todas las permutaciones de N elementos con complejidad de tiempo de ejecución en 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]));

El mismo algoritmo implementado como generador con complejidad espacial en O (N):

Comparación de rendimiento

Siéntase libre de agregar su implementación al siguiente conjunto de pruebas benchmark.js :

Resultados en tiempo de ejecución para Chrome 48:


1
¿Cómo se puede cambiar este código para entregar resultados para un n = 2 fijo? Por ejemplo, supongamos que tenemos un conjunto de tres letras: A, B y C. Podríamos preguntar cuántas formas podemos organizar 2 letras de ese conjunto. Cada posible arreglo sería un ejemplo de permutación. La lista completa de posibles permutaciones sería: AB, AC, BA, BC, CA y CB.
a4xrbj1

1
@ a4xrbj1 Vea, por ejemplo, el ejemplo de código en esta pregunta: stackoverflow.com/questions/37892738/… - ¿o está preguntando específicamente sobre la modificación de este método (del montón)?
le_m

@le_m sí, específicamente usando este método (del montón) ya que es muy rápido
a4xrbj1

@ a4xrbj1 Calcularía todas las combinaciones de longitud fija n (por ejemplo, AB, AC, BC para n = 2) utilizando una estrategia similar al enlace anterior (ver también stackoverflow.com/questions/127704/… ) y luego para cada combinación Calcule todas sus permutaciones utilizando el método de Heap. Por supuesto, casos especiales como n = 2 pueden optimizarse.
le_m

1
La versión del generador no funciona correctamente, debe hacerlo yield permutation.slice()si no corta, solo se calcula la última permutación.
Beldar

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

10
Wow, a pesar de su brevedad y falta de documentos, creo que esta es la respuesta más elegante. Mi explicación de este algoritmo es: Para cada elemento de la matriz (reducir), seleccione todos los demás elementos, permútelos (recursivamente) y concatene a este elemento.
aaron

Intenté esta solución aquí: codewars.com/kata/reviews/5254ca2719453dcc0b000280/groups/ ... Desenvolví el código de golf original en uno legible, pero es esencialmente el mismo. El problema con esto es que produce duplicados, y tuve que hacer un adicional .filter(uniq)en el resultado.
Andrey Mikhaylov - lolmaus

1
¿hay un ceceo paralelo al concepto [1,2,3].length == 3 && "foo" || "bar"o [1,2].length == 3 && "foo" || "bar"oh my! ¡Ahi esta! (or (and (= 3 2) (print "hello!")) (print "goodbye"))
Dmitry

@ lolmaus-AndreyMikhaylov cómo eliminar la duplicación, actualice la respuesta si puede
Pardeep Jain

@PardeepJain Di un enlace a mi solución anterior.
Andrey Mikhaylov - lolmaus

21

He mejorado la respuesta de SiGanteng .

Ahora es posible llamar permutemás de una vez, porque permArry usedCharsse borran cada vez.

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


10

La siguiente función permuta una matriz de cualquier tipo y llama a una función de devolución de llamada específica en cada permutación encontrada:

/*
  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);
  }

Si lo llamas así:

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

Creo que hará exactamente lo que necesita: llenar una matriz llamada resultcon las permutaciones de la matriz [1, 2, 3]. El resultado es:

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

Código ligeramente más claro en JSFiddle: http://jsfiddle.net/MgmMg/6/


10

La mayoría de las respuestas a esta pregunta utilizan operaciones costosas como inserciones y eliminaciones continuas de elementos en una matriz, o copiar matrices reiteradamente.

En cambio, esta es la solución típica de retroceso:

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

Dado que la matriz de resultados será enorme, podría ser una buena idea iterar los resultados uno por uno en lugar de asignar todos los datos simultáneamente. En ES6, esto se puede hacer con generadores:

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}

6

Esta es una tarea interesante y aquí está mi contribución. Es muy simple y rápido. Si está interesado, tenga paciencia conmigo y siga leyendo.

Si desea realizar este trabajo rápidamente, definitivamente debe dedicarse a la programación dinámica. Lo que significa que debes olvidarte de los enfoques recursivos. Eso es seguro...

OK, el código de le_m que usa el método Heap parece ser el más rápido hasta ahora. Bueno, no tengo un nombre para mi algoritmo, no sé si ya se implementó o no, pero es muy simple y rápido. Como con todos los enfoques de programación dinámica, comenzaremos con el problema más simple e iremos al resultado final.

Suponiendo que tenemos una variedad de a = [1,2,3], comenzaremos con

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

Luego siga estos tres pasos;

  1. Para cada elemento de nuestra rmatriz (resultado) agregaremos el siguiente elemento de la matriz de entrada.
  2. Rotaremos cada elemento su longitud muchas veces y almacenaremos cada instancia en la matriz de resultados provisional t. (bueno, excepto el primero para no perder el tiempo con 0 rotación)
  3. Una vez que terminemos con todos los elementos de rla matriz provisional, tdeberíamos tener el siguiente nivel de resultados, así que hacemos r = t; t = [];y continuamos hasta la longitud de la matriz de entrada a.

Entonces los siguientes son nuestros pasos;

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

Entonces aquí está el código

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

En pruebas múltiples, lo he visto resolver las 120 permutaciones de [0,1,2,3,4] durante 2000 veces en 25 ~ 35 ms.


1
Parece funcionar muy rápido, a veces más rápido, a veces más lento que el método Heap en FF / Ubuntu para diferentes iteraciones de longitud / calentamiento, etc. Necesitaría un jsperf para ver resultados para diferentes motores.
le_m

1
@le_m OK, he hecho algunas pruebas @JSBen en Ubuntu y CPU AMD: Con Chrome rotatePerm(el anterior) es consistentemente 1.2 más rápido. Con FF no hay consistencia. Después de varias pruebas, a veces heapPermes 2 veces más rápido, algunas veces rotatePermes 1,1 veces más rápido. Con otros navegadores de kits web como Opera o Epiphany, rotatePermresulta ser 1.1 veces más rápido. Sin embargo, con Edge heapPermes constantemente 1.2 veces más rápido cada vez.
Reducir

1
¡Agradable! Parece que, al menos en FF / Ubuntu, el rendimiento del método de almacenamiento dinámico depende principalmente del rendimiento de la copia de la matriz. Modifiqué su punto de referencia para comparar el corte en comparación con el empuje: jsben.ch/#/x7mYh - en FF y para matrices de entrada pequeñas, empujar parece mucho más rápido
le_m

2
Sería genial si el método del montón se pudiera superar en términos de rendimiento. Por cierto, su método genera el mismo resultado que el algoritmo de Langdon (página 16) del mismo artículo de 1977 que utilicé como referencia para el método de Heap: homepage.math.uiowa.edu/~goodman/22m150.dir/2007/…
le_m

2
@le_m Acabo de comprobar y parece ser lo mismo. Parece que hago rotación como él implementó. Solo con 40 años de retraso. Como he mencionado en mi respuesta, de hecho es un método muy simple. Mencionado como la opción solo cuando la rotación rápida está disponible. Actualmente estoy en Haskell y tiene un método incorporado para hacer un ciclo de lista (digamos matriz) indefinidamente (la evaluación perezosa hace una repetición infinita sin problema) y esto podría ser útil. Sin embargo, Haskell ya tiene una permutationsfunción estándar :)
Redu

6

Alguna versión inspirada de Haskell:

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

function perms(xs) {
  if (!xs.length) return [[]];
  return xs.flatMap((xi, i) => {
    // get permutations of xs without its i-th item, then prepend xi to each
    return perms([...xs.slice(0,i), ...xs.slice(i+1)]).map(xsi => [xi, ...xsi]);
  });
}
document.write(JSON.stringify(perms([1,2,3])));


5

Responda sin la necesidad de una matriz exterior o función adicional

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

¿Puedes hacer una combinación de ella? stackoverflow.com/questions/53555563/…
Techdive

5

La versión más rápida, más efectiva (recursos) y más elegante hoy en día (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`)


Hola, ¿te importaría explicar qué len % 2 // parity dependent adjacent elements swapsignifica y por qué se usa?
Pramesh Bajracharya

Mi código utiliza el "algoritmo del montón" para generar las permutaciones de la matriz. Entonces, si desea saber cómo funciona mi código, lea esta explicación del algoritmo de Heap: en.m.wikipedia.org/wiki/Heap%27s_algorithm
Vladislav Ladicky

¿Intentaste imprimir el resultado? ¿Cómo controlar el máximo si los elementos de la matriz son superiores a 10?
Marvix

4

Aquí hay una solución genial

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

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


2

Aquí hay otra solución "más recursiva".

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

Salida:

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

¿Puedes hacer una combinación para ello? stackoverflow.com/questions/53555563/…
Techdive

2
   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;
      }, []);
    }

Pruébalo con:

console.log(JSON.stringify(perm([1, 2, 3,4])));

2

La mayoría de las otras respuestas no utilizan las nuevas funciones del generador de JavaScript, que es una solución perfecta para este tipo de problema. Probablemente solo necesite una permutación a la vez en la memoria. Además, prefiero generar una permutación de una gama de índices, ya que esto me permite indexar cada permutación y saltar directamente a cualquier permutación en particular, así como ser utilizada para permutar cualquier otra colección.

// 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)}`);


2
#!/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]));

2

Aquí hay uno que hice ...

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;},[]);

¡Y aquí está otra vez, pero menos escrito!

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;
  },[]);
}

Cómo funciona: si la matriz es más larga que un elemento, recorre cada elemento y lo concatena con una llamada recursiva a sí mismo con los elementos restantes como argumento. No muta la matriz original.


2

Respuesta funcional usando flatMap:

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

1

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

Produce permutaciones ordenadas lexicográficamente. Funciona solo con números. En otro caso, debe cambiar el método de intercambio en la línea 34.


1

Similar en espíritu a la solución de estilo Haskell de @crl, pero trabajando con 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
    }))
  },[])    
}

1

Este es un caso de uso muy bueno para 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)));
    }, []);
}
  • Primero, manejamos el caso base y simplemente devolvemos la matriz si solo hay un elemento en ella
  • En todos los otros casos
    • creamos una matriz vacía
    • recorrer el conjunto de entrada
    • y agregue una matriz del valor actual y todas las permutaciones de la matriz restante [].concat(cv,a)

1

Aquí hay una versión mínima de ES6. El aplanamiento y sin funciones se pueden extraer de Lodash.

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

Resultado:

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

1
perm = x => x[0] ?  x.reduce((a, n) => (perm(x.filter(m => m!=n)).forEach(y => a.push([n,...y])), a), []): [[]]

2
¿Puedes agregar una explicación por favor?
Mehdi Bounya

3
Si bien esta respuesta puede resolver la pregunta, no contiene ninguna explicación de cómo o por qué lo hace.
samlev

1

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


1

Un poco tarde. Todavía por si acaso si esto ayuda a alguien.

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


1

Tuve una grieta al hacer una versión de esto que intenta ser concisa pero legible, y una programación puramente funcional.

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

Tenga en cuenta que el formato del argumento convierte una cadena de entrada en una matriz. No estoy seguro si eso es demasiado mágico ... No estoy seguro de haberlo visto en la naturaleza. Para una legibilidad real, probablemente lo haría input = [...input]para la primera línea de la función.


1

Esta es una implementación del algoritmo de Heap (similar a @ le_m's), excepto que es recursivo.

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

Parece que también es bastante más rápido: https://jsfiddle.net/3brqzaLe/


1

Mi primera contribución al sitio. Además, según las pruebas que he realizado, este código se ejecuta más rápido que todos los otros métodos mencionados aquí antes de esta fecha, por supuesto, es mínimo si hay pocos valores, pero el tiempo aumenta exponencialmente cuando se agregan demasiados.

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

Agregué una comparación de rendimiento stackoverflow.com/a/37580979/1647737 - no dude en actualizar.
le_m

0

Escribí una publicación para demostrar cómo permutar una matriz en JavaScript. Aquí está el código que hace esto.

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

Solo llama

permutar ([], [1,2,3,4])

trabajará. Para obtener detalles sobre cómo funciona esto, consulte la explicación en esa publicación.


0
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);
    }, []);
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.