जावास्क्रिप्ट सेट के लिए ऑब्जेक्ट समानता कैसे अनुकूलित करें


167

न्यू ईएस 6 (हार्मनी) नए सेट ऑब्जेक्ट का परिचय देता है । सेट द्वारा उपयोग की जाने वाली पहचान एल्गोरिथ्म ===ऑपरेटर के समान है और इसलिए वस्तुओं की तुलना करने के लिए बहुत उपयुक्त नहीं है:

var set = new Set();
set.add({a:1});
set.add({a:1});
console.log([...set.values()]); // Array [ Object, Object ]

गहरी ऑब्जेक्ट तुलना करने के लिए सेट ऑब्जेक्ट्स के लिए समानता कैसे अनुकूलित करें? क्या जावा जैसा कुछ है equals(Object)?


3
"अनुकूलित समानता" से आपका क्या अभिप्राय है? जावास्क्रिप्ट ओवरलोडिंग के लिए ऑपरेटर की अनुमति नहीं देता है, इसलिए ऑपरेटर को ओवरलोड करने का कोई तरीका नहीं है ===। ES6 सेट ऑब्जेक्ट में कोई तुलना विधि नहीं है। .has()विधि और .add()विधि केवल बंद यह एक ही वास्तविक वस्तु या एक आदिम के लिए एक ही मूल्य जा रहा है काम करते हैं।
jfriend00

12
"समानता को अनुकूलित करें" से मेरा मतलब है कि डेवलपर किस तरह वस्तुओं के कुछ जोड़े को समान मान सकता है या नहीं।
czerny

जवाबों:


107

ES6 Setऑब्जेक्ट में कोई तुलना विधि या कस्टम एक्स्टेंसिबिलिटी की तुलना नहीं है।

.has(), .add()और .delete()तरीकों को बंद यह एक ही वास्तविक वस्तु या एक आदिम के लिए एक ही मूल्य जा रहा है काम करते हैं और में प्लग के लिए एक साधन है या आप केवल उस तर्क की जगह नहीं है।

आप निश्चित रूप से अपनी खुद की वस्तु को एक से Setबदल सकते हैं और बदल सकते हैं .has(), .add()और .delete()कुछ ऐसी चीज़ों के साथ विधियाँ जो पहले यह पता लगाने के लिए कि वस्तु सेट में पहले से ही गहरी वस्तु की तुलना करती है, लेकिन प्रदर्शन संभवत: अच्छा नहीं होगा क्योंकि अंतर्निहित Setऑब्जेक्ट मदद नहीं करेगा। बिल्कुल भी। आपको संभवतः मूल कॉल करने से पहले अपने स्वयं के कस्टम तुलना का उपयोग करके एक मैच खोजने के लिए सभी मौजूदा वस्तुओं के माध्यम से एक क्रूर बल चलना करना होगा .add()

यहाँ इस लेख और ES6 सुविधाओं की चर्चा से कुछ जानकारी है:

५.२ मैं यह कैसे कॉन्फ़िगर कर सकता हूं कि नक्शे और सेट कैसे कुंजियों और मूल्यों की तुलना करते हैं

प्रश्न: यह अच्छा होगा यदि मानचित्र की चाबियाँ और कौन से सेट तत्वों को समान माना जाए, इसे कॉन्फ़िगर करने का एक तरीका था। क्यों नहीं है?

उत्तर: उस सुविधा को स्थगित कर दिया गया है, क्योंकि ठीक से और कुशलता से लागू करना मुश्किल है। एक विकल्प कॉलबैक को उन संग्रह को सौंपना है जो समानता को निर्दिष्ट करते हैं।

एक अन्य विकल्प, जावा में उपलब्ध है, एक विधि के माध्यम से समानता को निर्दिष्ट करना है जो ऑब्जेक्ट को लागू करता है (जावा में) (बराबर)। हालांकि, यह दृष्टिकोण उत्परिवर्तनीय वस्तुओं के लिए समस्याग्रस्त है: सामान्य तौर पर, यदि कोई वस्तु बदलती है, तो संग्रह के अंदर उसके "स्थान" को भी बदलना होगा। लेकिन जावा में ऐसा नहीं है। जावास्क्रिप्ट शायद विशेष अपरिवर्तनीय वस्तुओं (तथाकथित मूल्य वस्तुओं) के लिए मूल्य द्वारा तुलना को सक्षम करने के सुरक्षित मार्ग पर जाएगी। मूल्य से तुलना का अर्थ है कि यदि उनकी सामग्री समान है, तो दो मूल्यों को समान माना जाता है। जावास्क्रिप्ट में मूल्य की तुलना में आदिम मूल्यों की तुलना की जाती है।


