JavaScript'teki permütasyonlar?


139

Aşağıdakileri yapan bir işlev yazmaya çalışıyorum:

  • bağımsız değişken olarak bir tamsayı dizisi alır (ör. [1,2,3,4])
  • [1,2,3,4] 'ün olası tüm permütasyonlarının bir dizisini oluşturur, her permütasyonun uzunluğu 4'tür

Aşağıdaki işlev (çevrimiçi buldum) bunu bir dizeyi argüman olarak alarak ve dizenin tüm permütasyonlarını döndürerek yapar

Ben bir tamsayılar dizisi ile çalışmak için nasıl değiştirileceğini anlayamadım, (bu bazı yöntemlerin tamsayılar üzerinde yaptığından daha farklı dizelerde nasıl çalıştığı ile ilgili bir şey olduğunu düşünüyorum, ama emin değilim. ..)

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

Not: Ben işlevi dönüş dizileri yapmaya arıyorum tamsayılar , değil bir dizi dizeleri .

JavaScript'te olmak için gerçekten çözüme ihtiyacım var. Bunu zaten python'da nasıl yapacağımı anladım

Yanıtlar:


106

Dikkat ederseniz, kod aslında herhangi bir permütasyon yapmadan önce karakterleri bir diziye böler, böylece birleştirme ve bölme işlemini kaldırmanız yeterlidir.

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. İşlevi kullanmaya çalışırken bana garip bir şey oluyor. Ben tüm "liste manipülasyon fonksiyonu" sahip bir .js içinde tutmak. Eğer permute ([1,2,3]) ve daha sonra permute ([4,5,6]) ile kullanırsam, bir sonrakinin çıktısı hala birinciden çıktı alır. Bunu nasıl düzeltebileceğine dair bir fikrin var mı? Çok teşekkürler !
500


15
Küresel formlara işlevinizle erişme, kötü form!
Shmiddty

123

Biraz geç, ama buraya biraz daha zarif bir versiyon eklemek istiyorum. Herhangi bir dizi olabilir ...

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) sürümü ekleme. Ayrıca orijinal giriş dizisini değiştirmez. Chrome'daki konsolda çalışır ...

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

Yani...

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

Verim ...

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

Ve...

permutator([1,2,3]);

Verim ...

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

1
Kullanışlı bir faktöriyel fonksiyonunuz varsa (permütasyonlarla uğraştığınızı düşündüğünüz gibi), dış kapsam başlatmayı şu şekilde değiştirerek hızlandırabilirsiniz var results = new Array(factorial(inputArr.length)), length=0, daha sonraresults.push(…)results[length++]=…
Cyoce

1
Hat ne yapar var cur, memo = memo || [];?
Ricevind

2
@ user2965967 cur ve memo bildirir ve notu falsey (tanımsız dahil) olmadığı sürece memo'nun değeri olarak başlatır; bu durumda boş bir dizi olur. Başka bir deyişle, function parametresine varsayılan bir değer sağlamak için ideal bir yöntem değildir.
Bay Lavalamp,

Bu, orijinal diziyi değiştirir.
Shmiddty

2
olduğu slice()içinde permute(curr.slice(), m.concat(next))gerçekten gerekli?
Yoav

82

Aşağıdaki çok verimli algoritma, O (N!) Çalışma zamanı karmaşıklığına sahip N öğelerinin tüm permütasyonlarını oluşturmak için Heap'in yöntemini kullanır :

function permute(permutation) {
  var length = permutation.length,
      result = [permutation.slice()],
      c = new Array(length).fill(0),
      i = 1, k, p;

  while (i < length) {
    if (c[i] < i) {
      k = i % 2 && c[i];
      p = permutation[i];
      permutation[i] = permutation[k];
      permutation[k] = p;
      ++c[i];
      i = 1;
      result.push(permutation.slice());
    } else {
      c[i] = 0;
      ++i;
    }
  }
  return result;
}

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

O (N) 'de uzay karmaşıklığına sahip bir jeneratör olarak uygulanan aynı algoritma :

Performans karşılaştırması

Uygulamanızı aşağıdaki benchmark.js test paketine eklemekten çekinmeyin :

Chrome 48 için çalışma zamanı sonuçları:


