รหัสที่ง่ายที่สุดสำหรับการแยกอาร์เรย์ใน javascript


607

รหัสที่ง่ายที่สุดและไม่มีไลบรารีสำหรับการใช้งานการแยกอาร์เรย์ใน javascript คืออะไร ฉันต้องการเขียน

intersection([1,2,3], [2,3,4,5])

และรับ

[2, 3]

16
คุณต้องการง่ายหรือเร็ว
slaks

11
ลำดับความสำคัญเป็นเรื่องง่าย แต่ก็ไม่สามารถที่จะให้สมองที่ตายว่ามันจะเป็นหมูประสิทธิภาพ :)
ปีเตอร์

4
ฉันได้ทำหน้าทดสอบ JsFiddle Banchmark สำหรับวิธีการทั้งหมดที่นี่รวมถึงฟังก์ชั่นการแยก _underscore (สูงกว่าดีกว่า) ! ป้อนคำอธิบายภาพที่นี่จนถึงขณะนี้ intersect_safe ให้ผลลัพธ์ที่ดีที่สุด คุณเน้นสิ่งที่แย่ที่สุด
neoswf

การเพิ่ม a breakเพื่อSimple js loopsเพิ่ม ops / วินาทีถึง ~ 10M
Richard

19
ในกรณีที่คุณพลาด : คำตอบที่ง่ายที่สุดไม่ใช่คำตอบที่ยอมรับ แต่เป็นคำตอบที่อยู่ด้านล่าง: stackoverflow.com/questions/1885557/…
redben

คำตอบ:


1078

ใช้การรวมกันของArray.prototype.filterและArray.prototype.indexOf:

array1.filter(value => -1 !== array2.indexOf(value))

หรือตามที่vrugtehagelแนะนำไว้ในความคิดเห็นคุณสามารถใช้Array.prototype.includesรหัสล่าสุดได้ง่ายขึ้น:

array1.filter(value => array2.includes(value))

สำหรับเบราว์เซอร์รุ่นเก่า:

array1.filter(function(n) {
    return array2.indexOf(n) !== -1;
});

9
ซึ่งคุณสามารถแก้ไขได้โดยการเพิ่มเวอร์ชันไลบรารีบนต้นแบบของอาร์เรย์
อานนท์

12
ใช่ แต่มันก็คุ้มค่าที่จะพูดถึง
Tim Down

18
คำตอบที่ดีที่สุดที่นี่ทั้งเพื่อความเรียบง่ายและทำงานกับที่ไม่ใช่ตัวเลข
Muhd

41
intersection([1,2,1,1,3], [1])[1, 1, 1]ผลตอบแทน มันควรจะกลับมา[1]ไหม?
edjroot

21
แทนที่จะarray2.indexOf(n) != -1เป็นหนึ่งสามารถเขียนarray2.includes(n)รหัสได้ง่ายขึ้น
vrugtehagel

157

การทำลายล้างดูเหมือนง่ายที่สุดโดยเฉพาะอย่างยิ่งถ้าเราสามารถสมมติว่าอินพุตถูกเรียงลำดับ:

/* destructively finds the intersection of 
 * two arrays in a simple fashion.  
 *
 * PARAMS
 *  a - first array, must already be sorted
 *  b - second array, must already be sorted
 *
 * NOTES
 *  State of input arrays is undefined when
 *  the function returns.  They should be 
 *  (prolly) be dumped.
 *
 *  Should have O(n) operations, where n is 
 *    n = MIN(a.length, b.length)
 */
function intersection_destructive(a, b)
{
  var result = [];
  while( a.length > 0 && b.length > 0 )
  {  
     if      (a[0] < b[0] ){ a.shift(); }
     else if (a[0] > b[0] ){ b.shift(); }
     else /* they're equal */
     {
       result.push(a.shift());
       b.shift();
     }
  }

  return result;
}

การไม่ทำลายจะต้องมีผมที่ซับซ้อนมากขึ้นเนื่องจากเราต้องติดตามดัชนี:

/* finds the intersection of 
 * two arrays in a simple fashion.  
 *
 * PARAMS
 *  a - first array, must already be sorted
 *  b - second array, must already be sorted
 *
 * NOTES
 *
 *  Should have O(n) operations, where n is 
 *    n = MIN(a.length(), b.length())
 */
function intersect_safe(a, b)
{
  var ai=0, bi=0;
  var result = [];

  while( ai < a.length && bi < b.length )
  {
     if      (a[ai] < b[bi] ){ ai++; }
     else if (a[ai] > b[bi] ){ bi++; }
     else /* they're equal */
     {
       result.push(a[ai]);
       ai++;
       bi++;
     }
  }

  return result;
}

14
มีข้อผิดพลาดมากมายในintersect_safe: lengthเป็นคุณสมบัติในอาร์เรย์ไม่ใช่วิธีการ มีตัวแปร undelared เป็นในi result.push(a[i]);ในที่สุดสิ่งนี้ก็ไม่ได้ผลในกรณีทั่วไป: วัตถุสองชิ้นที่ไม่ได้ใหญ่กว่าวัตถุอื่นตามตัว>ดำเนินการไม่จำเป็นต้องเท่ากัน intersect_safe( [ {} ], [ {} ] )ตัวอย่างเช่นจะให้ (เมื่อข้อผิดพลาดที่กล่าวถึงก่อนหน้านี้ได้รับการแก้ไข) อาร์เรย์ที่มีองค์ประกอบหนึ่งซึ่งผิดอย่างชัดเจน
Tim Down

