วิธีใดเป็นวิธีที่รวดเร็วหรือสง่างามที่สุดในการคำนวณความแตกต่างของชุดโดยใช้อาร์เรย์ Javascript


103

ให้AและBเป็นสองชุด ฉันกำลังมองหาจริงๆวิธีที่รวดเร็วหรือสง่างามในการคำนวณความแตกต่างที่กำหนด ( A - BหรือA \Bขึ้นอยู่กับความชอบของคุณ) ระหว่างพวกเขา ทั้งสองชุดจะถูกจัดเก็บและจัดการเป็นอาร์เรย์ Javascript ตามที่ชื่อระบุ

หมายเหตุ:

  • เทคนิคเฉพาะของตุ๊กแกไม่เป็นไร
  • ฉันชอบใช้ฟังก์ชันเนทีฟ (แต่ฉันเปิดให้ไลบรารีที่มีน้ำหนักเบาถ้ามันเร็วกว่านี้)
  • ฉันเคยเห็น แต่ไม่ได้ทดสอบJS.Set (ดูจุดก่อนหน้า)

แก้ไข:ฉันสังเกตเห็นความคิดเห็นเกี่ยวกับชุดที่มีองค์ประกอบที่ซ้ำกัน เมื่อฉันพูดว่า "set" ฉันหมายถึงนิยามทางคณิตศาสตร์ซึ่งหมายความว่า (เหนือสิ่งอื่นใด) ที่ไม่มีองค์ประกอบที่ซ้ำกัน


คำศัพท์ "set difference" ที่คุณใช้คืออะไร มันมาจาก C ++ หรืออะไร?
Josh Stodola

ชุดของคุณมีอะไรบ้าง? ขึ้นอยู่กับชนิดที่คุณกำหนดเป้าหมาย (เช่นเบอร์) การคำนวณความแตกต่างชุดสามารถทำได้จริงๆที่รวดเร็วและสง่างาม หากชุดของคุณมี (พูด) องค์ประกอบ DOM คุณจะติดขัดกับการindexOfใช้งานที่ช้า
Crescent Fresh

@Crescent: ชุดของฉันมีตัวเลข - ขออภัยที่ไม่ได้ระบุ @ Josh: เป็นการดำเนินการชุดมาตรฐานในคณิตศาสตร์ ( en.wikipedia.org/wiki/Set_%28mathematics%29#Complements )
Matt Ball


1
@MattBall ไม่ฉันเห็นแบบนั้น แต่คำถามของ Josh ถูกต้องและยังไม่ได้รับคำตอบดังนั้นฉันจึงตอบ :)
แพท

คำตอบ:


173

หากไม่รู้ว่าวิธีนี้มีประสิทธิภาพมากที่สุดหรือไม่ แต่อาจสั้นที่สุด

A = [1, 2, 3, 4];
B = [1, 3, 4, 7];

diff = A.filter(function(x) { return B.indexOf(x) < 0 })

console.log(diff);

อัปเดตเป็น ES6:

A = [1, 2, 3, 4];
B = [1, 3, 4, 7];

diff = A.filter(x => !B.includes(x) );

console.log(diff);

8
+1: ไม่ใช่วิธีแก้ปัญหาที่มีประสิทธิภาพที่สุด แต่สั้นและอ่านง่าย
Christoph

10
หมายเหตุ: ไม่รองรับ array.filter ข้ามเบราว์เซอร์ (เช่นไม่ได้อยู่ใน IE) ดูเหมือนว่าจะไม่สำคัญกับ @Matt เนื่องจากเขาระบุว่า "เทคนิคเฉพาะของตุ๊กแกก็โอเค" แต่ฉันคิดว่ามันคุ้มค่าที่จะกล่าวถึง
Eric Bréchemier

45
เรื่องนี้ช้ามาก O (| A | * | B |)
glebm

1
@ EricBréchemierตอนนี้รองรับแล้ว (ตั้งแต่ IE 9) Array.prototype.filterเป็นคุณลักษณะ ECMAScript มาตรฐาน
Quentin Roy

5
ใน ES6 คุณสามารถใช้!B.includes(x)แทนB.indexOf(x) < 0:)
c24w

