JavaScript'te kümeler taklit ediliyor mu?


220

JavaScript ile çalışıyorum. Aşağıdaki özelliklere sahip, benzersiz , sıralanmamış dize değerlerinin bir listesini saklamak istiyorum :

  1. 'listede A' var mı diye sormanın hızlı bir yolu var mı?
  2. 'listede varsa A'yı listeden silmek' için hızlı bir yol
  3. 'listede yoksa A'yı eklemenin' hızlı bir yolu.

Gerçekten istediğim bir set. JavaScript'te bir seti taklit etmenin en iyi yolu için herhangi bir öneriniz var mı?

Bu soru , anahtarları özellikleri depolayan ve değerlerin tümü true değerine ayarlanmış bir Nesne kullanılmasını önerir : Bu mantıklı bir yol mu?



Yanıtlar:


262

ES6 özellikli bir ortamda (ihtiyacınız olan ES6 özelliklerine sahip belirli bir tarayıcı veya ortamınız için ES6 kodunu aktarırken) node.js programlıyorsanız, ES6'da Setyerleşik nesneyi kullanabilirsiniz . Çok güzel yeteneklere sahiptir ve ortamınızda olduğu gibi kullanılabilir.


ES5 ortamındaki birçok basit şey için, bir Nesne kullanmak çok iyi çalışır. Eğer objsizin nesnedir ve Asiz sette çalışmak istediğiniz değere sahip bir değişken olduğunu, o zaman bu yapabilirsiniz:

Başlatma kodu:

// create empty object
var obj = {};

// or create an object with some items already in it
var obj = {"1":true, "2":true, "3":true, "9":true};

Soru 1: mi Alistedeki:

if (A in obj) {
    // put code here
}

Soru 2: Varsa, listeden 'A'yı silin:

delete obj[A];

Soru 3: Zaten orada değilse listeye 'A' ekleyin

obj[A] = true;

Tamlık için A, listede olup olmadığının testi bununla biraz daha güvenlidir:

if (Object.prototype.hasOwnProperty.call(obj, A))
    // put code here
}

yerleşik yöntemler ve / veya özellik gibi temel nesneler arasındaki potansiyel çakışma nedeniyle constructor.


ES6'daki kenar çubuğu: ECMAScript 6'nın geçerli çalışma sürümü veya ES 2015 adı verilen bir şey yerleşik bir Set nesnesine sahiptir . Şimdi bazı tarayıcılarda uygulanmaktadır. Tarayıcı kullanılabilirlik süresini bir şekilde değiştiği için için, çizgiyi bakabilirsiniz Setiçinde bu ES6 uyumluluk tablosunun tarayıcı kullanılabilirlik için mevcut durumunu görmek için.

Yerleşik Set nesnesinin bir avantajı, tüm anahtarları Object gibi bir dizeye zorlamamasıdır, böylece hem 5 hem de "5" tuşlarına ayrı anahtarlar olarak sahip olabilirsiniz. Ve hatta, dizeleri dönüştürmeden Nesneleri doğrudan kümede kullanabilirsiniz. İşte Set nesnesindeki bazı yetenekleri ve MDN belgelerini açıklayan bir makale .

Şimdi ES6 set nesnesi için bir çoklu dolgu yazdım, böylece şimdi kullanmaya başlayabilirsiniz ve tarayıcı destekliyorsa otomatik olarak yerleşik set nesnesine erteleyecektir. Bunun avantajı, IE7'ye kadar çalışacak ES6 uyumlu kod yazmanızdır. Ancak, bazı dezavantajları var. ES6 set arayüzü, ES6 yineleyicilerinden yararlanır, böylece sizin gibi şeyler yapabilirsiniz for (item of mySet)ve sizin için otomatik olarak set boyunca tekrar eder. Ancak, bu tür bir dil özelliği çok dolgu ile uygulanamaz. Yeni ES6 dilleri özelliklerini kullanmadan bir ES6 setini yine de yineleyebilirsiniz, ancak açıkçası yeni dil özellikleri olmadan, aşağıda eklediğim diğer set arayüzü kadar uygun değildir.

Her ikisine de baktıktan sonra hangisinin sizin için en iyi olduğuna karar verebilirsiniz. ES6 set çoklu dolgusu burada: https://github.com/jfriend00/ES6-Set .

FYI, kendi testlerimde, Firefox v29 Set uygulamasının spesifikasyonun mevcut taslağında tam olarak güncel olmadığını fark ettim. Örneğin, .add()özellik açıklamaları ve çoklu dolum desteklediğim gibi yöntem çağrılarını zincirleyemezsiniz . Bu, henüz tamamlanmadığı için muhtemelen hareket halindeki bir spesifikasyon meselesidir.


