วิธีรับความแตกต่างระหว่างสองอาร์เรย์ใน JavaScript


752

มีวิธีคืนความแตกต่างระหว่างสองอาร์เรย์ใน JavaScript หรือไม่?

ตัวอย่างเช่น:

var a1 = ['a', 'b'];
var a2 = ['a', 'b', 'c', 'd'];

// need ["c", "d"]

9
สมมาตรหรือไม่สมมาตร?
การแข่งขัน Lightness ใน Orbit

ด้วยฟังก์ชั่น ES6 ใหม่นี้สามารถทำได้ง่ายๆเพียงซับเดียว (ใช้เวลานานมากในการใช้ในเบราว์เซอร์หลัก ๆ ในกรณีใด ๆ ตรวจสอบคำตอบ
ซัลวาดอร์ต้าหลี่

1
สิ่งสำคัญของการแก้ปัญหาคือประสิทธิภาพ ความซับซ้อนของเวลาแบบ asymptotic ของการทำงานประเภทนี้ - ในภาษาอื่น - คือO(a1.length x log(a2.length))- ประสิทธิภาพนี้เป็นไปได้ใน JavaScript หรือไม่?
Raul

คำตอบ:


218

ฉันคิดว่าคุณกำลังเปรียบเทียบอาร์เรย์ปกติ ถ้าไม่คุณจะต้องเปลี่ยนfor for loop เป็นfor .. in loop

function arr_diff (a1, a2) {

    var a = [], diff = [];

    for (var i = 0; i < a1.length; i++) {
        a[a1[i]] = true;
    }

    for (var i = 0; i < a2.length; i++) {
        if (a[a2[i]]) {
            delete a[a2[i]];
        } else {
            a[a2[i]] = true;
        }
    }

    for (var k in a) {
        diff.push(k);
    }

    return diff;
}

console.log(arr_diff(['a', 'b'], ['a', 'b', 'c', 'd']));
console.log(arr_diff("abcd", "abcde"));
console.log(arr_diff("zxc", "zxc"));

ทางออกที่ดีกว่าถ้าคุณไม่สนใจความเข้ากันได้แบบย้อนหลังกำลังใช้ตัวกรอง แต่ก็ยังแก้ปัญหานี้ได้ผล


46
สิ่งนี้อาจใช้งานได้ แต่จะทำสามลูปเพื่อทำสิ่งที่สามารถทำได้ในรหัสบรรทัดเดียวโดยใช้วิธีการกรองของ Array
Joshaven Potter

9
เพื่อให้ชัดเจนสิ่งนี้นำความแตกต่างสมมาตรของa1และa2ซึ่งต่างจากคำตอบอื่น ๆ ที่โพสต์ไว้ที่นี่
200_success

25
นี่ไม่ใช่คำตอบที่ดีที่สุด แต่ฉันจะมอบการโหวตให้กับองค์กรการกุศลเพื่อช่วยชดเชย downvote ที่ไม่เป็นธรรม เฉพาะคำตอบที่ผิดควรถูกลดระดับลงและถ้าฉันกำลังทำงานในโครงการที่มี cruft-browser ในขอบเขต (เวลาที่ยากลำบากเกิดขึ้น) คำตอบนี้อาจเป็นประโยชน์
Michael Scheper

3
ฉันอาจจะรู้ว่าสิ่งที่จะเกิดขึ้นเมื่อvar a1 = ['a', 'b'];และvar a2 = ['a', 'b', 'c', 'd', 'b'];, ก็จะกลับคำตอบที่ผิดคือแทนที่จะ['c', 'd', 'b'] ['c', 'd']
skbly7

4
วิธีที่เร็วที่สุดคือทางออกที่ไร้เดียงสาที่สุด ฉันได้ทดสอบวิธีแก้ปัญหาที่เสนอทั้งหมดสำหรับ diff symmetric ในหัวข้อนี้และผู้ชนะคือ:function diff2(a, b) { var i, la = a.length, lb = b.length, res = []; if (!la) return b; else if (!lb) return a; for (i = 0; i < la; i++) { if (b.indexOf(a[i]) === -1) res.push(a[i]); } for (i = 0; i < lb; i++) { if (a.indexOf(b[i]) === -1) res.push(b[i]); } return res; }
Nomaed

1230

มีวิธีที่ดีกว่าในการใช้ ES7:


การตัด

 let intersection = arr1.filter(x => arr2.includes(x));

แยกความแตกต่าง Venn Diagram

สำหรับมันจะให้ผลผลิต[1,2,3] [2,3] [2,3]ในทางกลับกันเพราะ[1,2,3] [2,3,5]จะกลับในสิ่งเดียวกัน


ข้อแตกต่าง

let difference = arr1.filter(x => !arr2.includes(x));

ความแตกต่างที่เหมาะสมเวนไดอะแกรม

สำหรับมันจะให้ผลผลิต[1,2,3] [2,3] [1]ในทางกลับกันเพราะ[1,2,3] [2,3,5]จะกลับในสิ่งเดียวกัน


สำหรับความแตกต่างที่สมมาตรคุณสามารถทำได้:

let difference = arr1
                 .filter(x => !arr2.includes(x))
                 .concat(arr2.filter(x => !arr1.includes(x)));

ความแตกต่างสมมาตรเวนไดอะแกรม

ด้วยวิธีนี้คุณจะได้รับอาร์เรย์ที่มีองค์ประกอบทั้งหมดของ arr1 ที่ไม่ได้อยู่ใน arr2 และในทางกลับกัน

@Joshaven Potter ชี้ให้เห็นถึงคำตอบของเขาคุณสามารถเพิ่มสิ่งนี้ลงใน Array.prototype เพื่อให้สามารถใช้งานได้เช่นนี้:

Array.prototype.diff = function(arr2) { return this.filter(x => !arr2.includes(x)); }
[1, 2, 3].diff([2, 3])

3
ฉันชอบการตรวจสอบ< 0มากกว่า== -1
Vic

1
การคำนวณArrayความแตกต่างจะถูกเรียกว่าset operationเนื่องจากการค้นหาสถานที่ให้บริการเป็นงานของตัวเองมากของSets ซึ่งเป็นคำสั่งของขนาดได้เร็วขึ้นแล้ว/indexOf includesกล่าวง่ายๆว่าโซลูชันของคุณไม่มีประสิทธิภาพและค่อนข้างช้า

@ ด้านหลัง แต่ด้วยSetค่าต้องไม่ซ้ำกันใช่ไหม
CervEd

1
@ LuisSieira ฉันเข้าใจว่ามันใช้งาน[1,2,3] [2,3,5]ได้เพราะตัวเลขนั้นไม่เหมือนใคร แต่ถ้าคุณพูด[1,1,2,3] [1,2,3,5]และคาดหวังว่า[1]คุณจะไม่สามารถใช้งานSetได้ วิธีการแก้ปัญหาของคุณจะไม่ได้ผลเช่นกัน: - / ฉันลงเอยด้วยการสร้างฟังก์ชั่นนี้เพราะฉันไม่สามารถหาวิธีที่น่าพึงพอใจที่จะทำมันให้รัดกุมขึ้น หากคุณมีความคิดเกี่ยวกับวิธีการทำเช่นนั้นฉันชอบที่จะรู้!
CervEd

3
ไม่ใช่Array.includes()คุณสมบัติ ES7 แทน ES6 ใช่ไหม (1) (2) - และเพื่อดำเนินการต่อด้วย ES6 คุณสามารถใช้Array.some()เช่น let intersection = aArray.filter(a => bArray.some(b => a === b))ไม่ใช่?
Jari Keinänen

910
Array.prototype.diff = function(a) {
    return this.filter(function(i) {return a.indexOf(i) < 0;});
};

////////////////////  
// Examples  
////////////////////

[1,2,3,4,5,6].diff( [3,4,5] );  
// => [1, 2, 6]

["test1", "test2","test3","test4","test5","test6"].diff(["test1","test2","test3","test4"]);  
// => ["test5", "test6"]

หมายเหตุ indexOf และตัวกรองไม่พร้อมใช้งานก่อนหน้าเช่น ie9


50
เบราว์เซอร์เดียวที่สำคัญที่ไม่รองรับตัวกรองและ indexOf คือ IE8 IE9 รองรับทั้งสองอย่าง ดังนั้นจึงไม่ผิด
ไบรอัน Larsen

14
ie7 และ ie8 ยังคงมีความเกี่ยวข้องมาก (แต่น่าเสียดาย) อย่างไรก็ตามคุณสามารถค้นหารหัส polyfill สำหรับทั้งสองฟังก์ชั่นบนไซต์ MDN: developer.mozilla.org/en/JavaScript/Reference/Global_Objects/… developer.mozilla.org/en/JavaScript/ การอ้างอิง / Global_Objects / ... โหลดในรหัสที่แสดงรายการภายใต้ "ความเข้ากันได้" ผ่าน IE แบบมีเงื่อนไข & BOOM รองรับ Ie7 / 8
1nfiniti

44
โซลูชันนี้มีเวลารันของ O (n ^ 2) โซลูชันเชิงเส้นจะมีประสิทธิภาพมากขึ้น
jholloman

75
หากคุณใช้ฟังก์ชั่นเช่นนี้[1,2,3].diff([3,4,5])มันจะกลับมา[1,2]แทน[1,2,4,5]ดังนั้นจึงไม่สามารถแก้ปัญหาในคำถามเดิมได้สิ่งที่ต้องระวัง
Bugster

12
@AlinPurcaru ไม่รองรับเบราว์เซอร์รุ่นเก่า! = ไม่ถูกต้อง เมื่อพิจารณา Netscape 2.0 รหัส JS ส่วนใหญ่ที่นี่ "ผิด" ตามคำจำกัดความนี้ มันเป็นสิ่งที่โง่ที่จะพูด
NullUserException

304

นี่เป็นวิธีที่ง่ายที่สุดในการรับผลลัพธ์ที่ตรงตามที่คุณต้องการโดยใช้ jQuery:

var diff = $(old_array).not(new_array).get();

diffตอนนี้มีสิ่งold_arrayที่ไม่ได้อยู่ในนั้นnew_array


4
@ แบทแมนใช่ แต่ถ้าพวกเขาอ้างถึงวัตถุเดียวกัน ( {a: 1} != {a: 1}) ( พิสูจน์ )
Matmarbon

เป็นไปได้หรือไม่ที่จะใช้กับอาร์เรย์ที่เก็บข้อมูลของวัตถุที่กำหนดเอง? ฉันลองใช้วิธีเดียวกัน แต่ไม่ได้ผล ความคิดใด ๆ จะได้รับการชื่นชมอย่างมาก
LetMeCodeYou

8
นี่เป็นเคล็ดลับหรือไม่? docพิจารณาวิธีการนี้เป็นส่วนหนึ่งของวิธีการองค์ประกอบ DOMและไม่เป็นผู้ช่วยที่อาร์เรย์ทั่วไป ดังนั้นจึงอาจใช้งานได้ในขณะนี้ แต่อาจไม่ได้อยู่ในเวอร์ชันในอนาคตเนื่องจากไม่ได้มีไว้สำหรับใช้ในลักษณะนี้ แม้ว่าฉันจะมีความสุขถ้ามันจะเป็นผู้ช่วยอาร์เรย์ทั่วไปอย่างเป็นทางการ
robsch

1
@robsch เมื่อคุณใช้.notกับอาร์เรย์ jQuery ใช้มันเป็นยูทิลิตี้ในตัว.grep()ซึ่งเป็นพิเศษสำหรับการกรองอาร์เรย์ ฉันไม่เห็นการเปลี่ยนแปลงนี้
superphonic

1
@vsync ดูเหมือนว่าคุณจะอยู่หลังความแตกต่างที่สมมาตร
superphonic

158

วิธีการแตกต่างในขีดล่าง (หรือการแทนที่แบบเลื่อนลงLo-Dash ) สามารถทำได้เช่นกัน:

(R)eturns the values from array that are not present in the other arrays

_.difference([1, 2, 3, 4, 5], [5, 2, 10]);
=> [1, 3, 4]

เช่นเดียวกับฟังก์ชั่นขีดล่างคุณสามารถใช้ในลักษณะเชิงวัตถุได้มากขึ้น:

_([1, 2, 3, 4, 5]).difference([5, 2, 10]);

4
ฉันคิดว่ามันเป็นวิธีแก้ปัญหาที่ดีและมีประสิทธิภาพโดยเฉพาะอย่างยิ่งเมื่อ lodash และขีดล่างยังคงต่อสู้เพื่อการใช้งานที่ดีที่สุด นอกจากนี้ยังรองรับ IE6
mahemoff

4
ระวังการใช้งานนี้จะไม่ทำงานสำหรับอาร์เรย์ของวัตถุ ดูstackoverflow.com/q/8672383/14731สำหรับข้อมูลเพิ่มเติม
Gili

1
ในฐานะที่เป็นหนึ่งในคำตอบที่กล่าวถึงมันทำงานได้ถ้ามันเป็นวัตถุเดียวกัน แต่ไม่ใช่ถ้าวัตถุสองมีคุณสมบัติเดียวกัน ฉันคิดว่าไม่เป็นไรเนื่องจากแนวคิดเรื่องความเท่าเทียมแตกต่างกันไป (เช่นอาจเป็นแอตทริบิวต์ "id" ในบางแอพ) อย่างไรก็ตามจะเป็นการดีถ้าคุณสามารถผ่านการทดสอบเปรียบเทียบเพื่อตัดกัน ()
mahemoff

สำหรับคนรุ่นหลัง: Lodash ตอนนี้มี _.differenceBy () ซึ่งใช้การโทรกลับเพื่อทำการเปรียบเทียบ หากคุณกำลังเปรียบเทียบวัตถุคุณสามารถวางฟังก์ชั่นที่เปรียบเทียบกับวัตถุที่ต้องการได้
SomeCallMeTim

2
ระวังถ้าลำดับของการขัดแย้งกลับกันมันจะไม่ทำงาน เช่น. _.difference ([5, 2, 10], [1, 2, 3, 4, 5]); ไม่สามารถรับ diff
Russj

79

JavaScript ธรรมดา

มีสองคำที่เป็นไปได้สำหรับ "ความแตกต่าง" ฉันจะให้คุณเลือกอันที่คุณต้องการ บอกว่าคุณมี:

var a1 = ['a', 'b'     ];
var a2 = [     'b', 'c'];
  1. หากคุณต้องการได้รับ['a']ใช้ฟังก์ชั่นนี้:

    function difference(a1, a2) {
      var result = [];
      for (var i = 0; i < a1.length; i++) {
        if (a2.indexOf(a1[i]) === -1) {
          result.push(a1[i]);
        }
      }
      return result;
    }
  2. หากคุณต้องการได้รับ['a', 'c'](องค์ประกอบทั้งหมดที่มีอยู่ในหรืออย่างใดอย่างหนึ่ง แต่ไม่ใช่ทั้งสองอย่าง - ความแตกต่างสมมาตรที่เรียกว่า) ให้ใช้ฟังก์ชั่นนี้:a1a2

    function symmetricDifference(a1, a2) {
      var result = [];
      for (var i = 0; i < a1.length; i++) {
        if (a2.indexOf(a1[i]) === -1) {
          result.push(a1[i]);
        }
      }
      for (i = 0; i < a2.length; i++) {
        if (a1.indexOf(a2[i]) === -1) {
          result.push(a2[i]);
        }
      }
      return result;
    }

Lodash / ขีดล่าง

หากคุณใช้ lodash คุณสามารถใช้_.difference(a1, a2)(กรณีที่ 1 ด้านบน) หรือ_.xor(a1, a2)(กรณีที่ 2)

หากคุณใช้ Underscore.js คุณสามารถใช้_.difference(a1, a2)ฟังก์ชันสำหรับกรณีที่ 1

ชุด ES6 สำหรับอาร์เรย์ที่มีขนาดใหญ่มาก

รหัสด้านบนใช้ได้กับทุกเบราว์เซอร์ อย่างไรก็ตามสำหรับอาร์เรย์ขนาดใหญ่ที่มีมากกว่า 10,000 รายการมันจะค่อนข้างช้าเนื่องจากมีความซับซ้อน O (n²) ในเบราว์เซอร์รุ่นใหม่เราสามารถใช้ประโยชน์จากSetวัตถุES6 เพื่อเร่งความเร็ว Lodash จะใช้โดยอัตโนมัติSetเมื่อพร้อมใช้งาน หากคุณไม่ได้ใช้ lodash ให้ใช้การดำเนินการต่อไปนี้ซึ่งได้รับแรงบันดาลใจจากโพสต์บล็อกของ Axel Rauschmayer :

function difference(a1, a2) {
  var a2Set = new Set(a2);
  return a1.filter(function(x) { return !a2Set.has(x); });
}

function symmetricDifference(a1, a2) {
  return difference(a1, a2).concat(difference(a2, a1));
}

หมายเหตุ

พฤติกรรมสำหรับตัวอย่างทั้งหมดอาจน่าประหลาดใจหรือไม่ชัดเจนถ้าคุณสนใจ-0, +0, NaNหรือ sparse arrays (สำหรับการใช้งานส่วนใหญ่สิ่งนี้ไม่สำคัญ)


ขอบคุณ. คุณบันทึกวันของฉัน ฉันต้องเปรียบเทียบอาร์เรย์ 300K และโซลูชัน "ชุด" ของคุณทำงานได้อย่างสมบูรณ์ นี่ควรเป็นคำตอบที่ยอมรับได้
justadev

52

ในการรับความแตกต่างแบบสมมาตรคุณต้องเปรียบเทียบอาร์เรย์ทั้งสองวิธี (หรือในทุกวิธีในกรณีที่มีหลายอาร์เรย์)

ป้อนคำอธิบายรูปภาพที่นี่


ES7 (ECMAScript 2016)

// diff between just two arrays:
function arrayDiff(a, b) {
    return [
        ...a.filter(x => !b.includes(x)),
        ...b.filter(x => !a.includes(x))
    ];
}

// diff between multiple arrays:
function arrayDiff(...arrays) {
    return [].concat(...arrays.map( (arr, i) => {
        const others = arrays.slice(0);
        others.splice(i, 1);
        const unique = [...new Set([].concat(...others))];
        return arr.filter(x => !unique.includes(x));
    }));
}

ES6 (ECMAScript 2015)

// diff between just two arrays:
function arrayDiff(a, b) {
    return [
        ...a.filter(x => b.indexOf(x) === -1),
        ...b.filter(x => a.indexOf(x) === -1)
    ];
}

// diff between multiple arrays:
function arrayDiff(...arrays) {
    return [].concat(...arrays.map( (arr, i) => {
        const others = arrays.slice(0);
        others.splice(i, 1);
        const unique = [...new Set([].concat(...others))];
        return arr.filter(x => unique.indexOf(x) === -1);
    }));
}

ES5 (ECMAScript 5.1)

// diff between just two arrays:
function arrayDiff(a, b) {
    var arrays = Array.prototype.slice.call(arguments);
    var diff = [];

    arrays.forEach(function(arr, i) {
        var other = i === 1 ? a : b;
        arr.forEach(function(x) {
            if (other.indexOf(x) === -1) {
                diff.push(x);
            }
        });
    })

    return diff;
}

// diff between multiple arrays:
function arrayDiff() {
    var arrays = Array.prototype.slice.call(arguments);
    var diff = [];

    arrays.forEach(function(arr, i) {
        var others = arrays.slice(0);
        others.splice(i, 1);
        var otherValues = Array.prototype.concat.apply([], others);
        var unique = otherValues.filter(function (x, j) { 
            return otherValues.indexOf(x) === j; 
        });
        diff = diff.concat(arr.filter(x => unique.indexOf(x) === -1));
    });
    return diff;
}

ตัวอย่าง:

// diff between two arrays:
const a = ['a', 'd', 'e'];
const b = ['a', 'b', 'c', 'd'];
arrayDiff(a, b); // (3) ["e", "b", "c"]

// diff between multiple arrays
const a = ['b', 'c', 'd', 'e', 'g'];
const b = ['a', 'b'];
const c = ['a', 'e', 'f'];
arrayDiff(a, b, c); // (4) ["c", "d", "g", "f"]

ความแตกต่างระหว่างอาร์เรย์ของวัตถุ

function arrayDiffByKey(key, ...arrays) {
    return [].concat(...arrays.map( (arr, i) => {
        const others = arrays.slice(0);
        others.splice(i, 1);
        const unique = [...new Set([].concat(...others))];
        return arr.filter( x =>
            !unique.some(y => x[key] === y[key])
        );
    }));
}

ตัวอย่าง:

const a = [{k:1}, {k:2}, {k:3}];
const b = [{k:1}, {k:4}, {k:5}, {k:6}];
const c = [{k:3}, {k:5}, {k:7}];
arrayDiffByKey('k', a, b, c); // (4) [{k:2}, {k:4}, {k:6}, {k:7}]

50

แนวทางที่สะอาดกว่าใน ES6 เป็นวิธีแก้ไขปัญหาต่อไปนี้

var a1 = ['a', 'b'];
var a2 = ['a', 'b', 'c', 'd'];

ข้อแตกต่าง

a2.filter(d => !a1.includes(d)) // gives ["c", "d"]

การตัด

a2.filter(d => a1.includes(d)) // gives ["a", "b"]

Union Disjunctive (Symmetric Difference)

[ ...a2.filter(d => !a1.includes(d)),
  ...a1.filter(d => !a2.includes(d)) ]

มันทำงานได้ในทิศทางเดียวเท่านั้น ทีนี้ลองจินตนาการว่าa1 = ['a', 'b', 'e']: eจะไม่ถูกดึงออกมา
imrok

ใช่นั่นคือความแตกต่างในทฤษฎีเซต (a2-a1) สิ่งที่คุณกำลังมองหาคือ (a2-a1) + (a1-a2)
ifelse.codes

1
@imrok ฉันเชื่อว่านี่คือสิ่งที่คุณกำลังมองหา [... a2.filter (d =>! a1.includes (d)), ... (a1.filter (d =>! a2.includes (d)) )]
ifelse.codes

2
ทางออกที่สวยงามขอบคุณ!
Thiago Alves

40

คุณสามารถใช้ชุดในกรณีนี้ มันได้รับการปรับให้เหมาะกับการใช้งานประเภทนี้ (สหภาพ, จุดตัด, ความแตกต่าง)

ตรวจสอบให้แน่ใจว่าใช้กับกรณีของคุณเมื่อไม่อนุญาตให้ทำซ้ำ

var a = new JS.Set([1,2,3,4,5,6,7,8,9]);
var b = new JS.Set([2,4,6,8]);

a.difference(b)
// -> Set{1,3,5,7,9}

4
ดูเหมือนว่าห้องสมุดที่ดี! สิ่งที่เป็นความอัปยศที่คุณไม่สามารถดาวน์โหลดเพียงSetฟังก์ชั่นโดยไม่ต้องได้รับทุกอย่างอื่น ...
Blixt

@Blixt ผมเชื่อว่าคุณสามารถดาวน์โหลดได้ทั้งหมดและมีเพียงแค่ไฟล์ set.js
ซามูเอล Carrijo

ชุดถูกนำมาใช้ในการปิด google เช่นกัน closure-library.googlecode.com/svn/docs/…
Ben Flynn

1
มันเป็นเวลา 1 ปีเหรอ? developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
......

32
function diff(a1, a2) {
  return a1.concat(a2).filter(function(val, index, arr){
    return arr.indexOf(val) === arr.lastIndexOf(val);
  });
}

ผสานทั้งอาร์เรย์ค่าที่ไม่ซ้ำกันจะปรากฏขึ้นเพียงครั้งเดียวดังนั้น indexOf () จะเหมือนกันกับ lastIndexOf ()


3
ฉันยอมรับว่านี่เป็นวิธีที่สะอาดและง่ายที่สุดและดีที่ไม่ต้องมีการสัมผัสต้นแบบ “ หากคุณไม่สามารถอธิบายให้เด็กอายุหกขวบฟังคุณไม่เข้าใจตัวเอง” - Albert Einstein
lacostenycoder

18

หากต้องการลบอาร์เรย์หนึ่งจากอีกแถวหนึ่งให้ใช้ตัวอย่างด้านล่าง:

var a1 = ['1','2','3','4','6'];
var a2 = ['3','4','5'];

var items = new Array();

items = jQuery.grep(a1,function (item) {
    return jQuery.inArray(item, a2) < 0;
});

มันจะส่งกลับ ['1,' 2 ',' 6 '] ซึ่งเป็นรายการของอาร์เรย์แรกที่ไม่มีอยู่ในวินาที

ดังนั้นตามตัวอย่างปัญหาของคุณรหัสต่อไปนี้เป็นทางออกที่แน่นอน:

var array1 = ["test1", "test2","test3", "test4"];
var array2 = ["test1", "test2","test3","test4", "test5", "test6"];

var _array = new Array();

_array = jQuery.grep(array2, function (item) {
     return jQuery.inArray(item, array1) < 0;
});

14

ด้วยการมาถึงของ ES6 ที่มีชุดและตัวดำเนินการเครื่องหมาย (ในขณะที่ใช้งานได้เฉพาะใน Firefox, ตรวจสอบตารางความเข้ากันได้ ) คุณสามารถเขียนหนึ่งซับต่อไปนี้:

var a = ['a', 'b', 'c', 'd'];
var b = ['a', 'b'];
var b1 = new Set(b);
var difference = [...new Set([...a].filter(x => !b1.has(x)))];

[ "c", "d" ]ซึ่งจะส่งผลให้


เพียงแค่อยากรู้ว่าคือการที่แตกต่างกันกว่าการทำb.filter(x => !a.indexOf(x)))
chovy

1
@ chovy มันแตกต่างกันในเวลาที่ซับซ้อน โซลูชันของฉันคือโซลูชันO(n + m)ของคุณคือO(n * m)ที่ n และ m คือความยาวของอาร์เรย์ ใช้เวลานานรายการและโซลูชันของฉันจะทำงานในไม่กี่วินาทีในขณะที่คุณใช้เวลาหลายชั่วโมง
Salvador Dali

สิ่งที่เกี่ยวกับการเปรียบเทียบคุณลักษณะของรายการวัตถุ? เป็นไปได้หรือไม่ที่ใช้โซลูชันนี้
chovy

2
a.filter(x => !b1.has(x))ง่ายกว่า และทราบสเป็คเพียงต้องการความซับซ้อนที่จะอยู่n * f(m) + mกับf(m)เส้นแบ่งย่อยโดยเฉลี่ย มันดีกว่าn * mแต่ก็ไม่จำเป็นn + mเสมอไป
Oriol

2
@SalvadorDali var difference = [...new Set([...a].filter(x => !b1.has(x)))];ทำไมคุณสร้างอาร์เรย์ 'a' ซ้ำกัน เหตุใดคุณจึงเปลี่ยนผลลัพธ์ของตัวกรองเป็นชุดจากนั้นกลับสู่อาร์เรย์ นี่ไม่เทียบเท่ากับvar difference = a.filter(x => !b1.has(x));
Deepak Mittal

13

วิธีการทำงานกับ ES2015

การคำนวณdifferenceระหว่างสองอาร์เรย์เป็นหนึ่งในSetการดำเนินการ คำนี้แสดงว่าSetควรใช้ชนิดเนทีฟเพื่อเพิ่มความเร็วในการค้นหา อย่างไรก็ตามมีการเปลี่ยนลำดับสามครั้งเมื่อคุณคำนวณความแตกต่างระหว่างสองชุด:

[+left difference] [-intersection] [-right difference]
[-left difference] [-intersection] [+right difference]
[+left difference] [-intersection] [+right difference]

นี่คือวิธีการแก้ปัญหาการทำงานที่สะท้อนให้เห็นถึงการเรียงสับเปลี่ยนเหล่านี้

ซ้ายdifference:

// small, reusable auxiliary functions

const apply = f => x => f(x);
const flip = f => y => x => f(x) (y);
const createSet = xs => new Set(xs);
const filter = f => xs => xs.filter(apply(f));


// left difference

const differencel = xs => ys => {
  const zs = createSet(ys);
  return filter(x => zs.has(x)
     ? false
     : true
  ) (xs);
};


// mock data

const xs = [1,2,2,3,4,5];
const ys = [0,1,2,3,3,3,6,7,8,9];


// run the computation

console.log( differencel(xs) (ys) );

ขวาdifference:

differencerไม่สำคัญ มันเป็นเพียงแค่differencelมีข้อโต้แย้งพลิก const differencer = flip(differencel)คุณสามารถเขียนฟังก์ชั่นสำหรับการอำนวยความสะดวก: นั่นคือทั้งหมด!

สมมาตรdifference:

ตอนนี้เรามีด้านซ้ายและด้านขวาการนำสมมาตรมาใช้differenceก็ไม่สำคัญเช่นกัน:

// small, reusable auxiliary functions

const apply = f => x => f(x);
const flip = f => y => x => f(x) (y);
const concat = y => xs => xs.concat(y);
const createSet = xs => new Set(xs);
const filter = f => xs => xs.filter(apply(f));


// left difference

const differencel = xs => ys => {
  const zs = createSet(ys);
  return filter(x => zs.has(x)
     ? false
     : true
  ) (xs);
};


// symmetric difference

const difference = ys => xs =>
 concat(differencel(xs) (ys)) (flip(differencel) (xs) (ys));

// mock data

const xs = [1,2,2,3,4,5];
const ys = [0,1,2,3,3,3,6,7,8,9];


// run the computation

console.log( difference(xs) (ys) );

ฉันเดาว่าตัวอย่างนี้เป็นจุดเริ่มต้นที่ดีที่จะได้รับความประทับใจความหมายของการเขียนโปรแกรมการทำงาน:

การเขียนโปรแกรมด้วย Building Block ที่สามารถเสียบเข้าด้วยกันได้หลายวิธี


12

วิธีการแก้ปัญหาโดยใช้indexOf()จะ ok สำหรับอาร์เรย์ขนาดเล็ก O(n^2)แต่เป็นพวกเขาเติบโตในระยะเวลาการทำงานของอัลกอริทึมแนวทาง นี่คือวิธีแก้ปัญหาที่จะทำงานได้ดีขึ้นสำหรับอาร์เรย์ที่มีขนาดใหญ่มากโดยใช้อ็อบเจกต์เป็นอาเรย์เชื่อมโยงเพื่อจัดเก็บรายการอาร์เรย์เป็นคีย์ มันยังกำจัดรายการที่ซ้ำกันโดยอัตโนมัติ แต่ใช้ได้กับค่าสตริงเท่านั้น (หรือค่าที่สามารถเก็บเป็นสตริงได้อย่างปลอดภัย):

function arrayDiff(a1, a2) {
  var o1={}, o2={}, diff=[], i, len, k;
  for (i=0, len=a1.length; i<len; i++) { o1[a1[i]] = true; }
  for (i=0, len=a2.length; i<len; i++) { o2[a2[i]] = true; }
  for (k in o1) { if (!(k in o2)) { diff.push(k); } }
  for (k in o2) { if (!(k in o1)) { diff.push(k); } }
  return diff;
}

var a1 = ['a', 'b'];
var a2 = ['a', 'b', 'c', 'd'];
arrayDiff(a1, a2); // => ['c', 'd']
arrayDiff(a2, a1); // => ['c', 'd']

คุณต้องการใช้ Object.hasOwnProperty () ทุกครั้งที่คุณทำ "for in" บนวัตถุ มิฉะนั้นคุณจะเสี่ยงต่อการวนลูปผ่านทุกฟิลด์ที่เพิ่มให้กับต้นแบบของออบเจ็กต์เริ่มต้น (หรือแค่พาเรนต์ของวัตถุของคุณ) นอกจากนี้คุณยังต้องการเพียงสองลูปหนึ่งอันสำหรับการสร้างตารางแฮชและอีกอันจะค้นหาบนโต๊ะแฮชนั้น
jholloman

1
@jholloman ผมกราบไม่เห็นด้วย ตอนนี้เราสามารถควบคุมการแจกแจงสำหรับคุณสมบัติใด ๆ ได้อย่างน่าจะเป็นไปได้ว่าคุณควรจะรวมคุณสมบัติใด ๆ ที่คุณได้รับระหว่างการแจงนับ
Phrogz

1
@Phrogz จุดที่ดีถ้าคุณกังวลเกี่ยวกับเบราว์เซอร์ที่ทันสมัยเท่านั้น น่าเสียดายที่ฉันต้องสนับสนุนกลับไปที่ IE7 ในที่ทำงานดังนั้นยุคหินเป็นรถไฟแห่งความคิดเริ่มต้นของฉันและเราไม่ได้ใช้ shims
jholloman

10

คำตอบข้างต้นโดย Joshaven Potter นั้นยอดเยี่ยม แต่จะส่งคืนองค์ประกอบในอาร์เรย์ B ที่ไม่ได้อยู่ในอาร์เรย์ C แต่ไม่ใช่วิธีอื่น ๆ ตัวอย่างเช่นถ้าvar a=[1,2,3,4,5,6].diff( [3,4,5,7]);มันจะออก: ==> [1,2,6]แต่ไม่ใช่ [1,2,6,7]ซึ่งเป็นความแตกต่างที่แท้จริงระหว่างสอง คุณยังคงสามารถใช้รหัสของ Potter ด้านบน แต่เพียงทำซ้ำการเปรียบเทียบหนึ่งครั้งย้อนหลังด้วย:

Array.prototype.diff = function(a) {
    return this.filter(function(i) {return !(a.indexOf(i) > -1);});
};

////////////////////  
// Examples  
////////////////////

var a=[1,2,3,4,5,6].diff( [3,4,5,7]);
var b=[3,4,5,7].diff([1,2,3,4,5,6]);
var c=a.concat(b);
console.log(c);

ผลลัพธ์นี้ควร: [ 1, 2, 6, 7 ]


9

อีกวิธีในการแก้ปัญหา

function diffArray(arr1, arr2) {
    return arr1.concat(arr2).filter(function (val) {
        if (!(arr1.includes(val) && arr2.includes(val)))
            return val;
    });
}

diffArray([1, 2, 3, 7], [3, 2, 1, 4, 5]);    // return [7, 4, 5]

นอกจากนี้คุณยังสามารถใช้ไวยากรณ์ฟังก์ชั่นลูกศร:

const diffArray = (arr1, arr2) => arr1.concat(arr2)
    .filter(val => !(arr1.includes(val) && arr2.includes(val)));

diffArray([1, 2, 3, 7], [3, 2, 1, 4, 5]);    // return [7, 4, 5]

7
Array.prototype.difference = function(e) {
    return this.filter(function(i) {return e.indexOf(i) < 0;});
};

eg:- 

[1,2,3,4,5,6,7].difference( [3,4,5] );  
 => [1, 2, 6 , 7]

คุณไม่ควรขยายวัตถุดั้งเดิมด้วยวิธีนี้ หากมาตรฐานแนะนำให้ใช้differenceเป็นฟังก์ชันในรุ่นอนาคตและฟังก์ชันนี้จะมีลายเซ็นของฟังก์ชันที่แตกต่างจากของคุณมันจะทำลายรหัสหรือไลบรารีต่างประเทศที่ใช้ฟังก์ชันนี้
t.niese

7

โซลูชันที่ง่ายมากพร้อมฟังก์ชันตัวกรองของ JavaScript:

var a1 = ['a', 'b'];
var a2 = ['a', 'b', 'c', 'd'];

function diffArray(arr1, arr2) {
  var newArr = [];
  var myArr = arr1.concat(arr2);
  
    newArr = myArr.filter(function(item){
      return arr2.indexOf(item) < 0 || arr1.indexOf(item) < 0;
    });
   alert(newArr);
}

diffArray(a1, a2);


6

เกี่ยวกับสิ่งนี้:

Array.prototype.contains = function(needle){
  for (var i=0; i<this.length; i++)
    if (this[i] == needle) return true;

  return false;
} 

Array.prototype.diff = function(compare) {
    return this.filter(function(elem) {return !compare.contains(elem);})
}

var a = new Array(1,4,7, 9);
var b = new Array(4, 8, 7);
alert(a.diff(b));

ดังนั้นวิธีนี้คุณสามารถทำarray1.diff(array2)เพื่อให้ได้ความแตกต่าง (ความซับซ้อนของเวลาที่น่ากลัวสำหรับอัลกอริทึม - O (array1.length x array2.length) ฉันเชื่อ)


การใช้ตัวเลือกตัวกรองเป็นแนวคิดที่ดี ... อย่างไรก็ตามคุณไม่จำเป็นต้องสร้างวิธีการบรรจุสำหรับอาร์เรย์ ฉันแปลงความคิดของคุณเป็นหนึ่งซับ ... ขอบคุณสำหรับแรงบันดาลใจ!
Joshaven Potter

คุณไม่จำเป็นต้องกำหนดฟังก์ชั่น contain () JS รวมถึง () ทำสิ่งเดียวกัน
Da Man


4
function diffArray(arr1, arr2) {
  var newArr = arr1.concat(arr2);
  return newArr.filter(function(i){
    return newArr.indexOf(i) == newArr.lastIndexOf(i);
  });
}

มันใช้งานได้สำหรับฉัน


3
  • โซลูชัน JavaScript ที่แท้จริง (ไม่มีไลบรารี)
  • เข้ากันได้กับเบราว์เซอร์รุ่นเก่า (ไม่ใช้filter)
  • O (n ^ 2)
  • fnพารามิเตอร์ตัวเลือกการโทรกลับที่ช่วยให้คุณระบุวิธีเปรียบเทียบรายการอาร์เรย์

function diff(a, b, fn){
    var max = Math.max(a.length, b.length);
        d = [];
    fn = typeof fn === 'function' ? fn : false
    for(var i=0; i < max; i++){
        var ac = i < a.length ? a[i] : undefined
            bc = i < b.length ? b[i] : undefined;
        for(var k=0; k < max; k++){
            ac = ac === undefined || (k < b.length && (fn ? fn(ac, b[k]) : ac == b[k])) ? undefined : ac;
            bc = bc === undefined || (k < a.length && (fn ? fn(bc, a[k]) : bc == a[k])) ? undefined : bc;
            if(ac == undefined && bc == undefined) break;
        }
        ac !== undefined && d.push(ac);
        bc !== undefined && d.push(bc);
    }
    return d;
}

alert(
    "Test 1: " + 
    diff(
        [1, 2, 3, 4],
        [1, 4, 5, 6, 7]
      ).join(', ') +
    "\nTest 2: " +
    diff(
        [{id:'a',toString:function(){return this.id}},{id:'b',toString:function(){return this.id}},{id:'c',toString:function(){return this.id}},{id:'d',toString:function(){return this.id}}],
        [{id:'a',toString:function(){return this.id}},{id:'e',toString:function(){return this.id}},{id:'f',toString:function(){return this.id}},{id:'d',toString:function(){return this.id}}],
        function(a, b){ return a.id == b.id; }
    ).join(', ')
);


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

ไม่มีเหตุผลในการแคชlengthค่า มันเป็นทรัพย์สินธรรมดาอยู่แล้ว jsperf.com/array-length-caching
vp_arth

3

สิ่งนี้ใช้งานได้: โดยทั่วไปผสานทั้งสองอาร์เรย์มองหารายการที่ซ้ำกันและผลักดันสิ่งที่ไม่ได้ทำซ้ำในอาร์เรย์ใหม่ซึ่งเป็นความแตกต่าง

function diff(arr1, arr2) {
  var newArr = [];
  var arr = arr1.concat(arr2);
  
  for (var i in arr){
    var f = arr[i];
    var t = 0;
    for (j=0; j<arr.length; j++){
      if(arr[j] === f){
        t++; 
        }
    }
    if (t === 1){
      newArr.push(f);
        }
  } 
  return newArr;
}


3

// แนวทาง es6

function diff(a, b) {
  var u = a.slice(); //dup the array
  b.map(e => {
    if (u.indexOf(e) > -1) delete u[u.indexOf(e)]
    else u.push(e)   //add non existing item to temp array
  })
  return u.filter((x) => {return (x != null)}) //flatten result
}

3

สมมาตรและความซับซ้อนเชิงเส้น ต้องใช้ ES6

function arrDiff(arr1, arr2) {
    var arrays = [arr1, arr2].sort((a, b) => a.length - b.length);
    var smallSet = new Set(arrays[0]);

    return arrays[1].filter(x => !smallSet.has(x));
}

3

อีกคำตอบหนึ่ง แต่ดูเหมือนว่าไม่มีใครพูดถึง jsperf โดยที่พวกเขาเปรียบเทียบอัลกอริธึมและการสนับสนุนเทคโนโลยีหลายอย่าง: https://jsperf.com/array-difference-javascript ดูเหมือนว่าการใช้ตัวกรองจะได้ผลลัพธ์ที่ดีที่สุด ขอบคุณ


2

แค่คิดว่า ... เพื่อความท้าทาย ;-) จะใช้งานได้ ... (สำหรับอาร์เรย์พื้นฐานของสตริงตัวเลขและอื่น ๆ ) ไม่มีอาร์เรย์ซ้อนกัน

