Перестановки в JavaScript?


138

Я намагаюся написати функцію, яка виконує такі дії:

  • приймає масив цілих чисел як аргумент (наприклад, [1,2,3,4])
  • створює масив усіх можливих перестановок [1,2,3,4], при цьому кожна перестановка має довжину 4

функція нижче (я знайшла це в Інтернеті) робить це, беручи рядок як аргумент і повертаючи всі перестановки цього рядка

Я не міг зрозуміти, як його змінити, щоб він працював з масивом цілих чисел, (я думаю, це має відношення до того, як деякі методи працюють по-різному на рядках, ніж у цілих числах, але я не впевнений. ..)

var permArr = [], usedChars = [];
function permute(input) {
  var i, ch, chars = input.split("");
  for (i = 0; i < chars.length; i++) {
    ch = chars.splice(i, 1);
    usedChars.push(ch);
    if (chars.length == 0)
      permArr[permArr.length] = usedChars.join("");
    permute(chars.join(""));
    chars.splice(i, 0, ch);
    usedChars.pop();
  }
  return permArr
};

Примітка. Я хочу зробити функцію повернення масивів цілих чисел , а не масив рядків .

Мені дуже потрібно, щоб рішення знаходилось в JavaScript. Я вже придумав, як це зробити в python

Відповіді:


106

Якщо ви помітили, код фактично розбиває символи на масив перед тим, як зробити будь-яку перестановку, тому ви просто видалите операцію з'єднання та розділення

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. Зі мною трапляється щось дивне, намагаючись використати вашу функцію. Я зберігаю його у .js, де у мене є вся моя "функція маніпулювання списком". Якщо я використовую його з перестановкою ([1,2,3]), а пізніше з перестановкою ([4,5,6]), то результат пізнішого все ж матиме результат, вихід з першого. Будь-яка ідея, як це виправити? Велике дякую !
500


15
Доступ до глобальних компаній у своїй функції, погана форма!
Шміддті

123

Трохи пізно, але люблю додати сюди трохи більш елегантну версію. Може бути будь-який масив ...

function permutator(inputArr) {
  var results = [];

  function permute(arr, memo) {
    var cur, memo = memo || [];

    for (var i = 0; i < arr.length; i++) {
      cur = arr.splice(i, 1);
      if (arr.length === 0) {
        results.push(memo.concat(cur));
      }
      permute(arr.slice(), memo.concat(cur));
      arr.splice(i, 0, cur[0]);
    }

    return results;
  }

  return permute(inputArr);
}

Додавання версії ES6 (2015). Також не мутує вихідний вхідний масив. Працює в консолі в Chrome ...

const permutator = (inputArr) => {
  let result = [];

  const permute = (arr, m = []) => {
    if (arr.length === 0) {
      result.push(m)
    } else {
      for (let i = 0; i < arr.length; i++) {
        let curr = arr.slice();
        let next = curr.splice(i, 1);
        permute(curr.slice(), m.concat(next))
     }
   }
 }

 permute(inputArr)

 return result;
}

Так...

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

Врожайність ...

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

І ...

permutator([1,2,3]);

Врожайність ...

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

1
Якщо у вас є зручна факторна функція (як це цілком ймовірно, враховуючи, що ви маєте справу з перестановками), ви можете прискорити її, змінивши ініціалізацію зовнішньої області на var results = new Array(factorial(inputArr.length)), length=0, а потім замінити results.push(…)наresults[length++]=…
Cyoce

1
Що робить лінія var cur, memo = memo || [];?
Ricevind

2
@ user2965967 Він оголошує cur і memo і ініціалізує memo як значення пам’яті, якщо тільки вона не є фальсією (включаючи невизначене), в цьому випадку це буде порожній масив. Іншими словами, це менш ніж ідеальний спосіб надати параметру функції значення за замовчуванням.
Містер Лаваламп,

Це змінює вихідний масив.
Шміддті

2
це slice()в permute(curr.slice(), m.concat(next))насправді потрібно?
Йоав