4
इस विशेष मुद्दे के बारे में लेख संदर्भ जोड़ा गया। ऐसा लगता है कि चुनौती यह है कि किसी वस्तु के साथ कैसे व्यवहार किया जाए जो उस समय के सेट के समान ही था, लेकिन अब इसे बदल दिया गया है और यह उस वस्तु के समान नहीं है। में है Setया नहीं?
jfriend00

3
क्यों एक सरल GetHashCode या इसी तरह लागू नहीं?
जैम्बी

@ जैम्बी - यह एक हैश बनाने की एक दिलचस्प परियोजना होगी जो सभी प्रकार की संपत्तियों और हैश की संपत्तियों को सही क्रम में संभालती है और परिपत्र संदर्भों और इसी तरह के सौदे करती है।
jfriend00

1
@Jamby यहां तक ​​कि एक हैश फ़ंक्शन के साथ आपको अभी भी टकराव से निपटना है। आप सिर्फ समानता की समस्या का सामना कर रहे हैं।
22

5
@ यह सही नहीं है, मैं डेवलपर को अपने विशिष्ट वर्गों के लिए अपने स्वयं के हैश फ़ंक्शन का प्रबंधन करने की अनुमति देता हूं जो लगभग हर मामले में टकराव की समस्या को रोकता है क्योंकि डेवलपर वस्तुओं की प्रकृति को जानता है और एक अच्छी कुंजी प्राप्त कर सकता है। किसी भी अन्य मामले में, वर्तमान तुलना विधि में वापसी। लॉट की भाषाओं को पहले से ही है कि नहीं, जे एस।
जेमबी

28

जैसा कि jfriend00 के उत्तर में समानता के संबंध के अनुकूलन का उल्लेख संभव नहीं है

निम्नलिखित कोड कम्प्यूटेशनल रूप से कुशल (लेकिन मेमोरी महंगा) वर्कअराउंड की रूपरेखा प्रस्तुत करता है :

class GeneralSet {

    constructor() {
        this.map = new Map();
        this[Symbol.iterator] = this.values;
    }

    add(item) {
        this.map.set(item.toIdString(), item);
    }

    values() {
        return this.map.values();
    }

    delete(item) {
        return this.map.delete(item.toIdString());
    }

    // ...
}

प्रत्येक सम्मिलित तत्व को toIdString()स्ट्रिंग को लौटाने वाली विधि को लागू करना होगा । दो वस्तुओं को समान माना जाता है यदि और केवल तभी जब उनके toIdStringतरीके समान मूल्य लौटाते हैं।


आपके पास कंस्ट्रक्टर एक फ़ंक्शन भी ले सकता है जो समानता के लिए वस्तुओं की तुलना करता है। यह अच्छा है अगर आप चाहते हैं कि यह समानता सेट की एक विशेषता हो, बल्कि इसमें उपयोग की गई वस्तुओं के बजाय।
बेन जे

1
@BenJ एक स्ट्रिंग बनाने और इसे मैप में डालने का बिंदु यह है कि इस तरह से आपका जावास्क्रिप्ट इंजन आपकी वस्तु के हैश मान को खोजने के लिए देशी कोड में एक ~ O (1) खोज का उपयोग करेगा, जबकि एक समानता फ़ंक्शन स्वीकार करेगा सेट का एक रैखिक स्कैन करने के लिए और हर तत्व की जाँच करें।
जैम्बी

3
इस पद्धति के साथ एक चुनौती यह है कि मुझे लगता है कि यह मानता है कि मूल्य item.toIdString()अपरिवर्तनीय है और बदल नहीं सकता। क्योंकि अगर यह हो सकता है, तो इसमें GeneralSetआसानी से "डुप्लिकेट" आइटम के साथ अमान्य हो सकता है। इसलिए, इस तरह का एक समाधान केवल कुछ विशेष परिस्थितियों तक ही सीमित रहेगा जहां सेट का उपयोग करते समय ऑब्जेक्ट स्वयं नहीं बदले जाते हैं या जहां एक सेट जो अमान्य हो जाता है वह परिणाम का नहीं है। ये सभी मुद्दे संभवतः आगे बताते हैं कि ईएस 6 सेट इस कार्यक्षमता को उजागर क्यों नहीं करता है क्योंकि यह वास्तव में केवल कुछ परिस्थितियों में काम करता है।
२२