function diffArrays(arr1, arr2, returnUnion){
  var ret = [];
  var test = {};
  var bigArray, smallArray, key;
  if(arr1.length >= arr2.length){
    bigArray = arr1;
    smallArray = arr2;
  } else {
    bigArray = arr2;
    smallArray = arr1;
  }
  for(var i=0;i<bigArray.length;i++){
    key = bigArray[i];
    test[key] = true;
  }
  if(!returnUnion){
    //diffing
    for(var i=0;i<smallArray.length;i++){
      key = smallArray[i];
      if(!test[key]){
        test[key] = null;
      }
    }
  } else {
    //union
    for(var i=0;i<smallArray.length;i++){
      key = smallArray[i];
      if(!test[key]){
        test[key] = true;
      }
    }
  }
  for(var i in test){
    ret.push(i);
  }
  return ret;
}

array1 = "test1", "test2","test3", "test4", "test7"
array2 = "test1", "test2","test3","test4", "test5", "test6"
diffArray = diffArrays(array1, array2);
//returns ["test5","test6","test7"]

diffArray = diffArrays(array1, array2, true);
//returns ["test1", "test2","test3","test4", "test5", "test6","test7"]

โปรดทราบว่าการเรียงลำดับจะไม่เป็นไปตามที่ระบุไว้ข้างต้น ... แต่ถ้าต้องการให้โทร. sort () ในอาร์เรย์เพื่อเรียงลำดับ


