JavaScriptでセットを模倣していますか?


220

私はJavaScriptで作業しています。次のプロパティを持つ、一意で順序付けされていない文字列値のリストを保存したいと思います。

  1. 「リストにAがいる」と尋ねる高速な方法は?
  2. 「リストにAが存在する場合、リストからAを削除する」ための高速な方法
  3. 「Aがまだ存在しない場合は、リストに追加する」ための高速な方法。

私が本当に欲しいのはセットです。JavaScriptでセットを模倣する最良の方法について何か提案はありますか?

この質問では、プロパティを格納するキーと値をすべてtrueに設定してObjectを使用することをお勧めします。これは賢明な方法ですか?



回答:


262

ES6対応環境(node.js、必要なES6機能を備えた特定のブラウザー、または環境に合わせてES6コードをトランスパイルするなど)でプログラミングしている場合は、ES6にSet組み込まれているオブジェクトを使用できます。非常に優れた機能を備えており、ご使用の環境でそのまま使用できます。


ES5環境での多くの単純なことに対して、オブジェクトの使用は非常にうまく機能します。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
}

constructorプロパティのような基本オブジェクトの組み込みメソッドやプロパティ間で潜在的な競合があるため。


ES6のサイドバー:ECMAScript 6の現在の作業バージョンまたはES 2015と呼ばれるものには、組み込みのSetオブジェクトがあります。現在、一部のブラウザーで実装されています。ブラウザの可用性は時間の経過とともに変化するためSetこのES6互換性テーブルの行を見て、ブラウザの可用性の現在のステータスを確認できます。

組み込みのSetオブジェクトの利点の1つは、オブジェクトのようにすべてのキーを文字列に強制変換しないため、5と "5"を別々のキーとして使用できることです。また、文字列を変換せずに、セットで直接オブジェクトを使用することもできます。ここだ、物品の機能とのいくつかについて説明しMDNのマニュアルセットオブジェクトには。

ES6セットオブジェクトのポリフィルを作成したので、今すぐそれを使い始めることができます。ブラウザがサポートしている場合は、組み込みのセットオブジェクトを自動的に遅延させます。これには、IE7まで機能するES6互換コードを作成するという利点があります。しかし、いくつかの欠点があります。ES6セットインターフェースはES6イテレータを利用しているため、ユーザーは次のようなことができfor (item of mySet)、自動的にセットを反復処理します。ただし、このタイプの言語機能は、ポリフィルを介して実装することはできません。新しいES6言語機能を使用せずにES6セットを繰り返し処理することもできますが、率直に言って、新しい言語機能がないと、以下に示す他のセットインターフェイスほど便利ではありません。

両方を見て、どちらが最適かを判断できます。ES6セットのポリフィルはhttps://github.com/jfriend00/ES6-Setにあります

ちなみに、私自身のテストで、Firefox v29 Setの実装が仕様の現在のドラフトでは完全に最新ではないことに気づきました。たとえば.add()、仕様の説明や私のポリフィルサポートのようにメソッド呼び出しをチェーンすることはできません。まだ確定されていないため、これはおそらく仕様の進行中の問題です。


事前に作成されたセットオブジェクト:任意のブラウザで使用できるセットを操作するためのメソッドが既に作成されているオブジェクトが必要な場合は、さまざまなタイプのセットを実装する一連のさまざまな事前に作成されたオブジェクトを使用できます。セットオブジェクトの基本を実装する小さなコードであるminiSetがあります。また、機能が豊富なセットオブジェクトと、ディクショナリ(各キーの値を保存/取得しましょう)やオブジェクトセット(オブジェクトのセット(JSオブジェクトまたはDOMオブジェクトを提供するオブジェクト)それぞれに一意のキーを生成する関数またはObjectSetがキーを生成します)。

これが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;

16
これは問題を解決しますが、明確にするために、この実装整数または文字列以外のもののセットに対しては機能しません
mkirk '13

3
@mkirk-はい、セットでインデックスを作成するアイテムには、インデックスキーになる文字列表現が必要です(たとえば、文字列であるか、アイテムを一意に記述するtoString()メソッドを持っている)。
jfriend00

4
リスト内のアイテムを取得するには、を使用できますObject.keys(obj)
Blixt

3
@Blixt- Object.keys()IE9、FF4、Safari 5、Opera 12以降が必要です。ここには、古いブラウザ用のポリフィルがあります
jfriend00

1
obj.hasOwnProperty(prop)メンバーシップチェックには使用しないでください。Object.prototype.hasOwnProperty.call(obj, prop)代わりに使用してください。これは、「セット」に値が含まれている場合でも機能します"hasOwnProperty"
davidchambers 2013

72

あなたはのようなプロパティなしでオブジェクトを作成することができます

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

いいですが、「hasOwnPropertyを使用する必要がなくなる」とあなたが言う理由は
わかりません

13
使用するだけでset = {}オブジェクト(例toStringhasOwnPropertyif (A in set)
:)