क्या .delete()इस उत्तर के सही कार्यान्वयन को जोड़ना संभव है ?
जेलेविकोविच

1
@JLewkovich सुनिश्चित
czerny

6

जैसा कि शीर्ष उत्तर में उल्लेख किया गया है, समानता को अनुकूलित करना उत्परिवर्तनीय वस्तुओं के लिए समस्याग्रस्त है। अच्छी खबर यह है (और मुझे आश्चर्य है कि किसी ने अभी तक इसका उल्लेख नहीं किया है) एक बहुत लोकप्रिय पुस्तकालय है जिसे अपरिवर्तनीय-जेएस कहा जाता है जो अपरिवर्तनीय प्रकारों का एक समृद्ध सेट प्रदान करता है जो आपके लिए खोज रहे गहरे मूल्य समानता शब्दार्थ प्रदान करते हैं।

यहाँ अपरिवर्तनीय js का उपयोग करके आपका उदाहरण दिया गया है :

const { Map, Set } = require('immutable');
var set = new Set();
set = set.add(Map({a:1}));
set = set.add(Map({a:1}));
console.log([...set.values()]); // [Map {"a" => 1}]

10
देशी सेट / मैप के साथ अपरिवर्तनीय js सेट / मैप के प्रदर्शन की तुलना कैसे होती है?
फ्रैंकस्टर

5

यहां उत्तरों में जोड़ने के लिए, मैंने आगे बढ़कर एक मैप रैपर लागू किया, जो एक कस्टम हैश फ़ंक्शन, एक कस्टम समानता फ़ंक्शन लेता है, और अलग-अलग मान संग्रहीत करता है जिसमें बकेट में समतुल्य (कस्टम) हैश होता है।

मुख्य रूप से, यह czerny के स्ट्रिंग संघनन विधि की तुलना में धीमा निकला

पूर्ण स्रोत यहां: https://github.com/makoConstruct/ValueMap


"स्ट्रिंग संघनन"? क्या उनका तरीका "स्ट्रिंग सरोगेटिंग" जैसा नहीं है (यदि आप इसे एक नाम देने जा रहे हैं)? या क्या कोई कारण है कि आप "कॉन्सेप्टन" शब्द का उपयोग करते हैं? मैं उत्सुक हूं ;-)
बिंकी

@binki यह एक अच्छा सवाल है और मुझे लगता है कि उत्तर एक अच्छी बात लाता है कि मुझे समझ में आने में थोड़ा समय लगा। आमतौर पर, हैश कोड की गणना करते समय, कोई व्यक्ति HashCodeBuilder की तरह कुछ करता है जो व्यक्तिगत क्षेत्रों के हैश कोड को गुणा करता है और अद्वितीय होने की गारंटी नहीं है (इसलिए एक कस्टम समानता फ़ंक्शन की आवश्यकता है)। हालाँकि, जब आप एक आईडी स्ट्रिंग उत्पन्न करते हैं, तो आप अलग-अलग क्षेत्रों की आईडी स्ट्रिंग को बदलते हैं, जो विशिष्ट होने की गारंटी देता है (और इस प्रकार कोई समानता कार्य की आवश्यकता नहीं है)
पेस

तो अगर आप के Pointरूप में परिभाषित किया गया है { x: number, y: number }तो आपका id stringशायद है x.toString() + ',' + y.toString()
पेस

अपनी समानता की तुलना करना कुछ मूल्य का निर्माण करता है जो केवल तभी भिन्न होता है जब चीजों को गैर-बराबर माना जाना चाहिए यह एक ऐसी रणनीति है जिसका मैंने पहले उपयोग किया है। कभी-कभी ऐसी चीजों के बारे में सोचना आसान होता है। उस स्थिति में, आप हैश के बजाय कुंजी उत्पन्न कर रहे हैं । जब तक आपके पास एक प्रमुख व्युत्पन्न होता है जो एक कुंजी को इस रूप में प्रस्तुत करता है कि मौजूदा उपकरण मूल्य-शैली समानता के साथ समर्थन करते हैं, जो लगभग हमेशा समाप्त होता है , तो आप पूरे हैशिंग और बकेटिंग कदम को छोड़ सकते हैं जैसा कि आपने कहा था और बस सीधे एक का उपयोग करें या व्युत्पन्न कुंजी के संदर्भ में पुरानी शैली की सादी वस्तु। StringMap
बिंकी