82

Наступний дуже ефективний алгоритм використовує метод Heap для генерації всіх перестановок N елементів зі складністю виконання в O (N!):

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

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

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

Той же алгоритм, реалізований як генератор з складністю простору в O (N):

Порівняння продуктивності

Сміливо додайте свою реалізацію до наступного тестового набору benchmark.js :

Результати виконання Chrome 48:


1
Як цей код можна змінити для отримання результатів за фіксованим n = 2? Наприклад, припустимо, що у нас є набір з трьох літер: A, B і C. Ми можемо запитати, якими способами ми можемо організувати 2 букви з цього набору. Кожна можлива композиція буде прикладом перестановки. Повний перелік можливих перестановок складе: AB, AC, BA, BC, CA та CB.
a4xrbj1

1
@ a4xrbj1 Дивіться, наприклад, зразок коду в цьому запитанні: stackoverflow.com/questions/37892738/… - чи ви конкретно запитуєте про зміну цього методу (Хіпа)?
le_m

@le_m так, спеціально використовуючи цей метод
Хіпа

@ a4xrbj1 Я би обчислював усі комбінації фіксованої довжини n (наприклад, AB, AC, BC для n = 2), використовуючи аналогічну стратегію для наведеного вище посилання (див. також stackoverflow.com/questions/127704/… ), а потім для кожної комбінації обчислити всі його перестановки за допомогою методу Heap. Звичайно, можна оптимізувати спеціальні випадки, такі як n = 2.
le_m

1
Версія генератора не працює належним чином, ви повинні зробити це, yield permutation.slice()якщо ви не розрізаєте, ви обчислюєте лише останню перестановку.
Бельдар

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
Нічого собі, незважаючи на свою лаконічність та відсутність документів, я думаю, що це найелегантніша відповідь. Моє пояснення цього алгоритму: Для кожного елемента в масиві (зменшити) виберіть усі інші елементи, перестановіть їх (рекурсивно) та прив'яжіться до цього елемента.
aaron

Спробували це рішення тут: codewars.com/kata/reviews/5254ca2719453dcc0b000280/groups/… Я розгорнув оригінальний код гольфу на читабельному, але він, по суті, той самий. Проблема в ньому полягає в тому, що він видає дублікати, і мені довелося зробити додатковий .filter(uniq)результат.
Андрій Михайлов - lolmaus

1
чи є паралельна концепція, [1,2,3].length == 3 && "foo" || "bar"чи [1,2].length == 3 && "foo" || "bar"моє! існує! (or (and (= 3 2) (print "hello!")) (print "goodbye"))
Дмитро

@ lolmaus-AndreyMikhaylov як видалити копію, будь ласка, оновіть відповідь, якщо зможете
Pardeep Jain

@PardeepJain Я дав посилання на своє рішення вище.
Андрій Михайлов - lolmaus

21

Я поліпшив SiGanteng «s відповідь .

Тепер можна дзвонити permuteне раз, тому що permArrі usedCharsщоразу очищаються.

function permute(input) {
    var permArr = [],
        usedChars = [];
    return (function main() {
        for (var i = 0; i < input.length; i++) {
            var ch = input.splice(i, 1)[0];
            usedChars.push(ch);
            if (input.length == 0) {
                permArr.push(usedChars.slice());
            }
            main();
            input.splice(i, 0, ch);
            usedChars.pop();
        }
        return permArr;
    })();
}


10

Наступна функція перетворює масив будь-якого типу та викликає вказану функцію зворотного виклику для кожної знайденої перестановки:

/*
  Permutate the elements in the specified array by swapping them
  in-place and calling the specified callback function on the array
  for each permutation.

  Return the number of permutations.

  If array is undefined, null or empty, return 0.

  NOTE: when permutation succeeds, the array should be in the original state
  on exit!
*/
  function permutate(array, callback) {
    // Do the actual permuation work on array[], starting at index
    function p(array, index, callback) {
      // Swap elements i1 and i2 in array a[]
      function swap(a, i1, i2) {
        var t = a[i1];
        a[i1] = a[i2];
        a[i2] = t;
      }

      if (index == array.length - 1) {
        callback(array);
        return 1;
      } else {
        var count = p(array, index + 1, callback);
        for (var i = index + 1; i < array.length; i++) {
          swap(array, i, index);
          count += p(array, index + 1, callback);
          swap(array, i, index);
        }
        return count;
      }
    }

    if (!array || array.length == 0) {
      return 0;
    }
    return p(array, 0, callback);
  }

Якщо ви називаєте це так:

  // Empty array to hold results
  var result = [];
  // Permutate [1, 2, 3], pushing every permutation onto result[]
  permutate([1, 2, 3], function (a) {
    // Create a copy of a[] and add that to result[]
    result.push(a.slice(0));
  });
  // Show result[]
  document.write(result);

Я думаю, це зробить саме те, що потрібно - заповнити масив, який називається resultперестановками масиву [1, 2, 3]. Результат:

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

Трохи чіткіший код на JSFiddle: http://jsfiddle.net/MgmMg/6/


10

Більшість відповідей на це питання використовують дорогі операції, такі як безперервне вставлення та видалення елементів у масив або повторне копіювання масивів.

Натомість це типове рішення про зворотний трек:

function permute(arr) {
  var results = [],
      l = arr.length,
      used = Array(l), // Array of bools. Keeps track of used items
      data = Array(l); // Stores items of the current permutation
  (function backtracking(pos) {
    if(pos == l) return results.push(data.slice());
    for(var i=0; i<l; ++i) if(!used[i]) { // Iterate unused items
      used[i] = true;      // Mark item as used
      data[pos] = arr[i];  // Assign item at the current position
      backtracking(pos+1); // Recursive call
      used[i] = false;     // Mark item as not used
    }
  })(0);
  return results;
}
permute([1,2,3,4]); // [  [1,2,3,4], [1,2,4,3], /* ... , */ [4,3,2,1]  ]

Оскільки масив результатів буде величезним, можливо, було б корисно повторити результати один за одним, а не розподіляти всі дані одночасно. У ES6 це можна зробити за допомогою генераторів:

function permute(arr) {
  var l = arr.length,
      used = Array(l),
      data = Array(l);
  return function* backtracking(pos) {
    if(pos == l) yield data.slice();
    else for(var i=0; i<l; ++i) if(!used[i]) {
      used[i] = true;
      data[pos] = arr[i];
      yield* backtracking(pos+1);
      used[i] = false;
    }
  }(0);
}
var p = permute([1,2,3,4]);
p.next(); // {value: [1,2,3,4], done: false}
p.next(); // {value: [1,2,4,3], done: false}
// ...
p.next(); // {value: [4,3,2,1], done: false}
p.next(); // {value: undefined, done: true}

6

Це цікаве завдання, і ось мій внесок. Це дуже просто і швидко. Якщо ви зацікавлені, будь ласка, несіть мене та читайте далі.

Якщо ви хочете швидко виконати цю роботу, вам неодмінно потрібно вступити в динамічне програмування. Що означає, що ви повинні забути про рекурсивні підходи. Це точно...

OK код le_m, який використовує метод Heap, здається, найшвидший до цих пір. Ну, я не отримав назву свого алгоритму, я не знаю, чи він уже реалізований чи ні, але це дуже просто і швидко. Як і у всіх підходах до динамічного програмування, ми почнемо з найпростішої проблеми та підемо на кінцевий результат.

Припустимо, що у нас є масив, з якого a = [1,2,3]ми почнемо

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

Потім виконайте ці три кроки;

  1. До кожного елемента нашого r(результату) масиву ми додамо наступний елемент вхідного масиву.
  2. Ми будемо обертати кожен елемент його довжини багато разів і будемо зберігати кожен екземпляр у проміжному масиві результатів t. (ну крім першого, щоб не витрачати час на 0 обертання)
  3. Після того, як ми закінчимо всі елементи rпроміжного масиву, tслід провести наступний рівень результатів, тому ми зробимо r = t; t = [];і продовжимо, поки не буде довжина вхідного масиву a.