1
Bu kod, sabit bir n = 2 sonucunu vermek için nasıl değiştirilebilir? Örneğin, üç harf grubumuz olduğunu varsayalım: A, B ve C. Bu setten 2 harfi kaç şekilde düzenleyebileceğimizi sorabiliriz. Her olası düzenleme bir permütasyon örneği olacaktır. Olası permütasyonların tam listesi: AB, AC, BA, BC, CA ve CB.
a4xrbj1

1
@ a4xrbj1 Örneğin bu sorudaki kod örneğine bakın: stackoverflow.com/questions/37892738/… - ya da özellikle bu (Heap) yöntemini değiştirmek mi istiyorsunuz?
le_m

@le_m evet, özellikle bu kadar hızlı (Heap's) yöntemini kullanarak
a4xrbj1

@ a4xrbj1 Yukarıda verilen bağlantıya benzer bir strateji kullanarak (ayrıca bkz. stackoverflow.com/questions/127704/… ) ve ardından her kombinasyon için sabit uzunluktaki n tüm kombinasyonları (örn. n = 2 için AB, AC, BC) hesaplayacağım. tüm permütasyonlarını Heap yöntemini kullanarak hesaplar. Elbette n = 2 gibi özel durumlar optimize edilebilir.
le_m

1
Jeneratör sürümü düzgün çalışmıyor, yield permutation.slice()dilimlemiyorsanız sadece hesaplanan son permütasyonu alırsınız.
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
Vay, kıvraklığına ve doküman eksikliğine rağmen, bunun en şık cevap olduğunu düşünüyorum. Bu algoritmaya ilişkin açıklamam şudur: Dizideki her öğe için (azaltma), diğer tüm öğeleri seçin, onlara izin verin (yinelemeli olarak) ve bu öğeyle uyumlu hale getirin.
aaron

Bu çözümü burada denedim: codewars.com/kata/reviews/5254ca2719453dcc0b000280/groups/… Orijinal golf kodunu okunabilir bir kod haline getirdim , ancak aslında aynı. Bununla ilgili sorun, kopyaları üretmesi .filter(uniq)ve sonuçta ek bir şey yapmam gerekti.
Andrey Mikhaylov - lolmaus

1
Konsepte paralel bir lisp var mı [1,2,3].length == 3 && "foo" || "bar"yoksa [1,2].length == 3 && "foo" || "bar"ah! var! (or (and (= 3 2) (print "hello!")) (print "goodbye"))
Dmitry

@ lolmaus-AndreyMikhaylov yineleme nasıl kaldırılırsa lütfen cevabı güncelleyin
Pardeep Jain

@PardeepJain Yukarıdaki çözümüme bir bağlantı verdim.
Andrey Mikhaylov - lolmaus

21

Ben geliştirdik SiGanteng 'ın cevabı .

Şimdi çağırmak mümkündür permuteçünkü, birden fazla kez permArrve usedCharsher seferinde temizlenir.

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

Aşağıdaki işlev, herhangi bir tür diziye izin verir ve bulunan her permütasyonda belirtilen bir geri arama işlevini çağırır:

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

Eğer böyle çağırırsan:

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

Ben tam olarak ihtiyacınız olanı yapacağını düşünüyorum - resultdizi [1, 2, 3] permütasyonları ile adlandırılan bir dizi doldurun . Sonuç:

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

JSFiddle'da biraz daha net kod: http://jsfiddle.net/MgmMg/6/


10

Bu sorunun cevabının çoğu, bir diziye sürekli ekleme ve öğe silme veya dizileri yinelemeli kopyalama gibi pahalı işlemleri kullanır.

Bunun yerine, bu tipik bir geri izleme çözümüdür:

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

Sonuç dizisi çok büyük olacağından, tüm verileri aynı anda ayırmak yerine sonuçları tek tek yinelemek iyi bir fikir olabilir. ES6'da, bu jeneratörler ile yapılabilir:

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

Bu ilginç bir görev ve işte benim katkım. Çok basit ve hızlı. Eğer ilgileniyorsanız lütfen benimle ayı ve okumaya devam edin.

Bu işi hızlı bir şekilde yapmak istiyorsanız, kesinlikle dinamik programlamaya girmelisiniz. Yani özyinelemeli yaklaşımları unutmalısınız. Kesinlikle...

Tamam le_m kod yığın yöntemini kullanır hızlı şimdiye kadar gibi görünüyor. Peki algoritmam için bir isimim yok, zaten uygulanmış olup olmadığını bilmiyorum ama çok basit ve hızlı. Tüm dinamik programlama yaklaşımlarında olduğu gibi en basit problemle başlayacağız ve nihai sonuca gideceğiz.

Biz bir dizi olduğunu varsayarsak a = [1,2,3]biz başlayacak

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

Sonra bu üç adımı izleyin;

  1. r(Sonuç) dizimizin her bir öğesi için giriş dizisinin sonraki öğesini ekleyeceğiz.
  2. Uzunluğunu her öğeyi defalarca döndüreceğiz ve her örneği geçici sonuç dizisinde saklayacağız t. (ilk rotasyon dışında 0 dönüş ile zaman kaybetmemek dışında)
  3. rAra dizinin tüm öğeleri bittikten sonra t, bir sonraki sonuç düzeyini tutmalıdır, böylece r = t; t = [];girdi dizisinin uzunluğuna kadar devam edip devam ederiz a.

Yani bizim adımlarımız;

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

İşte kod

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

Çoklu testte, [0,1,2,3,4] 'ün 120 permütasyonunu 25 ~ 35ms'de 2000 kez çözdüğünü gördüm.


1
Farklı uzunluk / ısınma yinelemeleri vb. İçin FF / Ubuntu'daki Heap yönteminden gerçekten hızlı, bazen daha hızlı, bazen daha yavaş çalışıyor gibi görünüyor. Farklı motorların sonuçlarını görmek için bir jsperf gerekir.
le_m

1
@ le_m Tamam Bazı testler yaptım @JSBen Ubuntu ve AMD CPU'da: Chrome ile rotatePerm(yukarıdakiler) sürekli olarak 1.2 daha hızlıdır. FF ile tutarlılık yoktur. Birden fazla testten sonra bazen heapPerm2 kat daha hızlı, bazen rotatePermde 1.1 kat daha hızlıdır. Opera veya Epiphany gibi diğer web kiti tarayıcıları ile rotatePermsürekli olarak 1.1 kat daha hızlı çıkıyor. Ancak Edge heapPermile her seferinde sürekli 1.2 kat daha hızlı.
Redu

1
Güzel! Görünüşe göre - en azından FF / Ubuntu'da - yığın yönteminin performansı esas olarak dizi kopyalamanın performansına bağlıdır. Kıyaslama ile itme karşılaştırmasını karşılaştırmak için kriterinizi değiştirdim: jsben.ch/#/x7mYh - FF'de ve küçük giriş dizileri için itme çok daha hızlı görünüyor
le_m 28:17

2
Öbek yöntemi performans açısından dayak olabilir harika olurdu. Bu arada, yönteminiz Heap'in yöntemi için referans olarak kullandığım 1977 makalesinden Langdon algoritmasıyla (sayfa 16) aynı çıktıyı üretir: homepage.math.uiowa.edu/~goodman/22m150.dir/2007/…
le_m

2
@ le_m Yeni kontrol ettim ve aynı şey gibi görünüyor. Uyguladığı gibi rotasyon yapıyor gibiyim. Sadece 40 yıllık gecikmeyle. Cevabımda bahsettiğim gibi aslında çok basit bir yöntem. Sadece hızlı dönüş mevcut olduğunda seçimden bahsedilmelidir. Şu anda Haskell içindeyim ve bir liste (diyelim dizi) döngüsü süresiz yapmak için yerleşik bir yöntemi vardır (tembel değerlendirme sonsuz tekrarlama sorun yok yapar) ve bu kullanışlı gelebilir. Yine de, Haskell'in zaten standart bir permutationsişlevi var :)
Redu

6

Haskell'den esinlenen bazı versiyonlar:

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

Dış diziye veya ek işleve gerek kalmadan yanıtlayın

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

ondan bir koordinasyon yapabilir misin? stackoverflow.com/questions/53555563/…
Techdive

5

Günümüzde en hızlı, en (en etkili) etkili ve en şık versiyon (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`)


Merhaba, ne len % 2 // parity dependent adjacent elements swapanlama geldiğini ve neden kullanıldığını açıklar mısınız?
Pramesh Bajracharya

Kodum dizi permütasyonları oluşturmak için "Heap algoritması" kullanır. Kodumun
Vladislav Ladicky

Sonucu yazdırmaya çalıştınız mı? dizi öğeleri 10 üzerinde ise maksimum nasıl kontrol edilir?
Marvix

4

İşte harika bir çözüm

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

İşte başka bir "daha özyinelemeli" çözüm.

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

Çıktı:

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

bunun için bir kombinasyon yapabilir misin? 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;
      }, []);
    }

Şunlarla test edin:

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

2

Diğer cevapların çoğu, bu tür bir sorun için mükemmel bir çözüm olan yeni javascript üreteci işlevlerini kullanmaz. Muhtemelen hafızada sadece bir permütasyona ihtiyacınız vardır. Ayrıca, her bir permütasyonu endekslememe ve herhangi bir belirli permütasyona doğrudan atlamamın yanı sıra başka bir koleksiyona izin vermek için kullanmamı sağladığından, bir dizi endeksin permütasyonunu oluşturmayı tercih ederim.

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

İşte yaptığım ...

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

Ve işte yine ama daha az ters yazılmış! ...

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

Nasıl çalışır: Dizi bir öğeden daha uzunsa, her öğede adım adım ilerler ve bağımsız değişken olarak diğer öğelerle özyinelemeli çağrı ile birleştirir. Orijinal diziyi değiştirmez.


2

FlatMap kullanarak işlevsel yanıt:

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>

Sözlüksel sıralı permütasyonlar üretir. Yalnızca rakamlarla çalışır. Diğer durumda, 34. satırdaki takas yöntemini değiştirmeniz gerekir.


1

@Crl'nin Haskell tarzı çözümüne benzer şekilde, ancak aşağıdakilerle çalışma 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

Bu harita / azaltma için çok güzel bir kullanım durumudur:

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)));
    }, []);
}
  • İlk olarak, temel durumu ele alıyoruz ve sadece içinde öğe varsa diziyi döndürüyoruz
  • Diğer tüm durumlarda
    • boş bir dizi oluşturuyoruz
    • girdi dizisi üzerinde döngü
    • ve geçerli değerin bir dizisini ve kalan dizinin tüm permütasyonlarını ekleyin [].concat(cv,a)

1

İşte minimal bir ES6 sürümü. Düzleştir ve işlevsiz Lodash'tan çekilebilir.

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

Sonuç:

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
Bir açıklama ekleyebilir misiniz lütfen.
Mehdi Bounya

3
Bu cevap ederken edebilir soruyu çözmek, o bunun nasıl ya da neden hiçbir açıklama içerir.
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

Biraz geç. Yine de herkesin yardım etmesi durumunda.

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

Kısa ama okunabilir ve tamamen işlevsel bir program yapmaya çalışan bir versiyonunu hazırlarken çatladım.

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

Bağımsız değişken biçimlendirmesinin giriş dizesini bir diziye dönüştürdüğünü unutmayın. Biraz fazla büyülü olup olmadığından emin değilim ... Vahşi doğada gördüğümden emin değilim. Gerçek okunabilirlik input = [...input]için muhtemelen fonksiyonun ilk satırı için yapardım .


1

Bu, Heap algoritmasının (@ le_m's'e benzer) bir uygulamasıdır, özyinelemelidir.

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

Görünüşe göre oldukça hızlı: https://jsfiddle.net/3brqzaLe/


1

Siteye ilk katkım. Ayrıca, yaptığım testlere göre, bu kod bu tarihten önce burada belirtilen tüm diğer yöntemlerden daha hızlı çalışır, elbette birkaç değer varsa minimumdur, ancak çok fazla eklenirken zaman katlanarak artar.

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

Bir performans karşılaştırması ekledim stackoverflow.com/a/37580979/1647737 - güncellemekten çekinmeyin.
le_m

0

JavaScript'te bir diziye nasıl izin verileceğini göstermek için bir yazı yazdım . İşte bunu yapan kod.

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

Sadece ara

permütasyon ([], [1,2,3,4])

çalışacak. Bunun nasıl çalıştığına ilişkin ayrıntılar için lütfen bu yayındaki açıklamaya bakı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);
    }, []);
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.