1
यदि आप वास्तव में एक कुंजी व्युत्पन्न के अपने कार्यान्वयन में स्ट्रिंग संघनन का उपयोग करते हैं, तो सावधान रहने की एक बात यह है कि यदि उन्हें किसी भी मूल्य पर लेने की अनुमति दी जाती है, तो स्ट्रिंग गुणों का विशेष उपचार किया जा सकता है। उदाहरण के लिए, अगर आपके पास {x: '1,2', y: '3'}और {x: '1', y: '2,3'}है, तो String(x) + ',' + String(y)दोनों वस्तुओं के लिए होगा उत्पादन एक ही मूल्य। एक सुरक्षित विकल्प, यह मानते हुए कि आप JSON.stringify()नियतात्मक होने पर भरोसा कर सकते हैं , इसकी स्ट्रिंग से बचने और JSON.stringify([x, y])इसके बजाय उपयोग करने का लाभ लेना है ।
बिंकी

3

उनकी तुलना सीधे तौर पर संभव नहीं है, लेकिन JSON.stringify काम करता है अगर चाबियाँ बस छांट दी गईं। जैसा कि मैंने एक टिप्पणी में बताया है

JSON.stringify ({a: 1, b: 2})! == JSON.stringify ({b: 2, a: 1});

लेकिन हम एक कस्टम स्ट्रिंग विधि के साथ उसके आसपास काम कर सकते हैं। पहले हम विधि लिखते हैं

कस्टम स्ट्रिंग

Object.prototype.stringifySorted = function(){
    let oldObj = this;
    let obj = (oldObj.length || oldObj.length === 0) ? [] : {};
    for (let key of Object.keys(this).sort((a, b) => a.localeCompare(b))) {
        let type = typeof (oldObj[key])
        if (type === 'object') {
            obj[key] = oldObj[key].stringifySorted();
        } else {
            obj[key] = oldObj[key];
        }
    }
    return JSON.stringify(obj);
}

सेट

अब हम एक सेट का उपयोग करते हैं। लेकिन हम वस्तुओं के बजाय स्ट्रिंग्स के सेट का उपयोग करते हैं

let set = new Set()
set.add({a:1, b:2}.stringifySorted());

set.has({b:2, a:1}.stringifySorted());
// returns true

सभी मान प्राप्त करें

जब हमने सेट बनाया और मूल्यों को जोड़ा, तो हम सभी मान प्राप्त कर सकते हैं

let iterator = set.values();
let done = false;
while (!done) {
  let val = iterator.next();

  if (!done) {
    console.log(val.value);
  }
  done = val.done;
}

यहाँ एक फ़ाइल http://tpcg.io/FnJg2i में सभी के साथ एक लिंक दिया गया है


"अगर चाबियाँ छांटी जाती हैं" तो एक बड़ी बात है, खासकर जटिल वस्तुओं के लिए
अलेक्जेंडर मिल्स

यही कारण है कि मैंने इस दृष्टिकोण को चुना है;)
राहत।

2

शायद आप JSON.stringify()गहरी वस्तु तुलना करने के लिए उपयोग करने का प्रयास कर सकते हैं ।

उदाहरण के लिए :

const arr = [
  {name:'a', value:10},
  {name:'a', value:20},
  {name:'a', value:20},
  {name:'b', value:30},
  {name:'b', value:40},
  {name:'b', value:40}
];

const names = new Set();
const result = arr.filter(item => !names.has(JSON.stringify(item)) ? names.add(JSON.stringify(item)) : false);

console.log(result);


2
यह काम कर सकता है, लेकिन JSON.stringify ({a: 1, b: 2}) के रूप में नहीं है! == JSON.stringify ({b: 2, a: 1}) यदि आपके प्रोग्राम में सभी ऑब्जेक्ट एक ही द्वारा बनाए गए हैं आप सुरक्षित हैं। लेकिन सामान्य रूप से सुरक्षित समाधान नहीं
राहत।

1
आह हाँ, "इसे स्ट्रिंग में बदलें"। सब कुछ के लिए जावास्क्रिप्ट का जवाब।
टिम्मम्म

2

