เปรียบเทียบชุด ECMA6 เพื่อความเท่าเทียมกัน


105

คุณเปรียบเทียบชุดจาวาสคริปต์สองชุดได้อย่างไร? ฉันลองใช้==และ===แต่ทั้งสองกลับเป็นเท็จ

a = new Set([1,2,3]);
b = new Set([1,3,2]);
a == b; //=> false
a === b; //=> false

สองชุดนี้มีค่าเท่ากันเนื่องจากตามนิยามแล้วชุดไม่มีลำดับ (อย่างน้อยก็ไม่ใช่โดยปกติ) ฉันได้ดูเอกสารสำหรับ Set on MDNแล้วและไม่พบว่าไม่มีประโยชน์ ใครทราบวิธีการทำเช่นนี้


สองชุดเป็นวัตถุสองชิ้นที่แตกต่างกัน ===มีไว้เพื่อความเท่าเทียมกันของคุณค่าไม่ใช่ความเท่าเทียมกันของวัตถุ
elclanrs

3
ทำซ้ำและเปรียบเทียบค่าของสมาชิกแต่ละคนหากเหมือนกันทั้งหมดชุดจะ "เหมือนกัน"
dandavis

1
@dandavis ด้วยชุดสมาชิกคือค่านิยม

2
ชุดและแผนที่มีคำสั่งซึ่งเป็นลำดับการแทรก - ไม่ว่าด้วยเหตุผลใดก็ตาม: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
CodeManX

8
ที่แย่ที่สุดก็new Set([1,2,3]) != new Set([1,2,3])คือ สิ่งนี้ทำให้ Javascript Setไม่มีประโยชน์สำหรับชุดของชุดเนื่องจาก superset จะมีชุดย่อยที่ซ้ำกัน วิธีแก้ปัญหาเดียวที่นึกถึงคือการแปลงชุดย่อยทั้งหมดเป็นอาร์เรย์เรียงลำดับแต่ละอาร์เรย์แล้วเข้ารหัสแต่ละอาร์เรย์เป็นสตริง (เช่น JSON)
7vujy0f0hy

คำตอบ:


75

ลองสิ่งนี้:

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

alert(eqSet(a, b)); // true

function eqSet(as, bs) {
    if (as.size !== bs.size) return false;
    for (var a of as) if (!bs.has(a)) return false;
    return true;
}

แนวทางที่ใช้งานได้มากขึ้นคือ:

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

alert(eqSet(a, b)); // true

function eqSet(as, bs) {
    return as.size === bs.size && all(isIn(bs), as);
}

function all(pred, as) {
    for (var a of as) if (!pred(a)) return false;
    return true;
}

function isIn(as) {
    return function (a) {
        return as.has(a);
    };
}

allฟังก์ชันนี้ใช้งานได้กับวัตถุที่ทำซ้ำได้ทั้งหมด (เช่นSetและMap)

ถ้า Array.fromได้รับการสนับสนุนอย่างกว้างขวางมากขึ้นเราสามารถใช้allฟังก์ชันดังต่อไปนี้:

function all(pred, as) {
    return Array.from(as).every(pred);
}

หวังว่าจะช่วยได้


2
ฉันคิดว่าคุณควรเปลี่ยนชื่อhasเป็นisPartOfหรือisInหรือelem
Bergi

1
@DavidGiven ใช่ชุดใน JavaScript จะวนซ้ำตามลำดับการแทรก: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Aadit M Shah

51
ทุกๆวันฉันมั่นใจมากขึ้นว่า JavaScript เป็นภาษาที่มีหมัดมากที่สุดเท่าที่เคยมีมา ทุกคนต้องคิดค้นฟังก์ชันพื้นฐานของตัวเองเพื่อรับมือกับข้อ จำกัด และนี่คือ ES6 และเราอยู่ในปี 2017! เหตุใดจึงไม่สามารถเพิ่มฟังก์ชันที่ใช้บ่อยเช่นนี้ในคุณสมบัติของ Set object ได้!
Ghasan

2
@ GhasanAl-Sakkaf ฉันเห็นด้วยบางที TC39 อาจประกอบด้วยนักวิทยาศาสตร์ แต่ไม่มีนักปฏิบัติ ...
Marecky

1
@TobiasFeil Haskell ยิ่งดี
Aadit M Shah

64

คุณยังสามารถลอง:

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

isSetsEqual = (a, b) => a.size === b.size && [...a].every(value => b.has(value));

console.log(isSetsEqual(a,b)) 


เป็นทางออกที่ดีกว่าอย่างแน่นอนเนื่องจากเหมาะกับเงื่อนไข if
Hugodby