2

ฉันต้องการฟังก์ชั่นที่คล้ายกันซึ่งใช้อาร์เรย์เก่าและอาร์เรย์ใหม่และให้อาร์เรย์ของรายการที่เพิ่มเข้ามาและอาร์เรย์ของรายการที่ถูกลบออกและฉันต้องการให้มันมีประสิทธิภาพ

คุณสามารถเล่นกับโซลูชั่นที่นำเสนอของฉันที่นี่: http://jsbin.com/osewu3/12

ทุกคนสามารถเห็นปัญหา / การปรับปรุงอัลกอริทึมนั้นได้หรือไม่? ขอบคุณ!

รายการรหัส:

function diff(o, n) {
  // deal with empty lists
  if (o == undefined) o = [];
  if (n == undefined) n = [];

  // sort both arrays (or this won't work)
  o.sort(); n.sort();

  // don't compare if either list is empty
  if (o.length == 0 || n.length == 0) return {added: n, removed: o};

  // declare temporary variables
  var op = 0; var np = 0;
  var a = []; var r = [];

  // compare arrays and add to add or remove lists
  while (op < o.length && np < n.length) {
      if (o[op] < n[np]) {
          // push to diff?
          r.push(o[op]);
          op++;
      }
      else if (o[op] > n[np]) {
          // push to diff?
          a.push(n[np]);
          np++;
      }
      else {
          op++;np++;
      }
  }

  // add remaining items
  if( np < n.length )
    a = a.concat(n.slice(np, n.length));
  if( op < o.length )
    r = r.concat(o.slice(op, o.length));

  return {added: a, removed: r}; 
}

