我在JavaScript工作。我想存储一个唯一的,无序的字符串值列表,具有以下属性:

问“a在名单中吗?” 一种快速的方法来做“从列表中删除a,如果它存在于列表” 一个快速的方法来做“添加a到列表中,如果它还没有出现”。

我真正想要的是一套。在JavaScript中模仿集合的最佳方法有什么建议吗?

这个问题建议使用一个对象,键存储属性,值全部设置为true:这是明智的方式吗?


当前回答

是的,这是一种明智的方式——对象就是这样(在这个用例中)——一堆可以直接访问的键/值。

在添加它之前,你需要检查它是否已经在那里,或者如果你只需要表示存在,“添加”它实际上不会改变任何东西,它只是再次将它设置在对象上。

其他回答

你可以创建一个没有类似属性的对象

var set = Object.create(null)

它可以作为一个集合,并消除了使用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

我刚刚注意到d3.js库有集,映射和其他数据结构的实现。 我不能争辩他们的效率,但从这是一个受欢迎的图书馆的事实来看,它一定是你需要的。

文档在这里

为了方便,我从链接中复制(前3个函数是那些感兴趣的)


d3.set([]数组)

构造一个新集合。如果指定了array,则将给定的字符串值数组添加到返回集。

set.has(值)

当且仅当此集合有指定值字符串的条目时返回true。

set.add(值)

将指定的值字符串添加到此集。

set.remove(值)

如果集合包含指定的值字符串,则删除它并返回true。否则,该方法不执行任何操作并返回false。

set.values ()

返回此集合中字符串值的数组。返回值的顺序是任意的。可以作为计算一组字符串的唯一值的方便方法。例如:

d3。Set (["foo", "bar", "foo", "baz"]).values();// "foo", "bar", "baz"

set.forEach(函数)

为此集中的每个值调用指定的函数,并将值作为参数传递。函数的上下文是这个集合。返回未定义。迭代顺序是任意的。

set.empty ()

当且仅当此集合为零值时返回true。

set.size ()

返回此集合中值的数目。

我已经开始了一个集的实现,目前工作得很好与数字和字符串。我主要关注的是差值运算,所以我尽可能地提高它的效率。fork和代码审查是受欢迎的!

https://github.com/mcrisc/SetJS

从ECMAScript 6开始,Set数据结构是一个内置特性。与node.js版本的兼容性可以在这里找到。

如果你在一个支持ES6的环境中编程(比如node.js,一个你需要的具有ES6功能的特定浏览器,或者为你的环境编译ES6代码),那么你可以使用ES6内置的Set对象。它具有非常好的功能,可以在您的环境中使用。


对于ES5环境中的许多简单的事情,使用Object工作得非常好。如果obj是你的对象,而A是一个变量,它拥有你想要在集合中操作的值,那么你可以这样做:

初始化代码:

// 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};

问题1:A在名单中吗?

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

问题2:如果列表中有“A”,请删除它:

delete obj[A];

问题3:如果清单上没有“A”,就把它加进去

obj[A] = true;

为了完整起见,测试A是否在列表中稍微安全一些:

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

因为内置方法和/或基对象上的属性(如构造函数属性)之间存在潜在冲突。


ES6边栏:ECMAScript 6或ES 2015的当前工作版本有一个内置的Set对象。现在在一些浏览器中实现了它。由于浏览器可用性随时间而变化,您可以查看ES6兼容性表中的Set行,以查看浏览器可用性的当前状态。

内置Set对象的一个优点是,它不像object那样将所有键强制转换为字符串,所以你可以将5和“5”作为单独的键。而且,您甚至可以直接在集合中使用对象,而不需要进行字符串转换。下面是一篇描述Set对象的一些功能和MDN文档的文章。

I have now written a polyfill for the ES6 set object so you could start using that now and it will automatically defer to the built-in set object if the browser supports it. This has the advantage that you're writing ES6 compatible code that will work all the way back to IE7. But, there are some downsides. The ES6 set interface takes advantage of the ES6 iterators so you can do things like for (item of mySet) and it will automatically iterate through the set for you. But, this type of language feature cannot be implemented via polyfill. You can still iterate an ES6 set without using the new ES6 languages features, but frankly without the new language features, it isn't as convenient as the other set interface I include below.

你可以在看完两者后决定哪一个最适合你。ES6集polyfill在这里:https://github.com/jfriend00/ES6-Set。

仅供参考,在我自己的测试中,我注意到Firefox v29 Set实现在当前的规范草案中并不是完全最新的。例如,你不能像规范所描述的和我的polyfill所支持的那样链接.add()方法调用。这可能是一个正在进行的规范问题,因为它还没有最终确定。


Pre-Built Set objects: If you want an already built object that has methods for operating on a set that you can use in any browser, you can use a series of different pre-built objects that implement different types of sets. There is a miniSet which is small code that implements the basics of a set object. It also has a more feature rich set object and several derivations including a Dictionary (let's you store/retrieve a value for each key) and an ObjectSet (let's you keep a set of objects - either JS objects or DOM objects where you either supply the function that generates a unique key for each one or the ObjectSet will generate the key for you).

这是一份miniSet的代码副本(最新的代码在github上)。

"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;