Permutazioni in JavaScript?


138

Sto cercando di scrivere una funzione che procede come segue:

  • accetta un array di numeri interi come argomento (ad es. [1,2,3,4])
  • crea una matrice di tutte le possibili permutazioni di [1,2,3,4], con ciascuna permutazione avente una lunghezza di 4

la funzione di seguito (l'ho trovata online) lo fa prendendo una stringa come argomento e restituendo tutte le permutazioni di quella stringa

Non sono riuscito a capire come modificarlo per farlo funzionare con una matrice di numeri interi (penso che questo abbia qualcosa a che fare con il modo in cui alcuni metodi funzionano in modo diverso sulle stringhe rispetto a quelli sugli interi, ma non ne sono sicuro. ..)

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: sto cercando di fare in modo che la funzione restituisca array di numeri interi , non una matrice di stringhe .

Ho davvero bisogno che la soluzione sia in JavaScript. Ho già capito come farlo in Python

Risposte:


106

Se noti, il codice divide effettivamente i caratteri in un array prima di eseguire qualsiasi permutazione, quindi rimuovi semplicemente l'operazione di unione e divisione

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. Mi sta succedendo qualcosa di strano che sta cercando di usare la tua funzione. Lo tengo in un .js dove ho tutta la mia "funzione di manipolazione dell'elenco". Se lo uso con permute ([1,2,3]), e successivamente permute ([4,5,6]), l'output del successivo ha ancora il risultato, output dal primo. Qualche idea su come risolverlo? Grazie molto !
500


15
Accesso ai globi nella tua funzione, cattiva forma!
Shmiddty,

123

Un po 'in ritardo, ma mi piace aggiungere una versione leggermente più elegante qui. Può essere qualsiasi array ...

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

Aggiunta di una versione ES6 (2015). Inoltre, non muta l'array di input originale. Funziona nella console in 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;
}

Così...

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

I rendimenti ...

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

E...

permutator([1,2,3]);

I rendimenti ...

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

1
Se hai una funzione fattoriale a portata di mano (come è abbastanza probabile considerando che hai a che fare con le permutazioni), potresti accelerarla cambiando l'inizializzazione dell'oscilloscopio esterno in var results = new Array(factorial(inputArr.length)), length=0, quindi sostituirla results.push(…)conresults[length++]=…
Cyoce

1
Cosa fa la linea var cur, memo = memo || [];?
Ricevind

2
@ user2965967 Dichiara cur e memo e inizializza il memo come il valore del memo, a meno che non sia falso (incluso undefined), nel qual caso sarà un array vuoto. In altre parole, è un modo tutt'altro che ideale per fornire al parametro funzione un valore predefinito.
Mr. Lavalamp,

Questo modifica l'array originale.
Shmiddty,

2
è la slice()a permute(curr.slice(), m.concat(next))davvero necessario?
Yoav

82

Il seguente algoritmo molto efficiente utilizza il metodo di Heap per generare tutte le permutazioni di N elementi con complessità di runtime in 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]));

Lo stesso algoritmo implementato come generatore con complessità spaziale in O (N):

Confronto delle prestazioni

Sentiti libero di aggiungere l'implementazione alla seguente suite di test benchmark.js :

Risultati di runtime per Chrome 48:


1
Come può essere modificato questo codice per fornire risultati per un n = 2 fisso? Ad esempio, supponiamo di avere un set di tre lettere: A, B e C. Potremmo chiederci in quanti modi possiamo organizzare 2 lettere da quel set. Ogni possibile disposizione sarebbe un esempio di permutazione. L'elenco completo delle possibili permutazioni sarebbe: AB, AC, BA, BC, CA e CB.
a4xrbj1,

1
@ a4xrbj1 Vedi ad esempio l'esempio di codice in questa domanda: stackoverflow.com/questions/37892738/… - o stai chiedendo specificamente di modificare questo metodo (di Heap)?
le_m,

@le_m sì, in particolare usando questo metodo (di Heap) perché è così veloce
a4xrbj1

@ a4xrbj1 Calcolerei tutte le combinazioni di lunghezza fissa n (es. AB, AC, BC per n = 2) usando una strategia simile al link sopra indicato (vedi anche stackoverflow.com/questions/127704/… ) e quindi per ogni combinazione calcola tutte le sue permutazioni usando il metodo di Heap. Naturalmente è possibile ottimizzare casi speciali come n = 2.
le_m

1
La versione del generatore non funziona correttamente, si dovrebbe fare yield permutation.slice()se non si divide, si ottiene solo l'ultima permutazione calcolata.
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
Caspita, nonostante la sua assurdità e la mancanza di documenti, penso che questa sia la risposta più elegante. La mia spiegazione di questo algoritmo è: Per ogni elemento dell'array (riduci), seleziona tutti gli altri elementi, permutali (ricorsivamente) e concedi a questo elemento.
Aaron,