ฉันชอบวิธีการแก้ปัญหานี้เป็นสำนวนและอ่านได้มากแค่ไหน! ขอบคุณ @Max
daydreamer

31

lodashให้_.isEqual()ซึ่งทำการเปรียบเทียบอย่างลึกซึ้ง สิ่งนี้มีประโยชน์มากหากคุณไม่ต้องการเขียนเอง สำหรับ lodash 4 ให้_.isEqual()เปรียบเทียบชุดอย่างเหมาะสม

const _ = require("lodash");

let s1 = new Set([1,2,3]);
let s2 = new Set([1,2,3]);
let s3 = new Set([2,3,4]);

console.log(_.isEqual(s1, s2)); // true
console.log(_.isEqual(s1, s3)); // false

7

คำตอบอื่น ๆ จะใช้ได้ดี นี่เป็นอีกทางเลือกหนึ่ง

// Create function to check if an element is in a specified set.
function isIn(s)          { return elt => s.has(elt); }

// Check if one set contains another (all members of s2 are in s1).
function contains(s1, s2) { return [...s2] . every(isIn(s1)); }

// Set equality: a contains b, and b contains a
function eqSet(a, b)      { return contains(a, b) && contains(b, a); }

// Alternative, check size first
function eqSet(a, b)      { return a.size === b.size && contains(a, b); }

อย่างไรก็ตามโปรดทราบว่าสิ่งนี้ไม่ได้เป็นการเปรียบเทียบความเท่าเทียมกันอย่างลึกซึ้ง ดังนั้น