टाइपस्क्रिप्ट उपयोगकर्ताओं के लिए दूसरों के उत्तर (विशेषकर czerny ) को एक अच्छा प्रकार-सुरक्षित और पुन: प्रयोज्य आधार के रूप में सामान्यीकृत किया जा सकता है:

/**
 * Map that stringifies the key objects in order to leverage
 * the javascript native Map and preserve key uniqueness.
 */
abstract class StringifyingMap<K, V> {
    private map = new Map<string, V>();
    private keyMap = new Map<string, K>();

    has(key: K): boolean {
        let keyString = this.stringifyKey(key);
        return this.map.has(keyString);
    }
    get(key: K): V {
        let keyString = this.stringifyKey(key);
        return this.map.get(keyString);
    }
    set(key: K, value: V): StringifyingMap<K, V> {
        let keyString = this.stringifyKey(key);
        this.map.set(keyString, value);
        this.keyMap.set(keyString, key);
        return this;
    }

    /**
     * Puts new key/value if key is absent.
     * @param key key
     * @param defaultValue default value factory
     */
    putIfAbsent(key: K, defaultValue: () => V): boolean {
        if (!this.has(key)) {
            let value = defaultValue();
            this.set(key, value);
            return true;
        }
        return false;
    }

    keys(): IterableIterator<K> {
        return this.keyMap.values();
    }

    keyList(): K[] {
        return [...this.keys()];
    }

    delete(key: K): boolean {
        let keyString = this.stringifyKey(key);
        let flag = this.map.delete(keyString);
        this.keyMap.delete(keyString);
        return flag;
    }

    clear(): void {
        this.map.clear();
        this.keyMap.clear();
    }

    size(): number {
        return this.map.size;
    }

    /**
     * Turns the `key` object to a primitive `string` for the underlying `Map`
     * @param key key to be stringified
     */
    protected abstract stringifyKey(key: K): string;
}

उदाहरण कार्यान्वयन तो यह सरल है: बस stringifyKeyविधि को ओवरराइड करें । मेरे मामले में मैं कुछ uriसंपत्ति को सख्त करता हूं ।

class MyMap extends StringifyingMap<MyKey, MyValue> {
    protected stringifyKey(key: MyKey): string {
        return key.uri.toString();
    }
}

उदाहरण का उपयोग तब होता है जैसे कि यह एक नियमित था Map<K, V>

const key1 = new MyKey(1);
const value1 = new MyValue(1);
const value2 = new MyValue(2);

const myMap = new MyMap();
myMap.set(key1, value1);
myMap.set(key1, value2); // native Map would put another key/value pair

myMap.size(); // returns 1, not 2

-1

दोनों सेटों के संयोजन से एक नया सेट बनाएं, फिर लंबाई की तुलना करें।

let set1 = new Set([1, 2, 'a', 'b'])
let set2 = new Set([1, 'a', 'a', 2, 'b'])
let set4 = new Set([1, 2, 'a'])

function areSetsEqual(set1, set2) {
  const set3 = new Set([...set1], [...set2])
  return set3.size === set1.size && set3.size === set2.size
}

console.log('set1 equals set2 =', areSetsEqual(set1, set2))
console.log('set1 equals set4 =', areSetsEqual(set1, set4))

set1 बराबर set2 = true

set1 के बराबर set4 = false


2
क्या यह उत्तर प्रश्न से संबंधित है? यह प्रश्न सेट क्लास के एक उदाहरण के संबंध में वस्तुओं की समानता के बारे में है। यह प्रश्न दो सेट उदाहरणों की समानता पर चर्चा करता है।
czerny

@ चित्रण आप सही हैं - मैं मूल रूप से इस स्टैकओवरफ़्लो प्रश्न को देख रहा था, जहाँ ऊपर की विधि का उपयोग किया जा सकता है: stackoverflow.com/questions/6229197/…
स्टीफन मुसरा

-2

किसी ऐसे व्यक्ति के लिए जिसने Google पर (मेरे रूप में) यह पाया कि कुंजी के रूप में किसी ऑब्जेक्ट का उपयोग करके मैप का मान प्राप्त करना चाहता है:

चेतावनी: यह उत्तर सभी वस्तुओं के साथ काम नहीं करेगा

var map = new Map<string,string>();

map.set(JSON.stringify({"A":2} /*string of object as key*/), "Worked");

console.log(map.get(JSON.stringify({"A":2}))||"Not worked");

आउटपुट:

काम

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.