1. ホーム
  2. ジャバスクリプト

[解決済み】JavaScriptでセットを模倣する?

2022-04-21 13:56:49

質問

私はJavaScriptで仕事をしています。のリストを保存したいのですが ユニーク を持つ、順序付けされない文字列値です。

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

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

これは の質問では、オブジェクトを使用することを推奨しています。 キーはプロパティを格納し、値はすべてtrueに設定されています。

どのように解決するのですか?

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


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 やES2015と呼ばれるものには 組み込みのSetオブジェクト . 現在、一部のブラウザで実装されています。 ブラウザの可用性は時間とともに変化するため、以下の行を見てください。 Set このES6互換性表 をクリックすると、ブラウザの利用可能性に関する現在のステータスが表示されます。

組み込みのSetオブジェクトの利点は、Objectのようにすべてのキーを文字列に強制しないので、5と"5"の両方を別々のキーとして持つことができる点です。 また、文字列の変換を行わずにセット内で直接Objectを使用することもできます。 以下はその例です。 記事 について説明しています。 MDNのドキュメント を設定します。

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

どちらを選ぶかは、両方を見てから決めてください。 ES6 set polyfillはこちらです。 https://github.com/jfriend00/ES6-Set .

参考までに、私自身のテストでは、Firefox v29 の Set の実装は、現在の仕様のドラフトに完全に対応していないことに気づきました。 たとえば .add() メソッド呼び出しは、仕様で記述され、私のポリフィルがサポートしています。 これはおそらく、まだ確定していないため、仕様が動いている問題だと思います。


プリビルドセットオブジェクト。 どのブラウザでも使える、集合を操作するメソッドを持つ、すでに構築されたオブジェクトが欲しい場合、異なるタイプの集合を実装する一連の異なる構築済みオブジェクトを使用することができます。 セットオブジェクトの基本を実装した小さなコードであるminiSetがあります。 また、より機能豊富なセットオブジェクトや、Dictionary(各キーの値を保存/取得可能)やObjectSet(JSオブジェクトまたはDOMオブジェクトのセットを保持し、各オブジェクトに固有のキーを生成する関数を提供するか、ObjectSetがキーを生成します)などの派生型があります。

以下は、ミニセットのコードのコピーです(最新のコードは 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;