Hoán vị trong JavaScript?


138

Tôi đang cố gắng viết một hàm thực hiện như sau:

  • lấy một mảng các số nguyên làm đối số (ví dụ: [1,2,3,4])
  • tạo ra một mảng gồm tất cả các hoán vị có thể có [1,2,3,4], với mỗi hoán vị có độ dài là 4

hàm bên dưới (tôi tìm thấy nó trực tuyến) thực hiện điều này bằng cách lấy một chuỗi làm đối số và trả về tất cả các hoán vị của chuỗi đó

Tôi không thể tìm ra cách sửa đổi nó để làm cho nó hoạt động với một loạt các số nguyên, (tôi nghĩ rằng điều này có liên quan đến cách một số phương thức hoạt động khác nhau trên các chuỗi so với các số nguyên, nhưng tôi không chắc chắn. ..)

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

Lưu ý: Tôi đang tìm cách làm cho hàm trả về các mảng số nguyên , không phải là một chuỗi các chuỗi .

Tôi thực sự cần giải pháp để có trong JavaScript. Tôi đã tìm ra cách để làm điều này trong python

Câu trả lời:


106

Nếu bạn nhận thấy, mã thực sự chia các ký tự thành một mảng trước khi thực hiện bất kỳ hoán vị nào, vì vậy bạn chỉ cần loại bỏ thao tác nối và tách

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. Một cái gì đó kỳ lạ đang xảy ra với tôi khi cố gắng sử dụng chức năng của bạn. Tôi giữ nó trong một .js nơi tôi có tất cả "chức năng thao tác danh sách" của mình. Nếu tôi sử dụng nó với permute ([1,2,3]), và sau đó là permute ([4,5,6]), đầu ra của cái sau vẫn có kết quả, đầu ra từ cái đầu tiên. Bất cứ ý tưởng làm thế nào để khắc phục điều đó ? Cảm ơn nhiều !
500


15
Truy cập toàn cầu trong chức năng của bạn, hình thức xấu!
Shmiddty

123

Hơi muộn, nhưng muốn thêm một phiên bản thanh lịch hơn một chút ở đây. Có thể là bất kỳ mảng nào ...

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

Thêm phiên bản ES6 (2015). Cũng không làm thay đổi mảng đầu vào ban đầu. Hoạt động trong bảng điều khiển trong 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;
}

Vì thế...

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

Sản lượng ...

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

Và ...

permutator([1,2,3]);

Sản lượng ...

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

1
Nếu bạn có một chức năng giai thừa tiện dụng (như rất có thể xem xét bạn đang xử lý các hoán vị), bạn có thể tăng tốc nó bằng cách thay đổi khởi tạo phạm vi bên ngoài thành var results = new Array(factorial(inputArr.length)), length=0, sau đó thay thế results.push(…)bằngresults[length++]=…
Cyoce

1
Dòng var cur, memo = memo || [];làm gì?
Ricevind

2
@ user2965967 Nó khai báo cur và memo, và nó khởi tạo memo là giá trị của memo, trừ khi nó là falsey (bao gồm cả không xác định), trong trường hợp đó sẽ là một mảng trống. Nói cách khác, đó là một cách ít lý tưởng hơn để cung cấp tham số hàm với giá trị mặc định.
Ông Lavalamp

Điều này sửa đổi các mảng ban đầu.
Shmiddty

2
slice()trong permute(curr.slice(), m.concat(next))thực sự cần thiết?
Yoav

82

Thuật toán rất hiệu quả sau đây sử dụng phương pháp của Heap để tạo ra tất cả các hoán vị của các phần tử N với độ phức tạp thời gian chạy trong 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]));

Thuật toán tương tự được triển khai như một trình tạo có độ phức tạp không gian trong O (N):

So sánh hiệu suất

Vui lòng thêm triển khai của bạn vào bộ thử nghiệm.j.j sau đây :

Kết quả thời gian chạy cho Chrome 48:


1
Làm thế nào mã này có thể được thay đổi để cung cấp kết quả cho n = 2 cố định? Ví dụ: giả sử chúng ta có một bộ gồm ba chữ cái: A, B và C. Chúng ta có thể hỏi có bao nhiêu cách để chúng ta có thể sắp xếp 2 chữ cái từ bộ đó. Mỗi sự sắp xếp có thể sẽ là một ví dụ về hoán vị. Danh sách đầy đủ các hoán vị có thể sẽ là: AB, AC, BA, BC, CA và CB.
a4xrbj1