1
@Tim Down: แก้ไขข้อผิดพลาดทางไวยากรณ์ที่คุณชี้ให้เห็น ไม่ว่าจะถูกหรือไม่ถูกต้องในการพิจารณาสิ่งใด ๆ ที่ผู้สูงอายุหรือผู้ที่มีความเท่าเทียมน้อยกว่าจะขึ้นอยู่กับข้อกำหนด ฉันไม่สังเกตเห็นอะไรเลยในคำถามเดิมที่บอกว่าเนื้อหานั้นคาดว่าจะมีอาร์เรย์ ตอนนี้คุณมีสิทธิ์ที่จะพูดว่าอินพุตที่ไม่คาดคิดควรได้รับการจัดการ แต่ถ้า spec ที่เกิดขึ้นแล้วเพื่อบอกว่าอินพุตนั้นต้องเป็นอาร์เรย์ของตัวเลข
atk

1
@ atk: ฉันใช้เวลาของคุณเพราะเป็นตัวอย่างในคำถามใช้อาร์เรย์ที่มีตัวเลขเท่านั้น
ทิมลง

4
คุณยังสามารถใช้.slice(0)เพื่อสร้างโคลนของอาร์เรย์ในintersect_safeแทนที่จะติดตามดัชนี
johnluetke

1
@thesmart: ถูกต้องมีวิธีการทำมากกว่านี้แน่นอน รหัสด้านบนมีจุดประสงค์ที่จะง่ายไม่เร็ว :)
atk

58

หากสภาพแวดล้อมของคุณรองรับECMAScript 6 Setวิธีหนึ่งที่ง่ายและมีประสิทธิภาพตามที่คาดคะเน (ดูลิงค์ข้อมูลจำเพาะ):

function intersect(a, b) {
  var setA = new Set(a);
  var setB = new Set(b);
  var intersection = new Set([...setA].filter(x => setB.has(x)));
  return Array.from(intersection);
}

สั้นลง แต่อ่านได้น้อยลง (เช่นกันโดยไม่ต้องสร้างทางแยกเพิ่มเติมSet):

function intersect(a, b) {
      return [...new Set(a)].filter(x => new Set(b).has(x));
}

หลีกเลี่ยงการใหม่Setจากbทุกครั้ง:

function intersect(a, b) {
      var setB = new Set(b);
      return [...new Set(a)].filter(x => setB.has(x));
}

หมายเหตุว่าเมื่อใช้ชุดคุณจะได้รับค่าที่แตกต่างกันดังนั้นการประเมินnew Set[1,2,3,3].size3


1
[...setA]ไวยากรณ์นี้คืออะไร มีการใช้งานจาวาสคริปต์แบบพิเศษไหม?
jxramos

1
@jxramos ที่เป็นไวยากรณ์การแพร่กระจายและในกรณีนี้มันถูกใช้เพื่อสร้างอาร์เรย์จากองค์ประกอบในชุด "Array.from (setA)" จะใช้งานได้ในกรณีนี้ แต่เนื่องจากคำถามที่ถามว่า "ง่ายที่สุด" ฉันพยายามทำให้การอ่านบนบรรทัดนั้นสะอาดยิ่งขึ้น ในเรื่องนี้ฉันคิดว่ารหัสนั้นง่ายกว่าดังนั้นฉันจะอัปเดตข้อมูลโค้ด
nbarbosa

@nbarbosa ฉันอยากรู้: ทำไมคุณ "โคลน" อาร์เรย์? #filter ไม่ทำลายอาร์เรย์เดิมใช่ไหม มันจะสร้างอาร์เรย์ใหม่หรือไม่?
bplittle

@bplittle ฉันเพิ่งสร้างอาร์เรย์จากชุดเพื่อลบรายการที่ซ้ำกันมิฉะนั้นการใช้อาร์เรย์โดยตรงจะส่งกลับรายการที่ซ้ำกัน ตัวอย่างเช่นถ้าฉันใช้อาร์เรย์โดยตรงตัดกัน ([1,2,2,4], [2,3]) จะให้ผล [2, 2]
nbarbosa

2
x => new Set(b).has(x)ฟังก์ชั่นลูกศรนั้นไม่เปลี่ยนbเป็นชุดทุกครั้งที่ถูกประมวลผลหรือ คุณควรบันทึกชุดนั้นในตัวแปร
Aran-Fey

39

ใช้ Underscore.jsหรือlodash.js

_.intersection( [0,345,324] , [1,0,324] )  // gives [0,324]

20
แย้มยิ้มขอ "ห้องสมุดฟรี"
LinuxDisciple

@LinuxDisciple ความผิดพลาดของฉันที่หายไปขอบคุณสำหรับบันทึก
Sai Ram

33
ไม่ว่ากรณีใด ๆ นี่เป็นรายชื่อ google อันดับต้น ๆ สำหรับการค้นหานี้ดังนั้นการมีคำตอบจากห้องสมุดจึงมีประโยชน์ ขอบคุณ
webnoob

ฉันก็ดีใจที่โพสต์นี้ ครั้งแรกที่ฉันรู้สึกว่าต้องการ underscore.js โดยปกติแล้วการแมป JavaScript และลดไปป์ไลน์จะทำงานได้อย่างสวยงาม แต่ไม่ใช่ในเวลานี้
Sridhar Sarnobat