86

7 ปีต่อมาด้วยSet object ของ ES6มันค่อนข้างง่าย (แต่ก็ยังไม่กะทัดรัดเท่าของ python A - B ) และมีรายงานว่าเร็วกว่าindexOfอาร์เรย์ขนาดใหญ่:

console.clear();
let a = new Set([1, 2, 3, 4]);
let b = new Set([5, 4, 3, 2]);


let a_minus_b = new Set([...a].filter(x => !b.has(x)));
let b_minus_a = new Set([...b].filter(x => !a.has(x)));
let a_intersect_b = new Set([...a].filter(x => b.has(x))); 

console.log([...a_minus_b]) // {1}
console.log([...b_minus_a]) // {5}
console.log([...a_intersect_b]) // {2,3,4}


1
เร็วกว่า indexOf มากสำหรับอาร์เรย์ขนาดใหญ่
Estus Flask

101
เหตุใดชุด JavaScript จึงไม่มีการรวมกัน / การตัดกัน / ความแตกต่างในตัวจึงเกินกว่าฉัน ...
SwiftsNamesake

6
ฉันเห็นด้วยอย่างยิ่ง; สิ่งเหล่านี้ควรเป็นพื้นฐานระดับล่างที่ใช้ในเครื่องยนต์ js นอกจากนี้ฉันยัง ...
Rafael

4
@SwiftsNamesake มีข้อเสนอสำหรับการตั้งค่าในตัววิธีการที่จะหวังว่าจะพูดคุยเกี่ยวกับใน Janurary 2018 เป็นgithub.com/tc39/agendas/blob/master/2018/01.md
John

15

คุณสามารถใช้วัตถุเป็นแผนที่เพื่อหลีกเลี่ยงการสแกนเชิงเส้นBสำหรับแต่ละองค์ประกอบAในคำตอบของ user187291 :

function setMinus(A, B) {
    var map = {}, C = [];

    for(var i = B.length; i--; )
        map[B[i].toSource()] = null; // any other value would do

    for(var i = A.length; i--; ) {
        if(!map.hasOwnProperty(A[i].toSource()))
            C.push(A[i]);
    }

    return C;
}

ไม่ใช่มาตรฐานtoSource()วิธีการที่จะใช้ในการได้รับชื่อคุณสมบัติไม่ซ้ำกัน; หากองค์ประกอบทั้งหมดมีการแสดงสตริงที่ไม่ซ้ำกันอยู่แล้ว (เช่นเดียวกับในกรณีของตัวเลข) คุณสามารถเร่งความเร็วโค้ดได้โดยวางtoSource()คำเรียกร้อง


9

สั้นที่สุดโดยใช้ jQuery คือ:

var A = [1, 2, 3, 4];
var B = [1, 3, 4, 7];

var diff = $(A).not(B);

console.log(diff.toArray());
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>


ส่งคืนวัตถุของความแตกต่าง
Drew Baker

2
jQuery notไม่ทำงานกับวัตถุทั่วไปที่ 3.0.0-rc1 อีกต่อไป ดูgithub.com/jquery/jquery/issues/3147
Marc-André Lafortune

2
มันไม่ได้เป็นความคิดที่ดีที่จะเพิ่มการพึ่งพาห้องสมุดบุคคล ~ 70k ที่ 3 เพียงแค่การทำเช่นนี้ตั้งแต่สิ่งเดียวกันสามารถทำได้ในเพียงไม่กี่บรรทัดของรหัสที่ปรากฏในคำตอบอื่น ๆ ที่นี่ อย่างไรก็ตามหากคุณใช้ jQuery ในโปรเจ็กต์ของคุณอยู่แล้วสิ่งนี้ก็ใช้ได้ดี
CBarr

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

นี่เป็นเพียงการส่งคืนจำนวน (2 ในกรณีนี้) ขององค์ประกอบของ A ซึ่งไม่ได้อยู่ใน B การแปลง 2 เป็นอาร์เรย์นั้นไม่มีจุดหมาย ...
Alex

6

ฉันจะแฮชอาร์เรย์ B จากนั้นเก็บค่าจากอาร์เรย์ A ที่ไม่มีอยู่ใน B:

function getHash(array){
  // Hash an array into a set of properties
  //
  // params:
  //   array - (array) (!nil) the array to hash
  //
  // return: (object)
  //   hash object with one property set to true for each value in the array

  var hash = {};
  for (var i=0; i<array.length; i++){
    hash[ array[i] ] = true;
  }
  return hash;
}

function getDifference(a, b){
  // compute the difference a\b
  //
  // params:
  //   a - (array) (!nil) first array as a set of values (no duplicates)
  //   b - (array) (!nil) second array as a set of values (no duplicates)
  //
  // return: (array)
  //   the set of values (no duplicates) in array a and not in b, 
  //   listed in the same order as in array a.

  var hash = getHash(b);
  var diff = [];
  for (var i=0; i<a.length; i++){
    var value = a[i];
    if ( !hash[value]){
      diff.push(value);
    }
  }
  return diff;
}

นั่นคืออัลกอริทึมเดียวกับที่ฉันโพสต์เมื่อครึ่งชั่วโมงที่แล้ว
Christoph

@ คริสตอฟ: คุณพูดถูก ... ฉันไม่สังเกตเห็นว่า ฉันพบว่าการใช้งานของฉันเข้าใจง่ายขึ้น :)
Eric Bréchemier

ฉันคิดว่ามันเป็นการดีกว่าที่จะคำนวณความแตกต่างภายนอก getDifference ดังนั้นจึงสามารถนำมาใช้ซ้ำได้หลายครั้ง อาจเป็นทางเลือกเช่นนั้น: getDifference(a, b, hashOfB)หากไม่ผ่านจะถูกคำนวณมิฉะนั้นจะถูกนำมาใช้ใหม่ตามที่เป็นอยู่
Christophe Roussy

4

เมื่อรวมแนวคิดจากคริสตอฟและสมมติว่ามีวิธีการทำซ้ำสองสามวิธีที่ไม่ได้มาตรฐานบนอาร์เรย์และอ็อบเจ็กต์ / แฮช ( eachและเพื่อน) เราสามารถกำหนดความแตกต่างการรวมกันและจุดตัดในเวลาเชิงเส้นได้ทั้งหมดประมาณ 20 บรรทัด:

var setOPs = {
  minusAB : function (a, b) {
    var h = {};
    b.each(function (v) { h[v] = true; });
    return a.filter(function (v) { return !h.hasOwnProperty(v); });
  },
  unionAB : function (a, b) {
    var h = {}, f = function (v) { h[v] = true; };
    a.each(f);
    b.each(f);
    return myUtils.keys(h);
  },
  intersectAB : function (a, b) {
    var h = {};
    a.each(function (v) { h[v] = 1; });
    b.each(function (v) { h[v] = (h[v] || 0) + 1; });
    var fnSel = function (v, count) { return count > 1; };
    var fnVal = function (v, c) { return v; };
    return myUtils.select(h, fnSel, fnVal);
  }
};

สิ่งนี้ถือว่าeachและfilterกำหนดไว้สำหรับอาร์เรย์และเรามีวิธียูทิลิตี้สองวิธี:

  • myUtils.keys(hash): ส่งคืนอาร์เรย์ด้วยคีย์ของแฮช

  • myUtils.select(hash, fnSelector, fnEvaluator): ส่งคืนอาร์เรย์พร้อมผลลัพธ์ของการเรียกfnEvaluator คู่คีย์ / ค่าซึ่ง fnSelectorส่งกลับค่าจริง

select()เป็นแรงบันดาลใจอย่างอิสระโดยธรรมดาชัดและเป็นเพียงfilter()และmap()รีดเป็นหนึ่ง (จะดีกว่าถ้ากำหนดไว้Object.prototypeแต่การทำเช่นนั้นทำให้เกิดความเสียหายกับ jQuery ดังนั้นฉันจึงตัดสินใจใช้วิธียูทิลิตี้แบบคงที่)

ประสิทธิภาพ: การทดสอบด้วย

var a = [], b = [];
for (var i = 100000; i--; ) {
  if (i % 2 !== 0) a.push(i);
  if (i % 3 !== 0) b.push(i);
}