6
完全に空のオブジェクトを作成できることを知りませんでした。ありがとう、あなたの解決策は非常にエレガントです。
blueFast 2014

1
興味深いですが、これの欠点は、set[A]=trueイニシャライザを1つだけではなく、追加したい要素ごとにステートメントを用意する必要があることです。
vogomatix 2014年

1
あなたは既に存在しているセットでセットを初期化を参照している場合はわからないあなたが何を意味するか、しかし、あなたはの線に沿って何かを行うことができますs = Object.create(null);s["thorben"] = true;ss = Object.create(s)
ThorbenCroisé

23

ECMAScript 6以降、Setデータ構造は組み込みの機能です。node.jsバージョンとの互換性はここにあります


4
こんにちは、わかりやすくするために、2014年になりましたが、Chromeではまだ実験段階ですか そうでない場合は、回答を編集していただけませんか。ありがとう
カレルビレク2014

1
はい、Chromeではまだ実験段階です。ECMAScriptが「公式」にリリースされることになっている2014年末までに、ECMAScriptはサポートされると思います。その後、それに応じて回答を更新します。
hymloth、2014

答えてくれてありがとう!(JavaScriptの回答はかなり迅速に古います。)
カレルは、Bilek

1
@Val inは、Setオブジェクトがその要素をプロパティとして持たないために機能しません。これは、セットが任意のタイプの要素を持つことができるが、プロパティは文字列であるため、悪いことです。使用できますhasSet([1,2]).has(1)
Oriol 2014

1
サルバドールダリの答えはより包括的で最新のものです。
Dan Dascalescu 2016

14

JavascriptののES6のバージョンでは、のためにタイプが組み込まれていセットお使いのブラウザでチェック互換性)。

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

するために要素を追加あなたは、単に使用セットに.add()して実行され、O(1)いずれか(それが存在しない場合)セットに要素を追加したり、それがすでにある場合は、何もしません。そこで任意のタイプの要素(配列、文字列、数値)を追加できます

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

セット内の要素の数確認するには、単にを使用できます.size。また走るO(1)

numbers.size; // 4

セットから要素削除するには、を使用します.delete()。値が存在する(および削除された)場合はtrueを返し、値が存在しない場合はfalseを返します。でも実行されO(1)ます。

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

要素がセット内に存在するかどうか確認するには、を使用します.has()。これは、要素がセット内にある場合はtrueを返し、そうでない場合はfalseを返します。でも実行されO(1)ます。

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

必要なメソッドに加えて、追加のメソッドはほとんどありません。

  • numbers.clear(); セットからすべての要素を削除するだけです
  • numbers.forEach(callback); セットの値を挿入順に反復する
  • numbers.entries(); すべての値のイテレータを作成します
  • numbers.keys(); と同じセットのキーを返します numbers.values()

オブジェクトタイプの値のみを追加できるWeaksetもあります。


.add()O(1)でrun への参照をポイントできますか?私はこれに興味をそそられます
Green

10

現在、数値と文字列で非常にうまく機能するセットの実装を開始しました。私の主な焦点は差分演算だったので、できる限り効率的にするようにしました。フォークとコードレビューは大歓迎です!

https://github.com/mcrisc/SetJS


このクラスはすごいです!CouchDBのmap / reduce関数内にJavaScriptを記述していなかった場合は、これを完全に使用します。
portforwardpodcast 2012

9

d3.jsライブラリにセット、マップ、その他のデータ構造の実装があることに気づきました。私はそれらの効率について議論することはできませんが、人気のあるライブラリであるという事実から判断すると、必要なものでなければなりません。

ドキュメントはこちら

便宜上、リンクからコピーします(最初の3つの機能が対象です)


  • d3.set([配列])

新しいセットを構築します。配列が指定されている場合、返されたセットに文字列値の指定された配列を追加します。

  • set.has(値)

このセットに指定された値の文字列のエントリがある場合にのみtrueを返します。

  • set.add(value)

指定された値の文字列をこのセットに追加します。

  • set.remove(値)

セットに指定された値の文字列が含まれている場合、それを削除してtrueを返します。それ以外の場合、このメソッドは何もせず、falseを返します。

  • set.values()

このセットの文字列値の配列を返します。戻り値の順序は任意です。文字列のセットの一意の値を計算する便利な方法として使用できます。例えば:

d3.set(["foo"、 "bar"、 "foo"、 "baz"])。values(); // "foo"、 "bar"、 "baz"

  • set.forEach(関数)

このセットの各値に対して指定された関数を呼び出し、値を引数として渡します。関数のthisコンテキストはこのセットです。未定義を返します。反復順序は任意です。

  • set.empty()

このセットの値がゼロの場合にのみtrueを返します。

  • set.size()

このセットの値の数を返します。


4

はい、それは賢明な方法です-つまり、すべてのオブジェクトが(このユースケースでは)、直接アクセスできる一連のキー/値です。

追加する前に、既に存在するかどうかを確認する必要があります。または、存在を示す必要があるだけの場合は、再度「追加」しても実際には何も変更されず、オブジェクトに再度設定されます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.