Ho provato questa soluzione qui: codewars.com/kata/reviews/5254ca2719453dcc0b000280/groups/… Ho scartato il codice golf originale in uno leggibile, ma è sostanzialmente lo stesso. Il problema è che produce duplicati e ho dovuto fare un ulteriore .filter(uniq)risultato.
Andrey Mikhaylov - lolmaus,

1
c'è un lisp parallelo al concetto [1,2,3].length == 3 && "foo" || "bar"o [1,2].length == 3 && "foo" || "bar"oh mio! c'è! (or (and (= 3 2) (print "hello!")) (print "goodbye"))
Dmitry,

@ lolmaus-AndreyMikhaylov come rimuovere i duplicati per favore Aggiorna la risposta se puoi
Pardeep Jain

@PardeepJain Ho fornito un link alla mia soluzione sopra.
Andrey Mikhaylov - lolmaus,

21

Ho migliorato la risposta di SiGanteng .

Ora è possibile chiamare permutepiù di una volta, perché permArre usedCharsvengono cancellati ogni volta.

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 seguente funzione permuta un array di qualsiasi tipo e chiama una specifica funzione di callback su ogni permutazione trovata:

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

Se lo chiami così:

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

Penso che farà esattamente ciò di cui hai bisogno: riempi un array chiamato resultcon le permutazioni dell'array [1, 2, 3]. Il risultato è:

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

Codice leggermente più chiaro su JSFiddle: http://jsfiddle.net/MgmMg/6/


10

La maggior parte delle risposte a questa domanda utilizza operazioni costose come inserimenti continui ed eliminazioni di elementi in un array o copia ripetuta di array.

Invece, questa è la tipica soluzione di backtracking:

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

Poiché l'array dei risultati sarà enorme, potrebbe essere una buona idea ripetere i risultati uno per uno invece di allocare tutti i dati contemporaneamente. In ES6, questo può essere fatto con generatori:

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

Questo è un compito interessante ed ecco il mio contributo. È molto semplice e veloce. Se interessati per favore abbiate pazienza e continuate a leggere.

Se desideri svolgere questo lavoro velocemente, devi assolutamente dedicarti alla programmazione dinamica. Ciò significa che dovresti dimenticare gli approcci ricorsivi. Certamente...

OK, il codice di le_m che utilizza il metodo Heap sembra essere il più veloce finora. Beh, non ho un nome per il mio algoritmo, non so se sia già stato implementato o meno, ma è molto semplice e veloce. Come per tutti gli approcci di programmazione dinamica, inizieremo con il problema più semplice e andremo al risultato finale.

Supponendo che avremo una serie di a = [1,2,3]cui inizieremo

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

Quindi seguire questi tre passaggi;

  1. Per ogni articolo del nostro rarray (risultato) aggiungeremo il prossimo articolo dell'array di input.
  2. Ruoteremo ogni articolo la sua lunghezza molte volte e memorizzeremo ogni istanza nella matrice dei risultati provvisori t. (beh, tranne per il primo a non perdere tempo con rotazione 0)
  3. Una volta terminato con tutti gli elementi rdell'array provvisorio, tdovremmo mantenere il livello successivo di risultati, quindi facciamo r = t; t = [];e continuiamo fino alla lunghezza dell'array di input a.

Quindi i seguenti sono i nostri passi;

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

Quindi ecco il codice

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

In test multipli l'ho visto risolvere le 120 permutazioni di [0,1,2,3,4] per 2000 volte in 25 ~ 35ms.


1
Sembra funzionare molto velocemente, a volte più veloce, a volte più lento del metodo Heap su FF / Ubuntu per diverse iterazioni di lunghezza / riscaldamento, ecc. Avrebbe bisogno di un jsperf per vedere i risultati per motori diversi.
le_m

1
@le_m OK ho fatto alcuni test @JSBen su Ubuntu e CPU AMD: con Chrome rotatePerm(quello sopra) è costantemente 1,2 più veloce. Con FF non c'è coerenza. Dopo più test a volte heapPermè 2 volte più veloce alcune volte rotatePermè 1,1 volte più veloce. Con altri browser web-kit come Opera o Epifania rotatePermrisulta costantemente 1,1 volte più veloce. Tuttavia, con Edge heapPermè costantemente 1,2 volte più veloce ogni volta.
Redu,

1
Bello! Sembra che - almeno su FF / Ubuntu - le prestazioni del metodo heap dipendono principalmente dalle prestazioni della copia dell'array. Ho modificato il tuo benchmark per confrontare lo slicing e lo push: jsben.ch/#/x7mYh - su FF e per piccoli array di input, lo push sembra molto più veloce
le_m

2
Sarebbe bello se il metodo heap potesse essere battuto dal punto di vista delle prestazioni. A proposito, il tuo metodo genera lo stesso output dell'algoritmo di Langdon (pagina 16) dallo stesso documento del 1977 che ho usato come riferimento per il metodo di Heap: homepage.math.uiowa.edu/~goodman/22m150.dir/2007/…
le_m,