2

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

    function find_diff(arr1, arr2) {
      diff = [];
      joined = arr1.concat(arr2);
      for( i = 0; i <= joined.length; i++ ) {
        current = joined[i];
        if( joined.indexOf(current) == joined.lastIndexOf(current) ) {
          diff.push(current);
        }
      }
      return diff;
    }

สำหรับรหัสของฉันฉันต้องการสำเนาที่ซ้ำกันออกมาเช่นกัน แต่ฉันเดาว่าไม่เป็นที่ต้องการเสมอไป

ฉันเดาว่าข้อเสียหลักคือมันอาจเปรียบเทียบตัวเลือกมากมายที่ถูกปฏิเสธไปแล้ว


2

แก้ไข Littlebit สำหรับคำตอบที่ดีที่สุด

function arr_diff(a1, a2)
{
  var a=[], diff=[];
  for(var i=0;i<a1.length;i++)
    a[a1[i]]=a1[i];
  for(var i=0;i<a2.length;i++)
    if(a[a2[i]]) delete a[a2[i]];
    else a[a2[i]]=a2[i];
  for(var k in a)
   diff.push(a[k]);
  return diff;
}

สิ่งนี้จะพิจารณาองค์ประกอบประเภทปัจจุบัน b / c เมื่อเราสร้าง [a1 [i]] มันจะแปลงค่าเป็นสตริงจากค่า oroginal ดังนั้นเราจึงสูญเสียค่าที่แท้จริง


สิ่งนี้ยังคงล้มเหลวสำหรับอาร์เรย์ของวัตถุ var a = [{a: 1}], b = [{b: 2}] arr_diff (a, b) == []
EricP
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.