1
@ a4xrbj1 Xem ví dụ mẫu mã trong câu hỏi này: stackoverflow.com/questions/37892738/ Khăn - hoặc bạn đang hỏi cụ thể về việc sửa đổi phương thức (Heap's) này?
le_m

@le_m có, cụ thể là sử dụng phương thức (Heap's) này vì nó quá nhanh
a4xrbj1

@ a4xrbj1 Tôi sẽ tính tất cả các kết hợp có độ dài cố định n (ví dụ AB, AC, BC cho n = 2) bằng cách sử dụng một chiến lược tương tự với liên kết đã cho ở trên (xem thêm stackoverflow.com/questions/127704/, ) và sau đó cho từng kết hợp tính toán tất cả các hoán vị của nó bằng phương pháp của Heap. Các trường hợp đặc biệt như n = 2 tất nhiên có thể được tối ưu hóa.
le_m

1
Phiên bản trình tạo không hoạt động chính xác, bạn nên làm yield permutation.slice()nếu bạn không cắt, bạn chỉ tính được hoán vị cuối cùng.
Beldar

41
var inputArray = [1, 2, 3];

var result = inputArray.reduce(function permute(res, item, key, arr) {
    return res.concat(arr.length > 1 && arr.slice(0, key).concat(arr.slice(key + 1)).reduce(permute, []).map(function(perm) { return [item].concat(perm); }) || item);
}, []);


alert(JSON.stringify(result));

10
Wow, mặc dù căng thẳng và thiếu tài liệu, tôi nghĩ rằng đây là câu trả lời thanh lịch nhất. Giải thích của tôi về thuật toán này là: Đối với mọi mục trong mảng (giảm), chọn tất cả các mục khác, hoán vị chúng (đệ quy) và nối với mục này.
aaron

Đã thử giải pháp này tại đây: codewars.com/kata/reviews/5254ca2719453dcc0b000280/groups/ trộm Tôi đã bỏ mã golf ban đầu thành một mã có thể đọc được, nhưng về cơ bản là giống nhau. Vấn đề với nó là nó tạo ra các bản sao và tôi đã phải làm thêm .filter(uniq)về kết quả.
Andrey Mikhaylov - lolmaus

1
Có một lisp song song với khái niệm [1,2,3].length == 3 && "foo" || "bar"hay [1,2].length == 3 && "foo" || "bar"oh my! có! (or (and (= 3 2) (print "hello!")) (print "goodbye"))
Dmitry

@ lolmaus-AndreyMikhaylov làm thế nào để loại bỏ trùng lặp xin vui lòng Cập nhật câu trả lời nếu bạn có thể
Pardeep Jain

@PardeepJain Tôi đã đưa ra một liên kết đến giải pháp của tôi ở trên.
Andrey Mikhaylov - lolmaus

21

Tôi đã cải thiện câu trả lời của SiGanteng .

Bây giờ có thể gọi permutenhiều lần, bởi vì permArrusedCharsđược xóa mỗi lần.

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

Hàm sau hoán vị một mảng thuộc bất kỳ loại nào và gọi hàm gọi lại được chỉ định trên mỗi hoán vị được tìm thấy:

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

Nếu bạn gọi nó như thế này:

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

Tôi nghĩ rằng nó sẽ làm chính xác những gì bạn cần - điền vào một mảng được gọi resultvới hoán vị của mảng [1, 2, 3]. Kết quả là:

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

Mã rõ ràng hơn một chút trên JSFiddle: http://jsfiddle.net/MgmMg/6/


10

Hầu hết các câu trả lời cho câu hỏi này sử dụng các thao tác đắt tiền như chèn liên tục và xóa các mục trong một mảng hoặc sao chép mảng theo cách lặp lại.

Thay vào đó, đây là giải pháp quay lui điển hình:

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

Vì mảng kết quả sẽ rất lớn, nên có thể lặp lại từng kết quả một thay vì phân bổ tất cả dữ liệu cùng một lúc. Trong ES6, điều này có thể được thực hiện với các trình tạo:

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

Đây là một nhiệm vụ thú vị và đây là đóng góp của tôi. Nó rất đơn giản và nhanh chóng. Nếu quan tâm xin vui lòng chịu với tôi và đọc tiếp.

Nếu bạn muốn công việc này nhanh chóng, bạn chắc chắn phải tham gia vào chương trình năng động. Có nghĩa là bạn nên quên đi cách tiếp cận đệ quy. Chắc chắn rồi...

OK đang le_m của trong đó sử dụng phương pháp của Heap có vẻ là nhanh nhất cho đến nay. Chà, tôi chưa có tên cho thuật toán của mình, tôi không biết liệu nó đã được thực hiện hay chưa nhưng nó rất đơn giản và nhanh chóng. Như với tất cả các phương pháp lập trình động, chúng tôi sẽ bắt đầu với vấn đề đơn giản nhất và đi đến kết quả cuối cùng.

Giả sử rằng chúng ta có một mảng a = [1,2,3]chúng ta sẽ bắt đầu với

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

Sau đó làm theo ba bước sau;

  1. Đối với mỗi mục của rmảng (kết quả), chúng tôi sẽ thêm mục tiếp theo của mảng đầu vào.
  2. Chúng tôi sẽ xoay từng mục có độ dài nhiều lần và sẽ lưu trữ từng trường hợp ở mảng kết quả tạm thời t. (tốt ngoại trừ lần đầu tiên không lãng phí thời gian với 0 vòng quay)
  3. Khi chúng tôi kết thúc với tất cả các mục của rmảng tạm thời tsẽ giữ mức kết quả tiếp theo để chúng tôi thực hiện r = t; t = [];và tiếp tục cho đến khi độ dài của mảng đầu vào a.

Vì vậy, sau đây là các bước của chúng tôi;

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

Đây là mã

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

Trong nhiều thử nghiệm tôi đã thấy nó giải quyết 120 hoán vị [0,1,2,3,4] trong 2000 lần trong 25 ~ 35ms.


1
Nó dường như chạy rất nhanh, đôi khi nhanh hơn, đôi khi chậm hơn phương thức Heap trên FF / Ubuntu cho các lần lặp dài / khởi động khác nhau, v.v. Sẽ cần một jsperf để xem kết quả cho các công cụ khác nhau.
le_m

1
@le_m OK tôi đã thực hiện một số thử nghiệm @JSBen Trên CPU Ubuntu & AMD: Với Chrome rotatePerm(cái ở trên) nhanh hơn 1,2. Với FF không có sự thống nhất. Sau nhiều lần kiểm tra đôi khi heapPermnhanh hơn 2 lần, một số rotatePermlần nhanh hơn 1,1 lần. Với các trình duyệt web-kit khác như Opera hay Epiphany rotatePermluôn bật nhanh hơn 1,1 lần. Tuy nhiên với Edge heapPermluôn nhanh hơn 1,2 lần mỗi lần.
Redu

1
Đẹp! Có vẻ như - ít nhất là trên FF / Ubuntu - hiệu suất của phương thức heap chủ yếu phụ thuộc vào hiệu suất sao chép mảng. Tôi đã sửa đổi điểm chuẩn của bạn để so sánh việc cắt so với đẩy: jsben.ch/#/x7mYh - trên FF và đối với các mảng đầu vào nhỏ, việc đẩy dường như nhanh hơn nhiều
le_m

2
Sẽ là tuyệt vời nếu phương pháp heap có thể được đánh bại hiệu suất khôn ngoan. Nhân tiện, phương thức của bạn tạo ra cùng một đầu ra như thuật toán của Langdon (trang 16) từ cùng một bài báo năm 1977 mà tôi đã sử dụng làm tài liệu tham khảo cho phương pháp của Heap: home.math.uiowa.edu/~goodman/22m150.dir/2007/iêu
le_m

2
@le_m Mình mới kiểm tra và có vẻ là điều tương tự. Tôi dường như làm luân chuyển như anh thực hiện. Chỉ với 40 năm trì hoãn. Như tôi đã đề cập trong câu trả lời của mình, thực tế nó là một phương pháp rất đơn giản. Được đề cập là sự lựa chọn chỉ khi có vòng quay nhanh. Hiện tại tôi đang tham gia Haskell và nó có một phương thức tích hợp để tạo một danh sách (giả sử mảng) theo chu kỳ (đánh giá lười biếng làm cho sự lặp lại vô hạn không có vấn đề gì) và điều này có thể có ích. Tuy nhiên, Haskell đã có permutationschức năng tiêu chuẩn :)
Redu

6

Một số phiên bản lấy cảm hứng từ 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

Trả lời mà không cần một mảng bên ngoài hoặc chức năng bổ sung

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

bạn có thể thực hiện một COmbination từ nó? stackoverflow.com/questions/53555563/
Mạnh

5

Phiên bản nhanh nhất, hiệu quả nhất (bán lại) hiệu quả nhất và thanh lịch nhất hiện nay (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`)


Xin chào, bạn có phiền giải thích ý len % 2 // parity dependent adjacent elements swapnghĩa của nó và tại sao nó được sử dụng không?
Pramesh Bajracharya

Mã của tôi sử dụng "thuật toán của Heap" để tạo các hoán vị mảng. Vì vậy, nếu bạn muốn biết mã của tôi hoạt động như thế nào, hãy đọc phần giải thích này về thuật toán của Heap: en.m.wikipedia.org/wiki/Heap%27s_alacticm
Vladislav Ladicky

Bạn đã thử in kết quả? Làm thế nào để kiểm soát tối đa nếu các phần tử mảng trên 10?
Marvix

4

Đây là một giải pháp tuyệt vời

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

Đây là một giải pháp "đệ quy" hơn.

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

Đầu ra:

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

bạn có thể làm cho một sự kết hợp cho nó? stackoverflow.com/questions/53555563/
Mạnh

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

Kiểm tra nó với:

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

2

Hầu hết các câu trả lời khác không sử dụng các hàm tạo javascript mới, đây là một giải pháp hoàn hảo cho loại vấn đề này. Bạn có thể chỉ cần một hoán vị tại thời điểm trong bộ nhớ. Ngoài ra, tôi thích tạo ra một hoán vị của một loạt các chỉ số vì điều này cho phép tôi lập chỉ mục cho từng hoán vị và nhảy thẳng đến bất kỳ hoán vị cụ thể nào cũng như được sử dụng để hoán vị bất kỳ bộ sưu tập nào khác.

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

Đây là một cái tôi đã là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;},[]);

Và đây là một lần nữa nhưng được viết ít căng thẳng hơn! ...

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

Cách thức hoạt động: Nếu mảng dài hơn một phần tử, nó sẽ bước qua từng phần tử và nối nó với một lệnh gọi đệ quy với chính các phần tử còn lại làm đối số. Nó không làm thay đổi mảng ban đầu.


2

Chức năng trả lời bằng cách sử dụng 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>

Đầu ra hoán vị theo thứ tự từ vựng. Chỉ hoạt động với số. Trong trường hợp khác, bạn phải thay đổi phương thức trao đổi trên dòng 34.


1

Tương tự như tinh thần với giải pháp kiểu Haskell của @crl, nhưng làm việc với 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

Đây là trường hợp sử dụng rất hay cho map / less:

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)));
    }, []);
}
  • Đầu tiên, chúng tôi xử lý trường hợp cơ sở và chỉ cần trả về mảng nếu chỉ có trên mục trong đó
  • Trong tất cả các trường hợp khác
    • chúng tôi tạo ra một mảng trống
    • vòng lặp trên mảng đầu vào
    • và thêm một mảng của giá trị hiện tại và tất cả các hoán vị của mảng còn lại [].concat(cv,a)

1

Đây là một phiên bản ES6 tối thiểu. Làm phẳng và không có chức năng có thể được kéo từ 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])
    ));

Kết quả:

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
Bạn có thể thêm một lời giải thích xin vui lòng.
Mehdi Bounya

3
Trong khi câu trả lời này có thể giải quyết câu hỏi, nó không chứa lời giải thích về cách thức hoặc lý do tại sao nó làm như vậy.
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

Khá muộn. Vẫn chỉ trong trường hợp nếu điều này giúp bất cứ ai.

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

Tôi đã có một vết nứt trong việc tạo ra một phiên bản này cố gắng ngắn gọn nhưng dễ đọc và hoàn toàn là lập trình chức năng.

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

Lưu ý rằng định dạng đối số biến một chuỗi đầu vào thành một mảng. Không chắc điều đó có hơi kỳ diệu không .. Không chắc là tôi đã nhìn thấy nó trong tự nhiên. Để dễ đọc thực sự, thay vào đó tôi có thể làm input = [...input]cho dòng đầu tiên của hàm.


1

Đây là một triển khai thuật toán của Heap (tương tự như @ le_m's), ngoại trừ nó được đệ quy.

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

Có vẻ như nó cũng khá nhanh hơn: https://jsfiddle.net/3brqzaLe/


1

Đóng góp đầu tiên của tôi cho trang web. Ngoài ra, theo các thử nghiệm mà tôi đã thực hiện, mã này chạy nhanh hơn tất cả các phương thức khác được đề cập ở đây trước ngày này, tất nhiên là tối thiểu nếu có ít giá trị, nhưng thời gian tăng theo cấp số nhân khi thêm quá nhiều.

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

Tôi đã thêm một so sánh hiệu suất stackoverflow.com/a/37580979/1647737 - hãy cập nhật.
le_m

0

Tôi đã viết một bài đăng để trình bày cách hoán vị một mảng trong JavaScript. Đây là mã làm điều này.

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

Chỉ cần gọi

hoán vị ([], [1,2,3,4])

sẽ làm việc. Để biết chi tiết về cách thức này hoạt động, xin vui lòng tham khảo lời giải thích trong bài viết đó.


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);
    }, []);
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.