2
@le_m Ho appena controllato e sembra essere la stessa cosa. Mi sembra di fare la rotazione come ha implementato. Solo con 40 anni di ritardo. Come ho già detto nella mia risposta, in realtà è un metodo molto semplice. Si dice che sia la scelta solo quando è disponibile la rotazione rapida. Attualmente sono in Haskell e ha un metodo integrato per fare un ciclo indefinito di un elenco (diciamo array) (la valutazione pigra rende una ripetizione infinita nessun problema) e questo potrebbe tornare utile. Tuttavia, Haskell ha già una permutationsfunzione standard :)
Redu,

6

Alcune versioni ispirate a 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

Rispondi senza la necessità di un array esterno o di una funzione aggiuntiva

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

puoi ricavarne una COmbinazione? stackoverflow.com/questions/53555563/...
Techdive

5

La versione più veloce, più efficace (più efficiente) ed elegante al giorno d'oggi (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`)


Ciao, ti dispiace spiegare cosa len % 2 // parity dependent adjacent elements swapsignifica e perché viene utilizzato?
Pramesh Bajracharya,

Il mio codice utilizza "l'algoritmo di Heap" per generare le permutazioni di array. Quindi, se vuoi sapere come funziona il mio codice sotto il cofano, leggi questa spiegazione dell'algoritmo di Heap: en.m.wikipedia.org/wiki/Heap%27s_algorithm
Vladislav Ladicky

Hai provato a stampare il risultato? come controllare il massimo se gli elementi dell'array superano i 10?
Marvix,

4

Ecco una bella soluzione

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

Ecco un'altra soluzione "più ricorsiva".

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

Produzione:

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

puoi fare una combinazione per questo? 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;
      }, []);
    }

Provalo con:

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

2

La maggior parte delle altre risposte non utilizza le nuove funzioni del generatore javascript che è una soluzione perfetta a questo tipo di problema. Probabilmente hai bisogno di una sola permutazione alla volta in memoria. Inoltre, preferisco generare una permutazione di un intervallo di indici in quanto ciò mi consente di indicizzare ciascuna permutazione e di saltare direttamente a una particolare permutazione, nonché di essere utilizzata per permutare qualsiasi altra raccolta.

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

Eccone uno che ho fatto ...

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

Ed eccolo di nuovo, ma scritto in modo meno incisivo! ...

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

Come funziona: se l'array è più lungo di un elemento, passa attraverso ciascun elemento e lo concatena con una chiamata ricorsiva a se stesso con gli elementi rimanenti come argomento. Non muta l'array originale.


2

Risposta funzionale utilizzando 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>

Emette permutazioni ordinate lessicograficamente. Funziona solo con numeri. In altri casi, è necessario modificare il metodo di scambio alla riga 34.


1

Simile nello spirito alla soluzione in stile Haskell di @crl, ma lavorando 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

Questo è un caso d'uso molto bello per la mappa / riduzione:

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)));
    }, []);
}
  • Innanzitutto, gestiamo il caso di base e semplicemente restituiamo l'array se è presente solo l'oggetto
  • In tutti gli altri casi
    • creiamo un array vuoto
    • passare in rassegna l'array di input
    • e aggiungi un array del valore corrente e tutte le permutazioni dell'array rimanente [].concat(cv,a)

1

Ecco una versione ES6 minima. L'appiattimento e senza funzioni possono essere estratti da 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])
    ));

Risultato:

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
Puoi aggiungere una spiegazione per favore.
Mehdi Bounya,

3
Sebbene questa risposta possa risolvere la domanda, non contiene alcuna spiegazione di come o perché lo faccia.
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

Piuttosto tardi. In ogni caso, se questo aiuta qualcuno.

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

Ho avuto una crepa nel realizzare una versione di questo che tenta di essere una programmazione concisa ma leggibile e puramente funzionale.

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

Si noti che la formattazione dell'argomento trasforma una stringa di input in un array. Non sono sicuro che sia un po 'troppo magico ... Non sono sicuro di averlo visto in natura. Per una vera leggibilità, probabilmente farei invece input = [...input]la prima riga della funzione.


1

Questa è un'implementazione dell'algoritmo di Heap (simile a @ le_m's), tranne che è ricorsiva.

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

Sembra anche abbastanza più veloce: https://jsfiddle.net/3brqzaLe/


1

Il mio primo contributo al sito. Inoltre, secondo i test che ho fatto, questo codice funziona più velocemente di tutti gli altri metodi qui menzionati prima di questa data, ovviamente è minimo se ci sono pochi valori, ma il tempo aumenta in modo esponenziale quando si aggiungono troppi.

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

Ho aggiunto un confronto delle prestazioni stackoverflow.com/a/37580979/1647737 - sentiti libero di aggiornare.
le_m,

0

Ho scritto un post per dimostrare come consentire una matrice in JavaScript. Ecco il codice che fa questo.

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

Chiama soltanto

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

funzionerà. Per i dettagli su come funziona, si prega di fare riferimento alla spiegazione in quel post.


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);
    }, []);
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.