Önceden Oluşturulmuş Küme nesneleri: Herhangi bir tarayıcıda kullanabileceğiniz bir kümede çalışma yöntemleri olan önceden oluşturulmuş bir nesne istiyorsanız, farklı türlerde kümeler uygulayan bir dizi farklı önceden oluşturulmuş nesne kullanabilirsiniz. Bir set nesnesinin temellerini uygulayan küçük bir kod olan bir miniSet vardır. Ayrıca daha zengin özelliklere sahip bir set nesnesi ve bir Sözlük (her bir anahtar için bir değer depolayalım / alalım) ve bir ObjectSet (bir nesne kümesi tutalım - ya JS nesneleri veya her biri için benzersiz bir anahtar üreten bir işlev veya ObjectSet anahtarı sizin için oluşturur).

İşte miniSet için kod kopyası (en yukarı güncel kodudur var github burada ).

"use strict";
//-------------------------------------------
// Simple implementation of a Set in javascript
//
// Supports any element type that can uniquely be identified
//    with its string conversion (e.g. toString() operator).
// This includes strings, numbers, dates, etc...
// It does not include objects or arrays though
//    one could implement a toString() operator
//    on an object that would uniquely identify
//    the object.
// 
// Uses a javascript object to hold the Set
//
// This is a subset of the Set object designed to be smaller and faster, but
// not as extensible.  This implementation should not be mixed with the Set object
// as in don't pass a miniSet to a Set constructor or vice versa.  Both can exist and be
// used separately in the same project, though if you want the features of the other
// sets, then you should probably just include them and not include miniSet as it's
// really designed for someone who just wants the smallest amount of code to get
// a Set interface.
//
// s.add(key)                      // adds a key to the Set (if it doesn't already exist)
// s.add(key1, key2, key3)         // adds multiple keys
// s.add([key1, key2, key3])       // adds multiple keys
// s.add(otherSet)                 // adds another Set to this Set
// s.add(arrayLikeObject)          // adds anything that a subclass returns true on _isPseudoArray()
// s.remove(key)                   // removes a key from the Set
// s.remove(["a", "b"]);           // removes all keys in the passed in array
// s.remove("a", "b", ["first", "second"]);   // removes all keys specified
// s.has(key)                      // returns true/false if key exists in the Set
// s.isEmpty()                     // returns true/false for whether Set is empty
// s.keys()                        // returns an array of keys in the Set
// s.clear()                       // clears all data from the Set
// s.each(fn)                      // iterate over all items in the Set (return this for method chaining)
//
// All methods return the object for use in chaining except when the point
// of the method is to return a specific value (such as .keys() or .isEmpty())
//-------------------------------------------


// polyfill for Array.isArray
if(!Array.isArray) {
    Array.isArray = function (vArg) {
        return Object.prototype.toString.call(vArg) === "[object Array]";
    };
}

function MiniSet(initialData) {
    // Usage:
    // new MiniSet()
    // new MiniSet(1,2,3,4,5)
    // new MiniSet(["1", "2", "3", "4", "5"])
    // new MiniSet(otherSet)
    // new MiniSet(otherSet1, otherSet2, ...)
    this.data = {};
    this.add.apply(this, arguments);
}

MiniSet.prototype = {
    // usage:
    // add(key)
    // add([key1, key2, key3])
    // add(otherSet)
    // add(key1, [key2, key3, key4], otherSet)
    // add supports the EXACT same arguments as the constructor
    add: function() {
        var key;
        for (var i = 0; i < arguments.length; i++) {
            key = arguments[i];
            if (Array.isArray(key)) {
                for (var j = 0; j < key.length; j++) {
                    this.data[key[j]] = key[j];
                }
            } else if (key instanceof MiniSet) {
                var self = this;
                key.each(function(val, key) {
                    self.data[key] = val;
                });
            } else {
                // just a key, so add it
                this.data[key] = key;
            }
        }
        return this;
    },
    // private: to remove a single item
    // does not have all the argument flexibility that remove does
    _removeItem: function(key) {
        delete this.data[key];
    },
    // usage:
    // remove(key)
    // remove(key1, key2, key3)
    // remove([key1, key2, key3])
    remove: function(key) {
        // can be one or more args
        // each arg can be a string key or an array of string keys
        var item;
        for (var j = 0; j < arguments.length; j++) {
            item = arguments[j];
            if (Array.isArray(item)) {
                // must be an array of keys
                for (var i = 0; i < item.length; i++) {
                    this._removeItem(item[i]);
                }
            } else {
                this._removeItem(item);
            }
        }
        return this;
    },
    // returns true/false on whether the key exists
    has: function(key) {
        return Object.prototype.hasOwnProperty.call(this.data, key);
    },
    // tells you if the Set is empty or not
    isEmpty: function() {
        for (var key in this.data) {
            if (this.has(key)) {
                return false;
            }
        }
        return true;
    },
    // returns an array of all keys in the Set
    // returns the original key (not the string converted form)
    keys: function() {
        var results = [];
        this.each(function(data) {
            results.push(data);
        });
        return results;
    },
    // clears the Set
    clear: function() {
        this.data = {}; 
        return this;
    },
    // iterate over all elements in the Set until callback returns false
    // myCallback(key) is the callback form
    // If the callback returns false, then the iteration is stopped
    // returns the Set to allow method chaining
    each: function(fn) {
        this.eachReturn(fn);
        return this;
    },
    // iterate all elements until callback returns false
    // myCallback(key) is the callback form
    // returns false if iteration was stopped
    // returns true if iteration completed
    eachReturn: function(fn) {
        for (var key in this.data) {
            if (this.has(key)) {
                if (fn.call(this, this.data[key], key) === false) {
                    return false;
                }
            }
        }
        return true;
    }
};