Отже, наступні наші кроки;

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

Так ось код

function perm(a){
  var r = [[a[0]]],
      t = [],
      s = [];
  if (a.length <= 1) return a;
  for (var i = 1, la = a.length; i < la; i++){
    for (var j = 0, lr = r.length; j < lr; j++){
      r[j].push(a[i]);
      t.push(r[j]);
      for(var k = 1, lrj = r[j].length; k < lrj; k++){
        for (var l = 0; l < lrj; l++) s[l] = r[j][(k+l)%lrj];
        t[t.length] = s;
        s = [];
      }
    }
    r = t;
    t = [];
  }
  return r;
}

var arr = [0,1,2,4,5];
console.log("The length of the permutation is:",perm(arr).length);
console.time("Permutation test");
for (var z = 0; z < 2000; z++) perm(arr);
console.timeEnd("Permutation test");

У багаторазовому тесті я бачив, як це дозволяє вирішити 120 перестановок [0,1,2,3,4] протягом 2000 разів за 25 ~ 35 мс.


1
Здається, він працює дуже швидко, іноді швидше, іноді повільніше, ніж метод Heap на FF / Ubuntu для різних ітерацій довжини / розминки тощо. Потрібен jsperf, щоб побачити результати для різних двигунів.
le_m

1
@le_m Гаразд, я зробив тест @JSBen На Ubuntu & AMD CPU: З Chrome rotatePerm(вищезгаданий) стабільно на 1,2 швидше. З ФФ немає консистенції. Після багаторазових тестів іноді heapPermце в 2 рази швидше, інколи rotatePerm- в 1,1 рази швидше. З іншими веб-наборами браузерів, таких як Opera або Epiphany, rotatePermстабільно виявляється, що в 1,1 рази швидше. Однак з Edge heapPermстабільно в 1,2 рази швидше кожного разу.
зменшення

1
Приємно! Схоже, що - принаймні на FF / Ubuntu - продуктивність методу купи в основному залежить від продуктивності копіювання масиву. Я змінив ваш орієнтир, щоб порівняти нарізання та натискання: jsben.ch/#/x7mYh - на FF та для малих вхідних масивів, натискання здається набагато швидшим
le_m

2
Було б чудово, якби метод кучі міг би бути виграшним. До речі, ваш метод генерує той самий результат, що і алгоритм Ленґдона (стор. 16) з тієї ж статті 1977 року, яку я використовував як посилання на метод Heap: homepage.math.uiowa.edu/~goodman/22m150.dir/2007/…
le_m

2
@le_m Я щойно перевірив, і здається, це те саме. Я, здається, ротацію, як він здійснив. Тільки з 40-річним запізненням. Як я вже говорив у своїй відповіді, це насправді дуже простий метод. Вибір згаданий лише тоді, коли доступно швидке обертання. В даний час я перебуваю в Haskell, і у нього є вбудований метод для безперервного циклу списку (скажімо, масив) (лінива оцінка робить нескінченне повторення жодних проблем), і це може стати в нагоді. Тим не менше, Haskell вже має стандартну permutationsфункцію :)
зменшити

6

Деякі версії, натхненні 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

Відповідь не потребує зовнішнього масиву чи додаткової функції

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

чи можете ви зробити з нього КОМБІНАЦІЮ? stackoverflow.com/questions/53555563 / ...
Techdive

5

Найшвидша, найефективніша та найелегантніша версія на сьогодні (2020 р.)

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

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

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

  return perms
}

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

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

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


Привіт, Ви не проти пояснити, що len % 2 // parity dependent adjacent elements swapозначає і для чого він використовується?
Прамеш Байрачаря

Мій код використовує "алгоритм Хіпа" для генерації перестановок масиву. Тож, якщо ви хочете дізнатися, як працює мій код під кришкою, прочитайте це пояснення алгоритму Heap: en.m.wikipedia.org/wiki/Heap%27s_algorithm
Владислав Ледікі