ให้สองชุดที่มีองค์ประกอบ 50,000 และ 66,666 ด้วยค่าเหล่านี้ AB จะใช้เวลาประมาณ 75ms ในขณะที่การรวมกันและจุดตัดจะอยู่ที่ประมาณ 150 มิลลิวินาที (Mac Safari 4.0 โดยใช้ Javascript Date เพื่อกำหนดเวลา)

ฉันคิดว่านั่นเป็นผลตอบแทนที่ดีสำหรับโค้ด 20 บรรทัด


1
คุณยังควรตรวจสอบhasOwnProperty()แม้ว่าองค์ประกอบที่มีตัวเลขฉะนั้นบางอย่างเช่นObject.prototype[42] = true;วิธีการ42ไม่สามารถเกิดขึ้นได้ในชุดผลลัพธ์
คริสโต

จริงอยู่ที่ว่ามันจะเป็นไปได้ที่จะตั้งค่า 42 ในลักษณะนั้น แต่มีกรณีการใช้งานกึ่งจริงที่ใครจะทำเช่นนั้นจริงหรือ? แต่สำหรับสตริงทั่วไปฉันเข้าใจ - มันอาจขัดแย้งกับตัวแปรหรือฟังก์ชัน Object.prototype ได้อย่างง่ายดาย
jg-faustus


3

ฟังก์ชั่นง่ายๆบางอย่างยืมมาจากคำตอบของ @ milan:

const setDifference = (a, b) => new Set([...a].filter(x => !b.has(x)));
const setIntersection = (a, b) => new Set([...a].filter(x => b.has(x)));
const setUnion = (a, b) => new Set([...a, ...b]);

การใช้งาน:

const a = new Set([1, 2]);
const b = new Set([2, 3]);

setDifference(a, b); // Set { 1 }
setIntersection(a, b); // Set { 2 }
setUnion(a, b); // Set { 1, 2, 3 }

2

สำหรับวิธีอดอาหารนี่ไม่ได้สวยหรูนัก แต่ฉันได้ทำการทดสอบเพื่อความแน่ใจ การโหลดอาร์เรย์หนึ่งอาร์เรย์เนื่องจากอ็อบเจ็กต์นั้นเร็วกว่ามากในการประมวลผลในปริมาณมาก:

var t, a, b, c, objA;

    // Fill some arrays to compare
a = Array(30000).fill(0).map(function(v,i) {
    return i.toFixed();
});
b = Array(20000).fill(0).map(function(v,i) {
    return (i*2).toFixed();
});

    // Simple indexOf inside filter
t = Date.now();
c = b.filter(function(v) { return a.indexOf(v) < 0; });
console.log('completed indexOf in %j ms with result %j length', Date.now() - t, c.length);

    // Load `a` as Object `A` first to avoid indexOf in filter
t = Date.now();
objA = {};
a.forEach(function(v) { objA[v] = true; });
c = b.filter(function(v) { return !objA[v]; });
console.log('completed Object in %j ms with result %j length', Date.now() - t, c.length);

ผล:

completed indexOf in 1219 ms with result 5000 length
completed Object in 8 ms with result 5000 length

แต่นี้ทำงานร่วมกับสตริงเท่านั้น หากคุณวางแผนที่จะเปรียบเทียบชุดหมายเลขที่คุณจะต้องการ map ผลกับการparseFloat


1
ไม่ควรเป็น c = b.filter(function(v) { return !A[v]; });ในฟังก์ชันที่สองหรือไม่?
fabianmoronzirfas

คุณถูก. ดูเหมือนว่าจะเร็วกว่าสำหรับฉัน
SmujMaiku

1

มันใช้งานได้ แต่ฉันคิดว่าอีกอันหนึ่งสั้นกว่ามากและก็ดูสง่างามด้วย

A = [1, 'a', 'b', 12];
B = ['a', 3, 4, 'b'];

diff_set = {
    ar : {},
    diff : Array(),
    remove_set : function(a) { ar = a; return this; },
    remove: function (el) {
        if(ar.indexOf(el)<0) this.diff.push(el);
    }
}

A.forEach(diff_set.remove_set(B).remove,diff_set);
C = diff_set.diff;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.