ฉัน <3 ขีดเส้นใต้และฉัน <3 Jeremy Ashkenas (ผู้สร้าง), แต่ถึงอย่างนั้นฉันก็ขอแนะนำให้ลองเช็คที่ Lodash แทน โดยพื้นฐานแล้วมันเป็นรุ่นที่ยอดเยี่ยมของ Underscore (แต่เดิมเป็นทางแยก) ซึ่งมีข้อเสียเพียงอย่างเดียวคือการปรับปรุงที่ดีที่สุด (และทำให้ไม่สามารถอ่านได้) ซอร์สโค้ด คนขีดล่างคิดว่าจะกำจัดขีดเส้นใต้ทั้งหมด (และบอกให้คนใช้ Lodash) แต่คนที่สนใจการอ่านซอร์สโค้ดแย้งว่าเก็บมันไว้ (ผมอยู่ข้างนั้นจริง ๆ แล้ว แต่ฉันเปลี่ยนมาเป็น Lodash) @see github.com/jashkenas/underscore/issues/2182
machineghost

14

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

Array.prototype.intersect = function(...a) {
  return [this,...a].reduce((p,c) => p.filter(e => c.includes(e)));
}
var arrs = [[0,2,4,6,8],[4,5,6,7],[4,6]],
     arr = [0,1,2,3,4,5,6,7,8,9];

document.write("<pre>" + JSON.stringify(arr.intersect(...arrs)) + "</pre>");


รหัสนี้ดูดี แต่ฉันไม่เข้าใจมันอย่างสมบูรณ์ เป็นไปได้ไหมที่จะอธิบายได้โปรด
ปกติ