Ви намагалися роздрукувати результат? як контролювати максимум, якщо елементів масиву понад 10?
Марвікс

4

Ось класне рішення

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

Ось ще одне «більш рекурсивне» рішення.

function perms(input) {
  var data = input.slice();
  var permutations = [];
  var n = data.length;

  if (n === 0) {
    return [
      []
    ];
  } else {
    var first = data.shift();
    var words = perms(data);
    words.forEach(function(word) {
      for (var i = 0; i < n; ++i) {
        var tmp = word.slice();
        tmp.splice(i, 0, first)
        permutations.push(tmp);
      }
    });
  }

  return permutations;
}

var str = 'ABC';
var chars = str.split('');
var result = perms(chars).map(function(p) {
  return p.join('');
});

console.log(result);

Вихід:

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

ви можете скласти комбінацію для цього? 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;
      }, []);
    }

Перевірте це за допомогою:

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

2

Більшість інших відповідей не використовують нові функції генератора javascript, що є ідеальним рішенням цього типу проблем. Вам, напевно, потрібна лише одна перестановка за часом. Крім того, я вважаю за краще генерувати перестановку для ряду індексів, оскільки це дозволяє мені індексувати кожну перестановку та переходити прямо до будь-якої конкретної перестановки, а також використовуватись для перестановки будь-якої іншої колекції.

// ES6 generator version of python itertools [permutations and combinations]
const range = function*(l) { for (let i = 0; i < l; i+=1) yield i; }
const isEmpty = arr => arr.length === 0;

const permutations = function*(a) {
    const r = arguments[1] || [];
    if (isEmpty(a)) yield r;
    for (let i of range(a.length)) {
        const aa = [...a];
        const rr = [...r, ...aa.splice(i, 1)];
        yield* permutations(aa, rr);
    }
}
console.log('permutations of ABC');
console.log(JSON.stringify([...permutations([...'ABC'])]));

const combinations = function*(a, count) {
    const r = arguments[2] || [];
    if (count) {
        count = count - 1;
        for (let i of range(a.length - count)) {
            const aa = a.slice(i);
            const rr = [...r, ...aa.splice(0, 1)];
            yield* combinations(aa, count, rr);
        }
    } else {
        yield r;
    }
}
console.log('combinations of 2 of ABC');
console.log(JSON.stringify([...combinations([...'ABC'], 2)]));



const permutator = function() {
    const range = function*(args) {
        let {begin = 0, count} = args;
        for (let i = begin; count; count--, i+=1) {
            yield i;
        }
    }
    const factorial = fact => fact ? fact * factorial(fact - 1) : 1;

    return {
        perm: function(n, permutationId) {
            const indexCount = factorial(n);
            permutationId = ((permutationId%indexCount)+indexCount)%indexCount;

            let permutation = [0];
            for (const choiceCount of range({begin: 2, count: n-1})) {
                const choice = permutationId % choiceCount;
                const lastIndex = permutation.length;

                permutation.push(choice);
                permutation = permutation.map((cv, i, orig) => 
                    (cv < choice || i == lastIndex) ? cv : cv + 1
                );

                permutationId = Math.floor(permutationId / choiceCount);
            }
            return permutation.reverse();
        },
        perms: function*(n) {
            for (let i of range({count: factorial(n)})) {
                yield this.perm(n, i);
            }
        }
    };
}();

console.log('indexing type permutator');
let i = 0;
for (let elem of permutator.perms(3)) {
  console.log(`${i}: ${elem}`);
  i+=1;
}
console.log();
console.log(`3: ${permutator.perm(3,3)}`);


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

Ось я зробив ...

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

І ось це знову, але написано менш лаконічно! ...

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

Принцип роботи: Якщо масив довший одного елемента, він переходить через кожен елемент і з'єднує його рекурсивним викликом до себе з рештою елементами в якості аргументу. Він не мутує вихідний масив.


2

