พีชคณิตใน 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 ฉันได้คิดวิธีการทำเช่นนี้ในงูหลาม

คำตอบ:


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 ที่ฉันมี "ฟังก์ชั่นการจัดการรายการทั้งหมด" ของฉัน ถ้าฉันใช้มันด้วย permute ([1,2,3]), และ permute ภายหลัง ([4,5,6]), ผลลัพธ์ของภายหลังนั้นยังคงมีผลลัพธ์, ผลลัพธ์จากอันแรก ความคิดใดที่จะแก้ไขได้อย่างไร ขอบคุณมาก !
500


15
เข้าถึง globals ในฟังก์ชั่นของคุณแบบฟอร์มไม่ดี
Shmiddty

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 และบันทึกและมันเริ่มต้นการบันทึกเป็นค่าของบันทึกเว้นแต่เป็นเท็จ (รวมถึงไม่ได้กำหนด) ซึ่งในกรณีนี้มันจะเป็นอาร์เรย์ที่ว่างเปล่า กล่าวอีกนัยหนึ่งมันเป็นวิธีที่ดีกว่าการให้พารามิเตอร์ฟังก์ชันเป็นค่าเริ่มต้น
นาย Lavalamp

สิ่งนี้จะแก้ไขอาเรย์ดั้งเดิม
Shmiddty