MiniSet.prototype.constructor = MiniSet;

16
Bu soruyu çözer ama açık olmak gerekirse, bu uygulama olmaz tamsayı veya dizeleri dışında şeyler setleri için çalışır.
mkirk

3
@mkirk - evet, kümede dizine eklediğiniz öğenin, dizin anahtarı olabilecek bir dize temsili olması gerekir (örneğin, bir dize veya öğeyi benzersiz bir şekilde tanımlayan bir toString () yöntemine sahiptir).
13:12

4
Listedeki öğeleri almak için kullanabilirsiniz Object.keys(obj).
Blixt

3
@Blixt - Object.keys()IE9, FF4, Safari 5, Opera 12 veya üstü gerekir. Burada eski tarayıcılar için bir çoklu dolgu var .
jfriend00

1
obj.hasOwnProperty(prop)Üyelik kontrolleri için kullanmayın . Kullan Object.prototype.hasOwnProperty.call(obj, prop)"set" değeri içerse bile çalıştığı yerine "hasOwnProperty".
davidchambers

72

Gibi özellikleri olmayan bir Nesne oluşturabilirsiniz

var set = Object.create(null)

bir set olarak hareket edebilir ve kullanım ihtiyacını ortadan kaldırır hasOwnProperty.


var set = Object.create(null); // create an object with no properties

if (A in set) { // 1. is A in the list
  // some code
}
delete set[a]; // 2. delete A from the list if it exists in the list 
set[A] = true; // 3. add A to the list if it is not already present

Güzel, ama neden "hasOwnProperty kullanma ihtiyacını ortadan kaldırır" dediğini emin değilim
blueFast

13
Sadece kullanırsanız set = {}de (örneğin Nesne tüm özelliklerini devralır toStringsizinle kümesinin yük için (eklemiş özelliklerini) kontrol etmek zorunda kalacak, böylece) hasOwnPropertyiçindeif (A in set)
Thorben Croise

6
Tamamen boş bir nesne yaratmanın mümkün olduğunu bilmiyordum. Teşekkürler, çözümünüz çok zarif.
blueFast

1
İlginç, ama bunun dezavantajı, set[A]=truetek bir initialiser yerine eklemek istediğiniz her öğe için ifadelere sahip olmanız gerektiğidir.
vogomatix

1
Ne demek istediğinizden emin değilsiniz, ancak zaten mevcut olan bir set tarafından bir seti başlatmaya atıfta bulunuyorsanız, şu satır boyunca bir şeyler yapabilirsinizs = Object.create(null);s["thorben"] = true;ss = Object.create(s)
Thorben Croisé

23

ECMAScript 6 itibariyle, Veri yapısını ayarla yerleşik bir özelliktir . Node.js sürümleriyle uyumluluk burada bulunabilir .


4
Merhaba, sadece netlik için - 2014'te, bu hala Chrome'da deneysel mi? Değilse, lütfen cevabınızı düzenleyebilir misiniz? Teşekkürler
Karel Bílek

1
Evet, Chrome için hala deneysel. ECMAScript'in 'resmi olarak' serbest bırakılması gerektiği 2014'ün sonunda destekleneceğine inanıyorum. Daha sonra cevabımı buna göre güncelleyeceğim.
Şubat'ta ilahi