Функціональна відповідь за допомогою 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>

Виводиться лексикографічно впорядковані перестановки. Працює лише з цифрами. В іншому випадку вам потрібно змінити метод swap у рядку 34.


1

Дуже схожий на рішення Haskell у стилі @crl, але працює з reduce:

function permutations( base ) {
  if (base.length == 0) return [[]]
  return permutations( base.slice(1) ).reduce( function(acc,perm) {
    return acc.concat( base.map( function(e,pos) {
      var new_perm = perm.slice()
      new_perm.splice(pos,0,base[0])
      return new_perm
    }))
  },[])    
}

1

Це дуже приємний випадок використання для карти / зменшення:

function permutations(arr) {
    return (arr.length === 1) ? arr :
    arr.reduce((acc, cv, index) => {
        let remaining = [...arr];
        remaining.splice(index, 1);
        return acc.concat(permutations(remaining).map(a => [].concat(cv,a)));
    }, []);
}
  • Спочатку ми обробляємо базовий випадок і просто повертаємо масив, якщо в ньому є лише предмет
  • У всіх інших випадках
    • ми створюємо порожній масив
    • петля над вхідним масивом
    • і додати масив поточного значення та всі перестановки решти масиву [].concat(cv,a)

1

Ось мінімальна версія ES6. Зрівняти і без функцій можна витягнути Лодаш.

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

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

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

Результат:

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

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

2
Чи можете ви додати пояснення, будь ласка.
Мехді Бунья

3
Хоча ця відповідь може вирішити питання, але вона не містить пояснень того, як чи чому це робиться.
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

Досить пізно. Ще на всякий випадок, якщо це комусь допоможе.

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

У мене з’явилася тріщина при створенні версії цього, що намагається бути чітким, але читабельним і чисто функціональним програмуванням.

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

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

Зауважте, що форматування аргументу перетворює рядок введення в масив. Не впевнений, чи це занадто магічно .. Не впевнений, що я бачив це в дикій природі. Для реальної читабельності я, мабуть, замість цього зробив input = [...input]би перший рядок функції.


1

Це реалізація алгоритму Heap (подібно до @ le_m's), за винятком рекурсивного.

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

Схоже, це теж досить швидко: https://jsfiddle.net/3brqzaLe/


1

Мій перший внесок на сайт. Крім того, згідно з тестами, які я зробив, цей код працює швидше, ніж всі інші методи, згадані тут до цієї дати, звичайно, це мінімально, якщо значень мало, але час додається занадто багато.

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

Я додав порівняння ефективності stackoverflow.com/a/37580979/1647737 - не соромтеся оновити.
le_m

0

Я написав пост, щоб продемонструвати, як переставити масив у JavaScript. Ось код, який це робить.

var count=0;
function permute(pre,cur){ 
    var len=cur.length;
    for(var i=0;i<len;i++){
        var p=clone(pre);
        var c=clone(cur);
        p.push(cur[i]);
        remove(c,cur[i]);
        if(len>1){
            permute(p,c);
        }else{
            print(p);
            count++;
        }
    }
}
function print(arr){
    var len=arr.length;
    for(var i=0;i<len;i++){
        document.write(arr[i]+" ");
    }
    document.write("<br />");
}
function remove(arr,item){
    if(contains(arr,item)){
        var len=arr.length;
        for(var i = len-1; i >= 0; i--){ // STEP 1
            if(arr[i] == item){             // STEP 2
                arr.splice(i,1);              // STEP 3
            }
        }
    }
}
function contains(arr,value){
    for(var i=0;i<arr.length;i++){
        if(arr[i]==value){
            return true;
        }
    }
    return false;
}
function clone(arr){
    var a=new Array();
    var len=arr.length;
    for(var i=0;i<len;i++){
        a.push(arr[i]);
    }
    return a;
}

Просто зателефонуйте

перестановка ([], [1,2,3,4])

буду працювати. Для отримання детальної інформації про те, як це працює, зверніться до пояснення в цій публікації.


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);
    }, []);
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.