2
เป็นslice()ในpermute(curr.slice(), m.concat(next))สิ่งที่จำเป็นจริงๆ?
Yoav

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/ … - หรือคุณกำลังถามเฉพาะเกี่ยวกับการแก้ไขวิธีนี้ (Heap's) หรือไม่?
le_m

@le_m ใช่โดยเฉพาะการใช้วิธีนี้ (Heap's) เพราะมันเร็วมาก
a4xrbj1

@ a4xrbj1 ฉันจะคำนวณการรวมกันของความยาวคงที่ n (เช่น AB, AC, BC สำหรับ n = 2) โดยใช้กลยุทธ์ที่คล้ายกับลิงก์ที่ให้ไว้ข้างต้น (ดูstackoverflow.com/questions/127704/… ) สำหรับแต่ละชุดค่าผสม คำนวณพีชคณิตทั้งหมดโดยใช้วิธีของฮีป กรณีพิเศษเช่น n = 2 สามารถเพิ่มประสิทธิภาพได้แน่นอน
le_m

1
เวอร์ชันตัวสร้างไม่ทำงานอย่างถูกต้องคุณควรทำyield permutation.slice()ถ้าคุณไม่เชือดคุณเพียงคำนวณการเปลี่ยนแปลงครั้งล่าสุด
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
ว้าวแม้จะมีความตึงและขาดเอกสาร แต่ฉันคิดว่านี่เป็นคำตอบที่หรูหราที่สุด คำอธิบายของฉันของอัลกอริทึมนี้คือ: สำหรับทุกรายการในอาร์เรย์ (ลด) ให้เลือกรายการอื่น ๆ ทั้งหมดเปลี่ยนมัน (เรียกซ้ำ) และเชื่อมต่อกับรายการนี้
aaron

ลองใช้วิธีแก้ไขนี้ที่นี่: codewars.com/kata/reviews/5254ca2719453dcc0b000280/groups/…ฉันได้ถอดรหัสกอล์ฟดั้งเดิมออกเป็นรหัสที่อ่านได้ แต่มันก็เหมือนกัน ปัญหาของมันคือมันสร้างสิ่งที่ซ้ำกันและฉันต้องทำเพิ่มเติม.filter(uniq)เกี่ยวกับผลลัพธ์
Andrey Mikhaylov - lolmaus

1
มีเสียงกระเพื่อมขนานกับแนวคิด[1,2,3].length == 3 && "foo" || "bar"หรือ[1,2].length == 3 && "foo" || "bar"โอ้ฉัน! มี! (or (and (= 3 2) (print "hello!")) (print "goodbye"))
Dmitry

@ lolmaus-AndreyMikhaylov วิธีการลบความซ้ำซ้อนโปรดอัปเดตคำตอบหากคุณสามารถทำได้
Pardeep Jain

@PardeepJain ฉันให้ลิงก์ไปยังโซลูชันของฉันด้านบน
Andrey Mikhaylov - 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 ~ 35ms


1
ดูเหมือนว่าจะทำงานได้เร็วจริงๆบางครั้งเร็วกว่าบางครั้งช้ากว่าวิธี Heap ใน FF / Ubuntu สำหรับการทำซ้ำความยาว / การอุ่นเครื่องที่แตกต่างกัน ฯลฯ จะต้องใช้ jsperf เพื่อดูผลลัพธ์สำหรับเอ็นจิ้นต่างๆ
le_m

1
@le_m ตกลงฉันได้ทำการทดสอบบางอย่าง @JSBenบน Ubuntu และ AMD CPU: เมื่อใช้ Chrome rotatePerm(ด้านบน) จะเร็วขึ้น 1.2 เท่า ด้วย FF ไม่มีความสอดคล้อง หลังจากการทดสอบหลายheapPermครั้งเร็วขึ้น 2 เท่าบางครั้งrotatePermก็เร็วขึ้น 1.1 เท่า ด้วยเบราว์เซอร์ชุดเว็บอื่น ๆ เช่น Opera หรือ Epiphany rotatePermจะกลายเป็น 1.1 เท่าเร็วขึ้น อย่างไรก็ตามด้วย Edge heapPermนั้นเร็วขึ้น 1.2 เท่าทุกครั้ง
Redu

1
ดี! ดูเหมือนว่า - อย่างน้อยใน FF / Ubuntu - ประสิทธิภาพของวิธีการฮีปส่วนใหญ่ขึ้นอยู่กับประสิทธิภาพของการคัดลอกอาเรย์ ฉันแก้ไขเกณฑ์มาตรฐานของคุณเพื่อเปรียบเทียบการแบ่งส่วนและการกด: jsben.ch/#/x7mYh - บน FF และสำหรับอาร์เรย์อินพุตขนาดเล็กการกดดูเหมือนจะเร็วกว่ามาก
le_m

2
จะดีมากถ้าวิธีฮีปสามารถเอาชนะประสิทธิภาพได้ อย่างไรก็ตามวิธีการของคุณสร้างผลลัพธ์เช่นเดียวกับอัลกอริทึมของ Langdon (หน้า 16) จากกระดาษ 1977 เดียวกันที่ฉันใช้เป็นข้อมูลอ้างอิงสำหรับวิธีการของ Heap: homepage.math.uiowa.edu/~goodman/22m150.dir/2007/
le_m

2
@le_m ฉันเพิ่งตรวจสอบและดูเหมือนว่าจะเป็นสิ่งเดียวกัน ฉันดูเหมือนจะหมุนเหมือนที่เขาใช้ เพียง 40 ปีล่าช้า ตามที่ฉันได้กล่าวไว้ในคำตอบของฉันมันเป็นวิธีที่ง่ายมาก กล่าวถึงว่าเป็นตัวเลือกเฉพาะเมื่อมีการหมุนเร็วเท่านั้น ขณะนี้ฉันอยู่ใน Haskell และมีวิธีการในการสร้างรายการ (สมมติว่าอาร์เรย์) วนไปเรื่อย ๆ (การประเมินแบบสันหลังยาวทำให้การทำซ้ำไม่มีที่สิ้นสุดไม่มีปัญหา) และนี่อาจเป็นประโยชน์ ถึงกระนั้น Haskell ก็มีpermutationsฟังก์ชั่นมาตรฐานอยู่แล้ว:)
Redu

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หมายของอะไรและทำไมจึงใช้
Pramesh Bajracharya

รหัสของฉันใช้ "อัลกอริทึมของฮีป" เพื่อสร้างพีชคณิตเรียงลำดับ ดังนั้นหากคุณต้องการทราบว่าโค้ดของฉันทำงานอย่างไรภายใต้ประทุนอ่านคำอธิบายของอัลกอริทึมของ Heap นี้: en.m.wikipedia.org/wiki/Heap%27s_algorithm ของ
Vladislav

คุณพยายามพิมพ์ผลลัพธ์หรือไม่ วิธีการควบคุมสูงสุดถ้าองค์ประกอบอาร์เรย์มากกว่า 10?
Marvix

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

คำตอบอื่น ๆ ส่วนใหญ่ไม่ได้ใช้ฟังก์ชั่นตัวสร้างจาวาสคริปต์ซึ่งเป็นโซลูชั่นที่สมบูรณ์แบบสำหรับปัญหาประเภทนี้ คุณอาจต้องการการเปลี่ยนแปลงเพียงครั้งเดียวในหน่วยความจำ นอกจากนี้ฉันชอบที่จะสร้างการเปลี่ยนแปลงของช่วงของดัชนีเพราะมันช่วยให้ฉันสามารถจัดทำดัชนีการเปลี่ยนแปลงแต่ละครั้งและกระโดดไปที่การเปลี่ยนแปลงใด ๆ ที่เฉพาะเจาะจงเช่นเดียวกับที่ใช้ในการเปลี่ยนแปลงคอลเลกชันอื่น ๆ

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

เอาต์พุตการเรียงสับเปลี่ยนคำสั่งทางพจนานุกรม ใช้ได้กับตัวเลขเท่านั้น ในกรณีอื่นคุณต้องเปลี่ยนวิธีการแลกเปลี่ยนที่บรรทัดที่ 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 เวอร์ชั่นขั้นต่ำ สามารถดึงแบนและไม่มีฟังก์ชั่นได้จาก 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])
    ));

ผลลัพธ์:

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
คุณช่วยเพิ่มคำอธิบายได้ไหม
Mehdi Bounya

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);
    }, []);
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.