Tamam, cevap verdiğin için teşekkürler! (JavaScript yanıtları oldukça hızlı bir şekilde güncelliğini
yitirir

1
@Val in, Setnesnelerin öğelerini özellikler olarak içermediği için çalışmaz, çünkü kümeler herhangi bir türde öğeye sahip olabilir, ancak özellikler dizelerdir. hasSet([1,2]).has(1)
Oriol

1
Salvador Dali'nin cevabı daha kapsamlı ve günceldir.
Dan Dascalescu

14

Javascript'in ES6 sürümünde, set için yerleşik bir tür var ( tarayıcınızla uyumluluğu kontrol edin ).

var numbers = new Set([1, 2, 4]); // Set {1, 2, 4}

İçin bir öğe eklemek basitçe kullanmak sete .add()çalışır, O(1)ve ya ekler kümesine elemanını (mevcut değil ise) veya zaten varsa hiçbir şey yapmaz. Oraya herhangi bir tür eleman ekleyebilirsiniz (diziler, dizeler, sayılar)

numbers.add(4); // Set {1, 2, 4}
numbers.add(6); // Set {1, 2, 4, 6}

Kümedeki eleman sayısını kontrol etmek için kullanabilirsiniz .size. Ayrıca çalışırO(1)

numbers.size; // 4

To set öğesinden kaldırmaz kullanım .delete(). Değer oradaysa (ve kaldırılmışsa) true değerini, değer yoksa false değerini döndürür. Ayrıca çalışır O(1).

numbers.delete(2); // true
numbers.delete(2); // false

To kontrol elemanı varoldukları için ister bir dizi kullanımda .has()eleman aksi sette ve yanlış ise true döndürür. Ayrıca çalışır O(1).

numbers.has(3); // false
numbers.has(1); // true

İstediğiniz yöntemlere ek olarak, birkaç tane daha var:

  • numbers.clear(); tüm öğeleri setten kaldırır
  • numbers.forEach(callback); ekleme sırasında kümenin değerleri üzerinden yineleme
  • numbers.entries(); tüm değerlerin tekrarını oluştur
  • numbers.keys(); ile aynı setin anahtarlarını döndürür numbers.values()

Ayrıca, yalnızca nesne türü değerler eklemenize izin veren bir Weakset de vardır.


.add()O (1) 'deki koşulara bir referans gösterebilir misiniz ? Ben merak ediyorum,
Yeşil

10

Şu anda sayılar ve dizelerle oldukça iyi çalışan Setler uygulamasını başlattım. Ana odak noktam fark operasyonuydu, bu yüzden onu olabildiğince verimli hale getirmeye çalıştım. Çatallar ve kod incelemeleri bekliyoruz!

https://github.com/mcrisc/SetJS


vay bu sınıf fındık! CouchDB harita / azaltma fonksiyonları içinde JavaScript yazmasaydım bunu tamamen kullanırdım!
portforwardpodcast

9

Sadece d3.js kütüphanesinin setler, haritalar ve diğer veri yapılarının uygulanmasına sahip olduğunu fark ettim. Verimlilikleri hakkında tartışamam, ancak popüler bir kütüphane olduğu gerçeğine bakarak ihtiyacınız olan şey olmalı.

Belgeler burada

Kolaylık sağlamak için bağlantıdan kopyalarım (ilk 3 işlev ilgi çeken işlevlerdir)


  • d3.set ([dizi])

Yeni bir set oluşturur. Dizi belirtilirse, verilen dize değerleri dizisini döndürülen kümeye ekler.

  • set.has (değer)

Yalnızca bu kümede belirtilen değer dizesi için bir girdi varsa true değerini döndürür.

  • set.add (değer)

Bu kümeye belirtilen değer dizesini ekler.

  • set.remove (değer)

Küme belirtilen değer dizesini içeriyorsa, onu kaldırır ve true değerini döndürür. Aksi takdirde, bu yöntem hiçbir şey yapmaz ve false değerini döndürür.

  • set.values ​​()

Bu kümedeki dize değerlerinin bir dizisini döndürür. Döndürülen değerlerin sırası isteğe bağlıdır. Bir dizi dizenin benzersiz değerlerini hesaplamanın uygun bir yolu olarak kullanılabilir. Örneğin:

d3.set (["foo", "bar", "foo", "baz"]). değerler (); // "foo", "bar", "baz"

  • set.forEach (fonksiyonu)

Değeri bağımsız değişken olarak ileterek bu kümedeki her değer için belirtilen işlevi çağırır. İşlevin bu bağlamı bu kümedir. Undefined değerini döndürür. Yineleme sırası isteğe bağlıdır.

  • set.empty ()

Yalnızca bu kümenin sıfır değerleri varsa true değerini döndürür.

  • ) (Set.size

Bu kümedeki değerlerin sayısını döndürür.


4

Evet, bu mantıklı bir yol - hepsi bir nesne (bu kullanım örneği için) - doğrudan erişime sahip bir grup anahtar / değer.

Eklemeden önce zaten orada olup olmadığını kontrol etmeniz gerekir, ya da sadece varlığını belirtmeniz gerekiyorsa, "tekrar eklemek" aslında hiçbir şeyi değiştirmez, sadece nesneye tekrar ayarlar.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.