eqSet(Set([{ a: 1 }], Set([{ a: 1 }])

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

// Find a member in "s" deeply equal to some value
function findDeepEqual(s, v) { return [...s] . find(m => deepEqual(v, m)); }

// See if sets s1 and s1 are deeply equal. DESTROYS s2.
function eqSetDeep(s1, s2) {
  return [...s1] . every(a1 => {
    var m1 = findDeepEqual(s2, a1);
    if (m1) { s2.delete(m1); return true; }
  }) && !s2.size;
}

สิ่งนี้ทำอะไร: สำหรับสมาชิกแต่ละคนของ s1 ให้มองหาสมาชิกที่เท่าเทียมกันอย่างลึกซึ้งของ s2 หากพบให้ลบออกเพื่อไม่สามารถใช้งานได้อีก ทั้งสองชุดมีค่าเท่ากันอย่างมากหากพบองค์ประกอบทั้งหมดใน s1 ใน s2 และ s2 หมด ยังไม่ทดสอบ

คุณอาจพบว่ามีประโยชน์นี้: http://www.2ality.com/2015/01/es6-set-operations.html


6

ไม่มีโซลูชันใดที่นำ "ย้อนกลับ" ฟังก์ชันการทำงานที่คาดไว้มาใช้กับโครงสร้างข้อมูลเช่นชุดของชุด ในสถานะปัจจุบันชุด Javascript จะไม่มีประโยชน์สำหรับจุดประสงค์นี้เนื่องจาก superset จะมีชุดย่อยที่ซ้ำกันซึ่ง Javascript มองผิดว่าแตกต่างกัน ทางออกเดียวที่ฉันคิดได้คือการแปลงแต่ละส่วนย่อยเป็นArrayเรียงลำดับแล้วเข้ารหัสเป็นString (เช่น JSON)

สารละลาย

var toJsonSet = aset /* array or set */ => JSON.stringify([...new Set(aset)].sort()); 
var fromJsonSet = jset => new Set(JSON.parse(jset));

การใช้งานพื้นฐาน

var toJsonSet = aset /* array or set */ => JSON.stringify([...new Set(aset)].sort()); 
var fromJsonSet = jset => new Set(JSON.parse(jset));

var [s1,s2] = [new Set([1,2,3]), new Set([3,2,1])];
var [js1,js2] = [toJsonSet([1,2,3]), toJsonSet([3,2,1])]; // even better

var r = document.querySelectorAll("td:nth-child(2)");
r[0].innerHTML = (toJsonSet(s1) === toJsonSet(s2)); // true
r[1].innerHTML = (toJsonSet(s1) == toJsonSet(s2)); // true, too
r[2].innerHTML = (js1 === js2); // true
r[3].innerHTML = (js1 == js2); // true, too

// Make it normal Set:
console.log(fromJsonSet(js1), fromJsonSet(js2)); // type is Set
<style>td:nth-child(2) {color: red;}</style>

<table>
<tr><td>toJsonSet(s1) === toJsonSet(s2)</td><td>...</td></tr>
<tr><td>toJsonSet(s1) == toJsonSet(s2)</td><td>...</td></tr>
<tr><td>js1 === js2</td><td>...</td></tr>
<tr><td>js1 == js2</td><td>...</td></tr>
</table>

การทดสอบขั้นสูงสุด: ชุดของชุด

var toSet = arr => new Set(arr);
var toJsonSet = aset /* array or set */ => JSON.stringify([...new Set(aset)].sort()); 
var toJsonSet_WRONG = set => JSON.stringify([...set]); // no sorting!

var output = document.getElementsByTagName("code"); 
var superarray = [[1,2,3],[1,2,3],[3,2,1],[3,6,2],[4,5,6]];
var superset;

Experiment1:
    superset = toSet(superarray.map(toSet));
    output[0].innerHTML = superset.size; // incorrect: 5 unique subsets
Experiment2:
    superset = toSet([...superset].map(toJsonSet_WRONG));
    output[1].innerHTML = superset.size; // incorrect: 4 unique subsets
Experiment3:
    superset = toSet([...superset].map(toJsonSet));
    output[2].innerHTML = superset.size; // 3 unique subsets
Experiment4:
    superset = toSet(superarray.map(toJsonSet));
    output[3].innerHTML = superset.size; // 3 unique subsets
code {border: 1px solid #88f; background-color: #ddf; padding: 0 0.5em;}
<h3>Experiment 1</h3><p>Superset contains 3 unique subsets but Javascript sees <code>...</code>.<br>Let’s fix this... I’ll encode each subset as a string.</p>
<h3>Experiment 2</h3><p>Now Javascript sees <code>...</code> unique subsets.<br>Better! But still not perfect.<br>That’s because we didn’t sort each subset.<br>Let’s sort it out...</p>
<h3>Experiment 3</h3><p>Now Javascript sees <code>...</code> unique subsets. At long last!<br>Let’s try everything again from the beginning.</p>
<h3>Experiment 4</h3><p>Superset contains 3 unique subsets and Javascript sees <code>...</code>.<br><b>Bravo!</b></p>


1
ทางออกที่ดี! และถ้าคุณรู้ว่าคุณมีชุดของสตริงหรือตัวเลขมันก็จะกลายเป็น[...set1].sort().toString() === [...set2].sort().toString()

3

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

วิธีการต่อไปนี้รวมสองชุดเข้าด้วยกันและเปรียบเทียบขนาดอย่างโง่เขลา ถ้าเหมือนกันก็เหมือนกัน:

const a1 = [1,2,3];
const a2 = [1,3,2];
const set1 = new Set(a1);
const set2 = new Set(a2);

const compareSet = new Set([...a1, ...a2]);
const isSetEqual = compareSet.size === set2.size && compareSet.size === set1.size;
console.log(isSetEqual);

กลับหัว : ง่ายและสั้นมาก ไม่มีไลบรารีภายนอกเฉพาะ vanilla JS

ข้อเสีย : มันอาจจะช้ากว่าแค่วนซ้ำค่าและคุณต้องมีพื้นที่มากขึ้น


1

การเปรียบเทียบสองวัตถุด้วย ==, ===

เมื่อมีการใช้==หรือ===ผู้ประกอบการที่จะเปรียบเทียบวัตถุสองคุณก็จะได้รับการยกเว้นในกรณีที่ผู้อ้างอิงวัตถุวัตถุเดียวกันfalse ตัวอย่างเช่น:

var a = b = new Set([1,2,3]); // NOTE: b will become a global variable
a == b; // <-- true: a and b share the same object reference

มิฉะนั้น == จะเท่ากับเท็จแม้ว่าวัตถุจะมีค่าเดียวกัน:

var a = new Set([1,2,3]);
var b = new Set([1,2,3]);
a == b; // <-- false: a and b are not referencing the same object

คุณอาจต้องพิจารณาเปรียบเทียบด้วยตนเอง

ใน ECMAScript 6 คุณสามารถแปลงชุดเป็นอาร์เรย์ล่วงหน้าเพื่อให้คุณสามารถระบุความแตกต่างระหว่างชุดเหล่านี้ได้:

function setsEqual(a,b){
    if (a.size !== b.size)
        return false;
    let aa = Array.from(a); 
    let bb = Array.from(b);
    return aa.filter(function(i){return bb.indexOf(i)<0}).length==0;
}

หมายเหตุ: Array.fromเป็นหนึ่งในคุณสมบัติ ECMAScript 6 มาตรฐาน แต่ไม่ได้รับการสนับสนุนอย่างกว้างขวางในเบราว์เซอร์สมัยใหม่ ตรวจสอบตารางความเข้ากันได้ที่นี่: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#Browser_compatibility


1
สิ่งนี้จะไม่ระบุสมาชิกbที่ไม่ได้อยู่ในa?

1
@torazaburo แน่นอน. วิธีที่ดีที่สุดที่จะข้ามการตรวจสอบว่าสมาชิกbไม่ได้อยู่ในเพื่อตรวจสอบว่าa a.size === b.size
Aadit M Shah

1
ใส่a.size === b.sizeก่อนเพื่อลัดวงจรการเปรียบเทียบของแต่ละองค์ประกอบถ้าไม่จำเป็น?

2
หากขนาดแตกต่างกันโดยนิยามแล้วชุดไม่เท่ากันดังนั้นควรตรวจสอบเงื่อนไขนั้นก่อนดีกว่า

1
ปัญหาอื่น ๆ ที่นี่คือโดยธรรมชาติของชุดการhasทำงานในชุดได้รับการออกแบบมาให้มีประสิทธิภาพมากซึ่งแตกต่างจากการindexOfทำงานบนอาร์เรย์ return !b.has(i)ดังนั้นจึงจะทำให้รู้สึกถึงการเปลี่ยนฟังก์ชั่นตัวกรองของคุณจะเป็น นอกจากนี้ยังช่วยขจัดความจำเป็นในการแปลงbเป็นอาร์เรย์


1

จากคำตอบที่ยอมรับโดยสมมติว่ามีการสนับสนุนArray.fromนี่คือหนึ่งซับ:

function eqSet(a, b) {
    return a.size === b.size && Array.from(a).every(b.has.bind(b));
}

หรือสายการบินเดียวจริงโดยสมมติว่าฟังก์ชันลูกศรและตัวดำเนินการกระจาย: eqSet = (a,b) => a.size === b.size && [...a].every(b.has.bind(b))
John Hoffer

1

หากชุดข้อมูลมีเฉพาะชนิดข้อมูลดั้งเดิมหรือวัตถุภายในชุดมีความเท่าเทียมกันในการอ้างอิงจะมีวิธีที่ง่ายกว่า

const isEqualSets = (set1, set2) => (set1.size === set2.size) && (set1.size === new Set([...set1, ...set2]).size);


0

ฉันทำตามแนวทางนี้ในการทดสอบ:

let setA = new Set(arrayA);
let setB = new Set(arrayB);
let diff = new Set([...setA].filter(x => !setB.has(x)));
expect([...diff].length).toBe(0);

5
รอสักครู่ ... นี่จะตรวจสอบว่า A มีองค์ประกอบที่ B ไม่มีหรือไม่? ไม่ได้ตรวจสอบว่า B มีองค์ประกอบที่ A ไม่มีหรือไม่ ถ้าคุณพยายามa=[1,2,3]และb=[1,2,3,4]จากนั้นก็กล่าวว่าพวกเขากำลังเดียวกัน ดังนั้นฉันเดาว่าคุณต้องตรวจสอบเพิ่มเติมเช่นsetA.size === setB.size

0

การปรับเปลี่ยนเล็กน้อยมากตามคำตอบของ @Aadit M Shah:

/**
 * check if two sets are equal in the sense that
 * they have a matching set of values.
 *
 * @param {Set} a 
 * @param {Set} b
 * @returns {Boolean} 
 */
const areSetsEqual = (a, b) => (
        (a.size === b.size) ? 
        [...a].every( value => b.has(value) ) : false
);

หากใครก็ตามกำลังมีปัญหาเช่นเดียวกับฉันเนื่องจากพฤติกรรมแปลก ๆ ของ Babel ล่าสุดต้องเพิ่มเงื่อนไขที่ชัดเจนที่นี่

(สำหรับพหูพจน์ฉันคิดว่าareง่ายกว่านิดหน่อยในการอ่านออกเสียง🙃)


-1

1) ตรวจสอบว่าขนาดเท่ากันหรือไม่ ถ้าไม่เช่นนั้นก็จะไม่เท่ากัน

2) วนซ้ำในแต่ละองค์ประกอบของ A และเช็คอินที่มีอยู่ใน B หากไม่มีการส่งคืน unequal

3) หาก 2 เงื่อนไขข้างต้นล้มเหลวนั่นหมายความว่ามันเท่ากัน

let isEql = (setA, setB) => {
  if (setA.size !== setB.size)
    return false;
  
  setA.forEach((val) => {
    if (!setB.has(val))
      return false;
  });
  return true;
}

let setA = new Set([1, 2, {
  3: 4
}]);
let setB = new Set([2, {
    3: 4
  },
  1
]);

console.log(isEql(setA, setB));

2) วิธีที่ 2

let isEql = (A, B) => {
  return JSON.stringify([...A].sort()) == JSON.stringify([...B].sort());
}

let res = isEql(new Set([1, 2, {3:4}]), new Set([{3:4},1, 2]));
console.log(res);


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