1
@novembersky มันรวบรวมอาร์เรย์เรื่องทั้งหมดในอาร์เรย์ที่ชอบ[[0,1,2,3,4,5,6,7,8,9],[0,2,4,6,8],[4,5,6,7],[4,6]]แล้วนำ.reduce()ไปใช้ [0,1,2,3,4,5,6,7,8,9].filter( e => [0,2,4,6,8].includes(e)การดำเนินการครั้งแรกจะดำเนินการและผลลัพธ์จะกลายเป็นใหม่pและcจะกลายเป็น[4,5,6,7]ในเทิร์นถัดไปและดำเนินการต่อไปเรื่อย ๆ จนกว่าจะไม่มีcเหลืออีก
Redu

1
นี่เป็นทางออกที่แพงถ้าคุณทำงานกับชุดข้อมูลขนาดใหญ่
Madbreaks

1
อย่าแก้ไขสิ่งที่prototypeไม่จำเป็น
fregante

14

// Return elements of array a that are also in b in linear time:
function intersect(a, b) {
  return a.filter(Set.prototype.has, new Set(b));
}

// Example:
console.log(intersect([1,2,3], [2,3,4,5]));

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

ทางเลือกและการเปรียบเทียบประสิทธิภาพ:

ดูตัวอย่างต่อไปนี้สำหรับการใช้งานทางเลือกและตรวจสอบhttps://jsperf.com/array-intersection-comparisonสำหรับการเปรียบเทียบประสิทธิภาพ

ผลลัพธ์ใน Firefox 53:

  • Ops / วินาทีในอาร์เรย์ขนาดใหญ่ (10,000 องค์ประกอบ):

    filter + has (this)               523 (this answer)
    for + has                         482
    for-loop + in                     279
    filter + in                       242
    for-loops                          24
    filter + includes                  14
    filter + indexOf                   10
  • Ops / วินาทีในอาร์เรย์ขนาดเล็ก (100 องค์ประกอบ):

    for-loop + in                 384,426
    filter + in                   192,066
    for-loops                     159,137
    filter + includes             104,068
    filter + indexOf               71,598
    filter + has (this)            43,531 (this answer)
    filter + has (arrow function)  35,588

2
intersect([1,2,2,3], [2,3,4,5])[2, 2, 3]ผลตอบแทน
SeregPie

1
@SeregPie แน่นอน ตามความคิดเห็น "องค์ประกอบกลับของอาร์เรย์ที่อยู่ใน b"
le_m

คำตอบที่มีคุณภาพอย่างไรก็ตามการใช้ชุดพื้นฐานจะเปลี่ยนแปลงผลลัพธ์ตั้งแต่คำถามของ op ถามเฉพาะเกี่ยวกับจุดตัดของอาเรย์ อายที่คำตอบนี้อาจให้ผลลัพธ์ที่ไม่คาดคิดเมื่อมีซ้ำกัน
Madbreaks

1
รัก แต่คุณได้เพิ่มฟังก์ชั่นที่ไม่จำเป็นด้วย "ตัวกรอง + รวม" a.filter(b.includes)ความพยายาม ควรรันเร็วกว่ามาก (เหมือนกับการอัพเกรดฟังก์ชั่นของคุณ)
SEoF

11

ลองใช้อาเรย์แบบเชื่อมโยงกันล่ะ?

function intersect(a, b) {
    var d1 = {};
    var d2 = {};
    var results = [];
    for (var i = 0; i < a.length; i++) {
        d1[a[i]] = true;
    }
    for (var j = 0; j < b.length; j++) {
        d2[b[j]] = true;
    }
    for (var k in d1) {
        if (d2[k]) 
            results.push(k);
    }
    return results;
}

แก้ไข:

// new version
function intersect(a, b) {
    var d = {};
    var results = [];
    for (var i = 0; i < b.length; i++) {
        d[b[i]] = true;
    }
    for (var j = 0; j < a.length; j++) {
        if (d[a[j]]) 
            results.push(a[j]);
    }
    return results;
}

1
Object.prototypeนี้จะยืนโอกาสถ้าอาร์เรย์ของคุณมีเพียงสตริงหรือตัวเลขและถ้าไม่มีสคริปต์ในหน้าเว็บของคุณได้สับสนกับ
Tim Down

2
ตัวอย่างของ OP ใช้ตัวเลขและหากสคริปต์มีระเบียบกับ Object.prototype ดังนั้นสคริปต์ควรเขียนใหม่หรือลบออก
Steven Huwig

คุณไม่ต้องการทั้ง (d1) และ (d2) สร้าง (d2) จากนั้นวนซ้ำ (a) แทนการวนซ้ำ (d1)
StanleyH

ควรจะd[b[i]] = true;แทนd[b[j]] = true;( iไม่ได้j) แต่การแก้ไขต้องใช้ 6 ตัวอักษร
Izhaki

@Izhaki ขอบคุณคง (เพิ่มความคิดเห็น // เพื่อให้ได้ข้อกำหนดการแก้ไขขั้นต่ำ)
Steven Huwig

8
  1. เรียงมัน
  2. ตรวจสอบทีละรายการจากดัชนี 0 สร้างอาร์เรย์ใหม่จากที่

บางอย่างเช่นนี้ไม่ผ่านการทดสอบอย่างดี

function intersection(x,y){
 x.sort();y.sort();
 var i=j=0;ret=[];
 while(i<x.length && j<y.length){
  if(x[i]<y[j])i++;
  else if(y[j]<x[i])j++;
  else {
   ret.push(x[i]);
   i++,j++;
  }
 }
 return ret;
}

alert(intersection([1,2,3], [2,3,4,5]));

PS: อัลกอริทึมที่ออกแบบมาสำหรับตัวเลขและสายอักขระปกติการตัดกันของวัตถุอาร์เรย์อาจไม่ทำงาน


3
การเรียงลำดับไม่จำเป็นต้องช่วยอาร์เรย์ของวัตถุใด ๆ
Tim Down

หากอาร์เรย์ไม่ได้ถูกจัดเรียงคุณจำเป็นต้องวนรอบ 1,000,000 ครั้งเมื่อคุณตัดกันอาร์เรย์ยาว 1,000 เส้นและอาร์เรย์ยาว 1000 เส้น
คุณ

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

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

8

ประสิทธิภาพของการนำไปใช้ของ @ atk สำหรับอาร์เรย์ที่เรียงลำดับของดั้งเดิมสามารถปรับปรุงได้โดยใช้. pop แทนที่จะเป็น. shift

function intersect(array1, array2) {
   var result = [];
   // Don't destroy the original arrays
   var a = array1.slice(0);
   var b = array2.slice(0);
   var aLast = a.length - 1;
   var bLast = b.length - 1;
   while (aLast >= 0 && bLast >= 0) {
      if (a[aLast] > b[bLast] ) {
         a.pop();
         aLast--;
      } else if (a[aLast] < b[bLast] ){
         b.pop();
         bLast--;
      } else /* they're equal */ {
         result.push(a.pop());
         b.pop();
         aLast--;
         bLast--;
      }
   }
   return result;
}

ฉันสร้างมาตรฐานการใช้ jsPerf: http://bit.ly/P9FrZK มันเร็วกว่าการใช้. pop ประมาณสามเท่า


1
เช่นเดียวกับข้อความด้านข้างสำหรับผู้อื่น - สิ่งนี้จะใช้ได้กับตัวเลขเท่านั้นไม่ใช่สตริง
Izhaki

โปรดทราบว่าหากคุณแทนที่a[aLast] > b[bLast]ด้วยa[aLast].localeCompare(b[bLast]) > 0(และเหมือนกันกับelse ifด้านล่าง) สิ่งนี้จะทำงานกับสตริง
แอนดรู

1
ความแตกต่างความเร็วขึ้นอยู่กับขนาดของอาร์เรย์เพราะ.popเป็น O (1) และ.shift()เป็น O (n)
Esailija

8

ใช้jQuery :

var a = [1,2,3];
var b = [2,3,4,5];
var c = $(b).not($(b).not(a));
alert(c);

8
สิ่งนี้สามารถเขียนได้c = $(b).filter(a);แต่ฉันจะไม่แนะนำให้ใช้ jQuery สำหรับการจัดการอาเรย์แบบนี้เนื่องจากเอกสารระบุว่าใช้งานได้กับองค์ประกอบเท่านั้น
Stryner

1
นี่ไม่ได้ตอบคำถามของ op: "อะไรคือรหัสที่ง่ายที่สุดและไม่มีไลบรารีฟรี ... "
Madbreaks

7

สำหรับอาร์เรย์ที่มีสตริงหรือตัวเลขเท่านั้นคุณสามารถทำอะไรบางอย่างกับการเรียงลำดับตามคำตอบอื่น ๆ สำหรับกรณีทั่วไปของอาร์เรย์ของวัตถุใด ๆ ฉันไม่คิดว่าคุณสามารถหลีกเลี่ยงได้ในระยะยาว ต่อไปนี้จะให้จุดตัดของอาร์เรย์ใด ๆ ที่ระบุเป็นพารามิเตอร์เพื่อarrayIntersection:

var arrayContains = Array.prototype.indexOf ?
    function(arr, val) {
        return arr.indexOf(val) > -1;
    } :
    function(arr, val) {
        var i = arr.length;
        while (i--) {
            if (arr[i] === val) {
                return true;
            }
        }
        return false;
    };

function arrayIntersection() {
    var val, arrayCount, firstArray, i, j, intersection = [], missing;
    var arrays = Array.prototype.slice.call(arguments); // Convert arguments into a real array

    // Search for common values
    firstArray = arrays.pop();
    if (firstArray) {
        j = firstArray.length;
        arrayCount = arrays.length;
        while (j--) {
            val = firstArray[j];
            missing = false;

            // Check val is present in each remaining array 
            i = arrayCount;
            while (!missing && i--) {
                if ( !arrayContains(arrays[i], val) ) {
                    missing = true;
                }
            }
            if (!missing) {
                intersection.push(val);
            }
        }
    }
    return intersection;
}

arrayIntersection( [1, 2, 3, "a"], [1, "a", 2], ["a", 1] ); // Gives [1, "a"]; 

ใช้ได้เฉพาะในกรณีที่เอกลักษณ์ของวัตถุเป็นรูปแบบของความเท่าเทียมกันเท่านั้น
Steven Huwig

ใช่ แต่ฉันคิดว่านั่นจะเป็นเรื่องธรรมดาสำหรับคนส่วนใหญ่ นอกจากนี้ยังเป็นเรื่องเล็กน้อยที่จะเสียบฟังก์ชั่นทางเลือกเพื่อทำการทดสอบความเท่าเทียมกัน
Tim Down

ฉันคิดว่าคุณกำลังสร้างตัวแปรทั่วโลกโดยไม่ได้ตั้งใจก่อนในตัวอย่างของคุณ
Jason Jackson

@ JasonJackson: ถูกต้องขอบคุณ เปลี่ยนใจฉันอย่างชัดเจนว่าจะเรียกตัวแปรfirstArrหรือfirstArrayไม่และไม่ได้อัปเดตข้อมูลอ้างอิงทั้งหมด แก้ไขแล้ว.
ทิมดาวน์

7

มันค่อนข้างสั้นโดยใช้ ES2015 และเซต ยอมรับค่า Array like เช่น String และลบค่าที่ซ้ำกัน

let intersection = function(a, b) {
  a = new Set(a), b = new Set(b);
  return [...a].filter(v => b.has(v));
};

console.log(intersection([1,2,1,2,3], [2,3,5,4,5,3]));

console.log(intersection('ccaabbab', 'addb').join(''));


แปลงจาก Set เป็น array ด้วย[... a]จะลบรายการที่ซ้ำกันออกเป็นไอเดียที่ดีขอบคุณ
V-SHY

1
โซลูชันนี้มีให้สองครั้งก่อนที่คุณจะ
Madbreaks

7

คุณสามารถใช้Setเป็นthisArgของArray#filterและใช้Set#hasเป็นโทรกลับ

function intersection(a, b) {
    return a.filter(Set.prototype.has, new Set(b));
}

console.log(intersection([1, 2, 3], [2, 3, 4, 5]));


ฉันไม่รู้ว่าทำไมสิ่งนี้จึงไม่ได้รับการโหวตมากขึ้น มันชัดเจนคำตอบที่ดีที่สุด
Paul Rooney

5

การปรับแต่งเล็ก ๆ ให้เล็กที่สุดที่นี่ ( ตัวกรอง / indexOf ) คือการสร้างดัชนีของค่าในหนึ่งในอาร์เรย์โดยใช้วัตถุ JavaScript จะลดลงจาก O (N * M) เป็นเวลาเชิงเส้น "คงที่" source1 source2

function intersect(a, b) {
  var aa = {};
  a.forEach(function(v) { aa[v]=1; });
  return b.filter(function(v) { return v in aa; });
}

นี่ไม่ใช่วิธีแก้ปัญหาที่ง่ายที่สุด (มันเป็นรหัสมากกว่าตัวกรอง + indexOf ) และไม่ใช่วิธีที่เร็วที่สุด (อาจช้ากว่าโดยปัจจัยคงที่มากกว่าintersect_safe () ) แต่ดูเหมือนว่าจะมีความสมดุลที่ดี มันอยู่ในด้านที่ง่ายมากในขณะที่ให้ประสิทธิภาพที่ดีและไม่จำเป็นต้องมีอินพุตที่จัดเรียงไว้ล่วงหน้า


5

อีกวิธีการจัดทำดัชนีสามารถประมวลผลจำนวนอาร์เรย์ในครั้งเดียว:

// Calculate intersection of multiple array or object values.
function intersect (arrList) {
    var arrLength = Object.keys(arrList).length;
        // (Also accepts regular objects as input)
    var index = {};
    for (var i in arrList) {
        for (var j in arrList[i]) {
            var v = arrList[i][j];
            if (index[v] === undefined) index[v] = 0;
            index[v]++;
        };
    };
    var retv = [];
    for (var i in index) {
        if (index[i] == arrLength) retv.push(i);
    };
    return retv;
};

มันทำงานได้เฉพาะกับค่าที่สามารถประเมินเป็นสตริงและคุณควรผ่านพวกเขาเป็นอาร์เรย์เช่น:

intersect ([arr1, arr2, arr3...]);

... แต่มันยอมรับวัตถุอย่างโปร่งใสว่าเป็นพารามิเตอร์หรือเป็นองค์ประกอบใด ๆ ที่จะถูกตัดกัน ตัวอย่าง:

intersect ({foo: [1, 2, 3, 4], bar: {a: 2, j:4}}); // [2, 4]
intersect ([{x: "hello", y: "world"}, ["hello", "user"]]); // ["hello"]

แก้ไข:ฉันเพิ่งสังเกตเห็นว่านี่เป็นวิธีที่บั๊กเล็กน้อย

นั่นคือ: ฉันเขียนโค้ดโดยคิดว่าอาร์เรย์อินพุตไม่สามารถมีการทำซ้ำได้เอง (ตามตัวอย่างที่ให้มา)

แต่ถ้าอาร์เรย์อินพุทเกิดขึ้นจะมีการทำซ้ำซึ่งจะให้ผลลัพธ์ที่ผิด ตัวอย่าง (ใช้การใช้งานด้านล่าง):

intersect ([[1, 3, 4, 6, 3], [1, 8, 99]]);
// Expected: [ '1' ]
// Actual: [ '1', '3' ]

โชคดีที่เรื่องนี้แก้ไขได้ง่ายเพียงเพิ่มการทำดัชนีระดับที่สอง นั่นคือ:

เปลี่ยนแปลง:

        if (index[v] === undefined) index[v] = 0;
        index[v]++;

โดย:

        if (index[v] === undefined) index[v] = {};
        index[v][i] = true; // Mark as present in i input.

...และ:

         if (index[i] == arrLength) retv.push(i);

โดย:

         if (Object.keys(index[i]).length == arrLength) retv.push(i);

ตัวอย่างที่สมบูรณ์:

// Calculate intersection of multiple array or object values.
function intersect (arrList) {
    var arrLength = Object.keys(arrList).length;
        // (Also accepts regular objects as input)
    var index = {};
    for (var i in arrList) {
        for (var j in arrList[i]) {
            var v = arrList[i][j];
            if (index[v] === undefined) index[v] = {};
            index[v][i] = true; // Mark as present in i input.
        };
    };
    var retv = [];
    for (var i in index) {
        if (Object.keys(index[i]).length == arrLength) retv.push(i);
    };
    return retv;
};

intersect ([[1, 3, 4, 6, 3], [1, 8, 99]]); // [ '1' ]

2
นี่เป็นคำตอบที่ดีที่สุดเมื่อใช้การดัดแปลงเพียงเล็กน้อย หลังจากvar v = เพิ่มบรรทัดif (typeof v == 'function') continue;แล้วมันจะข้ามการเพิ่มฟังก์ชั่นไปยังผลลัพธ์ ขอบคุณ!
Zsolti

ขอบคุณ @Zsolti ฉันไม่ได้เพิ่มข้อเสนอแนะของคุณเพราะมีฟังก์ชั่น (และวิธีที่เราต้องการจัดการ) อยู่นอกขอบเขตของคำถามเดิม แต่ดูการแก้ไขของฉัน:หากคุณสามารถทำซ้ำในอาร์เรย์อินพุตของคุณแล้วการใช้งานเดิมเป็นรถ ฉันแก้ไขมันในการแก้ไขของฉัน ... ในทางกลับกันถ้าคุณรู้ว่าไม่มีการทำซ้ำใด ๆ การใช้งานดั้งเดิมจะถูกกว่าเล็กน้อย
bitifet

... เกี่ยวกับฟังก์ชั่นพวกเขายังสามารถตัดกันได้: หากเราตรวจพบพวกเขาเช่น @Zsolti พูดว่า (ด้วยif (typeof v == 'function')แล้วเราสามารถใช้ stringification ( v.toString()) เป็นกุญแจสำหรับดัชนีได้ แต่เราต้องทำอะไรบางอย่างเพื่อรักษามันไว้ วิธีที่ง่ายที่สุดที่จะทำคือเพียงแค่กำหนดฟังก์ชั่นเดิมเป็นค่าแทนของมูลค่าที่แท้จริงที่เรียบง่ายแบบบูล แต่ในกรณีที่ deindexaton ล่าสุดควรจะยังมีการเปลี่ยนแปลงในการตรวจสอบสภาพนี้และเรียกคืนมูลค่าที่เหมาะสม (ฟังก์ชั่น)..
bitifet

วิธีนี้เร็วแค่ไหนกับ 30 อาร์เรย์ที่มี 100 องค์ประกอบ .. การใช้งาน CPU เป็นอย่างไร?
aidonsnous

5

คุณสามารถใช้ (สำหรับเบราว์เซอร์ทั้งหมดยกเว้น IE):

const intersection = array1.filter(element => array2.includes(element));

หรือสำหรับ IE:

const intersection = array1.filter(element => array2.indexOf(element) !== -1);

จะดีถ้าคุณสามารถเปลี่ยนให้เป็นฟังก์ชั่น
avalanche1

@ avalanche1 const intersection = (a1, a2) => a1.filter (e => a2.includes (e));
jota3

4
function intersection(A,B){
var result = new Array();
for (i=0; i<A.length; i++) {
    for (j=0; j<B.length; j++) {
        if (A[i] == B[j] && $.inArray(A[i],result) == -1) {
            result.push(A[i]);
        }
    }
}
return result;
}

4

ด้วยข้อ จำกัด บางประการเกี่ยวกับข้อมูลของคุณคุณสามารถทำได้ในเวลาเชิงเส้น !

สำหรับจำนวนเต็มบวก : ใช้การจับคู่อาร์เรย์กับค่าบูลีน "เห็น / ไม่เห็น"

function intersectIntegers(array1,array2) { 
   var seen=[],
       result=[];
   for (var i = 0; i < array1.length; i++) {
     seen[array1[i]] = true;
   }
   for (var i = 0; i < array2.length; i++) {
     if ( seen[array2[i]])
        result.push(array2[i]);
   }
   return result;
}

มีเทคนิคที่คล้ายกันสำหรับวัตถุ : ใช้คีย์จำลองตั้งค่าเป็น "จริง" สำหรับแต่ละองค์ประกอบในอาร์เรย์ 1 จากนั้นมองหาคีย์นี้ในองค์ประกอบของอาร์เรย์ 2 ทำความสะอาดเมื่อคุณทำเสร็จแล้ว

function intersectObjects(array1,array2) { 
   var result=[];
   var key="tmpKey_intersect"
   for (var i = 0; i < array1.length; i++) {
     array1[i][key] = true;
   }
   for (var i = 0; i < array2.length; i++) {
     if (array2[i][key])
        result.push(array2[i]);
   }
   for (var i = 0; i < array1.length; i++) {
     delete array1[i][key];
   }
   return result;
}

แน่นอนคุณต้องแน่ใจว่ากุญแจไม่เคยปรากฏมาก่อนมิฉะนั้นคุณจะทำลายข้อมูลของคุณ ...


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

ทางออกที่น่าสนใจฉันชอบมัน โซลูชันอื่น ๆ ส่วนใหญ่คือ O (n ^ 2) แต่นี่คือ O (n) ฉันเพิ่มรหัสจำนวนเต็มเพื่อ ericP ของซอประสิทธิภาพการทำงานที่นี่jsfiddle.net/321juyLu/2 มันมาเป็นอันดับที่ 3 ฉัน
เปรียบเหมือน

3

ฉันจะสนับสนุนสิ่งที่ได้ผลดีที่สุดสำหรับฉัน:

if (!Array.prototype.intersect){
Array.prototype.intersect = function (arr1) {

    var r = [], o = {}, l = this.length, i, v;
    for (i = 0; i < l; i++) {
        o[this[i]] = true;
    }
    l = arr1.length;
    for (i = 0; i < l; i++) {
        v = arr1[i];
        if (v in o) {
            r.push(v);
        }
    }
    return r;
};
}


2

'use strict'

// Example 1
function intersection(a1, a2) {
    return a1.filter(x => a2.indexOf(x) > -1)
}

// Example 2 (prototype function)
Array.prototype.intersection = function(arr) {
    return this.filter(x => arr.indexOf(x) > -1)
} 

const a1 = [1, 2, 3]
const a2 = [2, 3, 4, 5]

console.log(intersection(a1, a2))
console.log(a1.intersection(a2))


2

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

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

ข้อ จำกัด เหล่านี้ช่วยเพิ่มความสามารถในการจัดองค์ประกอบและนำกลับมาใช้ใหม่ของฟังก์ชั่นที่เกี่ยวข้อง

// small, reusable auxiliary functions

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


// intersection

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


// mock data

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


// run it

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

โปรดทราบว่าSetมีการใช้งานประเภทเนทีฟซึ่งมีประสิทธิภาพการค้นหาที่ได้เปรียบ

หลีกเลี่ยงการซ้ำซ้อน

เห็นได้ชัดว่ารายการที่เกิดขึ้นซ้ำ ๆ จากครั้งแรกArrayจะถูกเก็บรักษาไว้ในขณะที่รายการที่สองArrayถูกทำซ้ำ นี่อาจเป็นหรืออาจไม่ใช่พฤติกรรมที่ต้องการ หากคุณต้องการผลลัพธ์ที่ไม่ซ้ำเพียงแค่ใช้dedupeกับอาร์กิวเมนต์แรก:

// auxiliary functions

const apply = f => x => f(x);
const comp = f => g => x => f(g(x));
const afrom = apply(Array.from);
const createSet = xs => new Set(xs);
const filter = f => xs => xs.filter(apply(f));


// intersection

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


// de-duplication

const dedupe = comp(afrom) (createSet);


// mock data

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


// unique result

console.log( intersect(dedupe(xs)) (ys) );

คำนวณค่าตัดกันของจำนวนArrays ใด ๆ

หากคุณต้องการที่จะคำนวณจุดตัดของจำนวนพลของArrayแค่เขียนด้วยintersect foldlนี่คือฟังก์ชั่นความสะดวกสบาย:

// auxiliary functions

const apply = f => x => f(x);
const uncurry = f => (x, y) => f(x) (y);
const createSet = xs => new Set(xs);
const filter = f => xs => xs.filter(apply(f));
const foldl = f => acc => xs => xs.reduce(uncurry(f), acc);


// intersection

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


// intersection of an arbitrarily number of Arrays

const intersectn = (head, ...tail) => foldl(intersect) (head) (tail);


// mock data

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


// run

console.log( intersectn(xs, ys, zs) );


ฟังก์ชั่นที่น่าประทับใจ: ต้องทำสองครั้งเพื่อยืนยันว่าไม่ใช่ Haskell nitpick เพียงอย่างเดียว(expr ? true : false)คือซ้ำซ้อน ใช้ในexprกรณีที่ไม่จำเป็นต้องใช้บูลีนจริงเพียงแค่ความจริง / เท็จ
jose_castro_arnaud

2

เพื่อความเรียบง่าย:

// Usage
const intersection = allLists
  .reduce(intersect, allValues)
  .reduce(removeDuplicates, []);


// Implementation
const intersect = (intersection, list) =>
  intersection.filter(item =>
    list.some(x => x === item));

const removeDuplicates = (uniques, item) =>
  uniques.includes(item) ? uniques : uniques.concat(item);


// Example Data
const somePeople = [bob, doug, jill];
const otherPeople = [sarah, bob, jill];
const morePeople = [jack, jill];

const allPeople = [...somePeople, ...otherPeople, ...morePeople];
const allGroups = [somePeople, otherPeople, morePeople];

// Example Usage
const intersection = allGroups
  .reduce(intersect, allPeople)
  .reduce(removeDuplicates, []);

intersection; // [jill]

ประโยชน์ที่ได้รับ:

  • สิ่งสกปรกง่าย
  • ศูนย์กลางข้อมูล
  • ใช้ได้กับจำนวนรายการโดยพลการ
  • ใช้ได้กับความยาวของรายการโดยพลการ
  • ใช้งานได้กับค่านิยมหลายประเภท
  • ทำงานเพื่อเรียงลำดับโดยพลการ
  • ยังคงรูปร่าง (คำสั่งของการปรากฏตัวครั้งแรกในอาร์เรย์ใด ๆ )
  • ออกเร็วที่สุดเท่าที่จะเป็นไปได้
  • หน่วยความจำปลอดภัยสั้น ๆ ของการดัดแปลงแก้ไขด้วยต้นแบบ Function / Array

ข้อเสีย:

  • การใช้งานหน่วยความจำที่สูงขึ้น
  • การใช้ CPU ที่สูงขึ้น
  • ต้องมีความเข้าใจในการลด
  • ต้องการความเข้าใจในการไหลของข้อมูล

คุณไม่ต้องการใช้สิ่งนี้สำหรับเอ็นจิ้น 3D หรือเคอร์เนล แต่ถ้าคุณมีปัญหาในการทำให้มันทำงานในแอพที่อ้างอิงเหตุการณ์การออกแบบของคุณมีปัญหามากขึ้น


2

.reduceเพื่อสร้างแผนที่และ.filterค้นหาจุดตัด deleteภายใน.filterช่วยให้เราปฏิบัติต่ออาร์เรย์ที่สองราวกับว่าเป็นชุดที่ไม่ซ้ำกัน

function intersection (a, b) {
  var seen = a.reduce(function (h, k) {
    h[k] = true;
    return h;
  }, {});

  return b.filter(function (k) {
    var exists = seen[k];
    delete seen[k];
    return exists;
  });
}

ฉันพบว่าวิธีนี้ค่อนข้างง่ายที่จะให้เหตุผล มันทำงานในเวลาคงที่


2

นี่อาจเป็นวิธีที่ง่ายที่สุดนอกเหนือจากlist1.filter (n => list2.includes (n))

var list1 = ['bread', 'ice cream', 'cereals', 'strawberry', 'chocolate']
var list2 = ['bread', 'cherry', 'ice cream', 'oats']

function check_common(list1, list2){
	
	list3 = []
	for (let i=0; i<list1.length; i++){
		
		for (let j=0; j<list2.length; j++){	
			if (list1[i] === list2[j]){
				list3.push(list1[i]);				
			}		
		}
		
	}
	return list3
	
}

check_common(list1, list2) // ["bread", "ice cream"]


นี่มีความซับซ้อนของเวลา O (nm) ... สามารถแก้ไขได้ใน O (n + m)
alchuang

2

หากคุณต้องการให้มันจัดการกับการตัดกันหลาย ๆ อาร์เรย์:

const intersect = (a, b, ...rest) => {
  if (rest.length === 0) return [...new Set(a)].filter(x => new Set(b).has(x));
  return intersect(a, intersect(b, ...rest));
};

console.log(intersect([1,2,3,4,5], [1,2], [1, 2, 3,4,5], [2, 10, 1])) // [1,2]


แต่วิธีแก้ปัญหานี้รวดเร็วแค่ไหนสำหรับ 30 อาร์เรย์ที่มีองค์ประกอบ 100 รายการ
aidonsnous

สิ่งนี้ไม่ได้ใช้อะไรนอกจากเป็นวิธีดั้งเดิมของจาวาสคริปต์และสำหรับวีเอ็มที่โค้ดจะทำงานนั้นมีอิสระที่จะปรับให้เหมาะสมที่สุดเท่าที่จะทำได้ ฉันค่อนข้างมั่นใจว่าไม่มีวิธีแก้ปัญหาที่เร็วกว่านี้เนื่องจากคุณกำลังใช้งานรุ่น V8 ที่ทันสมัยเมื่อเทียบกับอายุของความคิดเห็นนี้
Belfordz


2

ฉันได้เขียนฟังก์ชัน intesection ซึ่งสามารถตรวจจับการแยกอาร์เรย์ของวัตถุตามคุณสมบัติเฉพาะของวัตถุเหล่านั้นได้

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

if arr1 = [{id: 10}, {id: 20}]
and arr2 =  [{id: 20}, {id: 25}]

และเราต้องการจุดตัดตามidคุณสมบัติจากนั้นผลลัพธ์ควรเป็น:

[{id: 20}]

ดังนั้นฟังก์ชั่นสำหรับรหัสเดียวกัน (หมายเหตุ: รหัส ES6) คือ:

const intersect = (arr1, arr2, accessors = [v => v, v => v]) => {
    const [fn1, fn2] = accessors;
    const set = new Set(arr2.map(v => fn2(v)));
    return arr1.filter(value => set.has(fn1(value)));
};

และคุณสามารถเรียกใช้ฟังก์ชันเป็น:

intersect(arr1, arr2, [elem => elem.id, elem => elem.id])

หมายเหตุ: ฟังก์ชั่นนี้ค้นหาจุดตัดที่พิจารณาว่าอาร์เรย์แรกเป็นอาร์เรย์หลักและผลลัพธ์ของการแยกจะเป็นของอาร์เรย์หลัก


2

คิดว่าสิ่งนี้จะเร็วขึ้นด้วย O (array1 + array2) เวลาที่สมมติว่า map.has () คือ ~ O (1) โปรดแก้ไขให้ฉันถ้าผิด

const intersection = (a1, a2) => {
  let map = new Map();
  let result = []
  for (let i of a1) {
    if (!map.has(i)) map.set(i, true);
  }
  for (let i of a2) {
    if (map.has(i)) result.push(i)
  }
  return result;
}


1

นี่คือการดำเนินการunderscore.js :

_.intersection = function(array) {
  if (array == null) return [];
  var result = [];
  var argsLength = arguments.length;
  for (var i = 0, length = array.length; i < length; i++) {
    var item = array[i];
    if (_.contains(result, item)) continue;
    for (var j = 1; j < argsLength; j++) {
      if (!_.contains(arguments[j], item)) break;
    }
    if (j === argsLength) result.push(item);
  }
  return result;
};

แหล่งที่มา: http://underscorejs.org/docs/underscore.html#section-62


ไม่ใช่การอ้างอิงที่ไม่ดีหากมี undesrcore
Dimitrios Mistriotis
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.