Есть ли хорошая реализация хэша JavaScript (кода / таблицы)? - PullRequest
6 голосов
/ 22 октября 2008

Да, я знаю, что вы можете использовать обычные объекты в качестве ассоциативных массивов в JavaScript, но я бы хотел использовать что-то ближе к реализации Java в Map (HashMap, LinkedHashMap и т. Д.). Нечто, что может иметь любые данные, используемые в качестве ключа. Есть ли какой-нибудь хороший хэш (код / ​​таблица) в реализации JavaScript там?

Ответы [ 4 ]

23 голосов
/ 22 октября 2008

В javascript объекты - это буквально хеш-реализация . Java HashMap будет немного подделкой, поэтому я бы предложил вам переосмыслить ваши потребности.

Прямой ответ - не , я не верю, что в Java есть отличная реализация HashMap Java. Если он есть, он обязательно должен быть частью библиотеки, которую вы можете или не хотите использовать, и вам, безусловно, не нужно включать библиотеку , чтобы иметь небольшую хеш-таблицу.

Итак, давайте продолжим и напишем один, просто чтобы изучить проблему . Вы можете использовать его, если хотите. Мы просто начнем с написания конструктора и добавим Array, который является Object, но имеет несколько полезных методов, которые не позволят этому примеру стать слишком утомительным:

function HashMap () {
    var obj = [];
    return obj;
}

var myHashMap = HashMap();

Мы добавим некоторые методы прямо из мира Java, но по мере их перевода будем переходить на javascript ...

function HashMap() {
    var obj = [];
    obj.size = function () {
        return this.length;
    };
    obj.isEmpty = function () {
        return this.length === 0;
    };
    obj.containsKey = function (key) {
        for (var i = 0; i < this.length; i++) {
            if (this[i].key === key) {
                return i;
            }
        }
        return -1;
    };
    obj.get = function (key) {
        var index = this.containsKey(key);
        if (index > -1) {
            return this[index].value;
        }
    };
    obj.put = function (key, value) {
        if (this.containsKey(key) !== -1) {
            return this.get(key);
        }
        this.push({'key': key, 'value': value});
    };
    obj.clear = function () {
        this = null;  // Just kidding...
    };
    return obj;
}

Мы могли бы продолжать строить это, но я думаю, что это неправильный подход. В конце концов, мы в конечном итоге используем то, что javascript предоставляет за кулисами, потому что у нас просто нет типа HashMap. В процессе , притворяясь , он поддается всем видам дополнительной работы .

Немного иронично, что одна из вещей, которая делает javascript таким интересным и разнообразным языком, - это легкость, с которой он справляется с этим видом борьбы . Мы можем буквально делать все, что захотим, и быстрый пример здесь ничего не делает, если он не иллюстрирует обманчивую силу языка. Тем не менее, учитывая эту силу, кажется, лучше не использовать .

Я просто думаю, что javascript хочет быть легче. Моя личная рекомендация состоит в том, чтобы вы повторно изучили проблему, прежде чем пытаться реализовать правильный Java HashMap. Javascript не хочет и не предоставляет для одного .

Запомните нативную альтернативу :

var map = [{}, 'string', 4, {}];

.. так быстро и легко по сравнению.

С другой стороны, я не верю, что здесь есть какие-то жесткие ответы. Эта реализация действительно может быть вполне приемлемым решением . Если вы чувствуете, что можете его использовать, я бы сказал, поверните его . Но я бы никогда не использовал его, если бы чувствовал, что у нас есть достаточно простых и более естественных средств в нашем распоряжении ... что я почти уверен, что мы делаем.

Sidenote: Эффективность связана со стилем? Обратите внимание, что производительность снизилась .. есть большая буква O, смотрящая нам в лицо на HashMap.put () ... Неоптимальная производительность, вероятно, не является здесь пробкой, и вы ' Вероятно, мне нужно сделать что-то очень амбициозное или иметь большой набор данных, прежде чем вы даже заметите снижение производительности современного браузера. Просто интересно отметить, что операции, как правило, становятся менее эффективными, когда вы работаете с зерном, почти как если бы на работе была естественная энтропия. Javascript - это язык высокого уровня, и он должен предлагать эффективные решения, когда мы соблюдаем его соглашения, так же как HashMap в Java будет гораздо более естественным и высокопроизводительным выбором.

7 голосов
/ 15 мая 2009

Я выпустил отдельную реализацию хэш-таблицы JavaScript, которая выходит за рамки перечисленных здесь.

http://www.timdown.co.uk/jshashtable/

1 голос
/ 22 октября 2008

Обратите внимание, что коллекции Java, использующие в качестве ключа «любой объект», не совсем верны. Да, вы можете использовать любой объект, но если этот объект не имеет хороших реализаций hashcode () и equals (), он не будет работать хорошо. Базовый класс Object имеет реализацию по умолчанию для них, но для того, чтобы пользовательские классы работали (эффективно) в качестве ключей хеш-таблицы, их необходимо переопределить. У Javascript нет эквивалента (который я знаю).

Чтобы создать хеш-таблицу в javascript, которая может (эффективно) использовать произвольные объекты в качестве ключа, вам необходимо применить что-то похожее к объектам, которые вы используете, по крайней мере, если вы хотите сохранить прирост производительности хеш-таблицы. Если вы можете применить метод hashcode (), который возвращает String, тогда вы можете просто использовать Object под капотом в качестве фактической хеш-таблицы.

В противном случае вам потребуется что-то вроде других опубликованных решений, которые на данный момент не работают как хеш-таблицы. Они оба выполняют O (n) поиск по списку, чтобы попытаться найти ключ, что в значительной степени противоречит цели хеш-таблицы (хеш-таблицы обычно имеют постоянное время для получения / ввода).

0 голосов
/ 22 октября 2008

Вот наивная реализация, которую я только что собрал - как упомянул Кепаро в комментарии, одной из больших проблем является проверка на равенство:

var ObjectMap = function()
{
    this._keys = [];
    this._values = [];
};

ObjectMap.prototype.clear = function()
{
    this._keys = [];
    this._values = [];
};

ObjectMap.prototype.get = function(key)
{
    var index = this._indexOf(key, this._keys);
    if (index != -1)
    {
        return this._values[index];
    }
    return undefined;
};

ObjectMap.prototype.hasKey = function(key)
{
    return (this._indexOf(key, this._keys) != -1);
};

ObjectMap.prototype.hasValue = function(value)
{
    return (this._indexOf(value, this._values) != -1);
};

ObjectMap.prototype.put = function(key, value)
{
    var index = this._indexOf(key, this._keys);
    if (index == -1)
    {
        index = this._keys.length;
    }

    this._keys[index] = key;
    this._values[index] = value;
};

ObjectMap.prototype.remove = function(key)
{
    var index = this._indexOf(key, this._keys);
    if (index != -1)
    {
        this._keys.splice(index, 1);
        this._values.splice(index, 1);
    }
};

ObjectMap.prototype.size = function()
{
    return this._keys.length;
};

ObjectMap.prototype._indexOf = function(item, list)
{
    for (var i = 0, l = list.length; i < l; i++)
    {
        if (this._equals(list[i], item))
        {
            return i;
        }
    }
    return -1;
};

ObjectMap.prototype._equals = function(a, b)
{
    if (a === b)
    {
        return true;
    }

    // Custom objects can implement an equals method
    if (typeof a.equals == "function" &&
        typeof b.equals == "function")
    {
        return a.equals(b);
    }

    // Arrays are equal if they're the same length and their contents are equal
    if (a instanceof Array && b instanceof Array)
    {
        if (a.length != b.length)
        {
            return false;
        }

        for (var i = 0, l = a.length; i < l; i++)
        {
            if (!this._equals(a[i], b[i]))
            {
                return false;
            }
        }

        return true;
    }

    // Checking object properties - objects are equal if they have all the same
    // properties and they're all equal.
    var seenProperties = {};
    for (var prop in a)
    {
        if (a.hasOwnProperty(prop))
        {
            if (!b.hasOwnProperty(prop))
            {
                return false;
            }

            if (!this._equals(a[prop], b[prop]))
            {
                return false;
            }

            seenProperties[prop] = true;
        }
    }

    for (var prop in b)
    {
        if (!(prop in seenProperties) && b.hasOwnProperty(prop))
        {
            if (!a.hasOwnProperty(prop))
            {
                return false;
            }

            if (!this._equals(b[prop], a[prop]))
            {
                return false;
            }
        }
    }

    return true;
};

Пример использования:

>>> var map = new ObjectMap();
>>> var o = {a: 1, b: [1,2], c: true};
>>> map.put(o, "buns");
>>> map.get(o)
"buns"
>>> map.get({a: 1, b: [1,2], c: true});
"buns"
>>> map.get({a: 1, b: [1,2], c: true, d:"hi"});
>>> var a = [1,2,3];
>>> map.put(a, "cheese");
>>> map.get(a);
"cheese"
>>> map.get([1,2,3]);
"cheese"
>>> map.get([1,2,3,4]);
>>> var d = new Date();
>>> map.put(d, "toast");
>>> map.get(d);
"toast"
>>> map.get(new Date(d.valueOf()));
"toast"

Это ни в коем случае не полная реализация, просто указатель на способ реализации такого объекта. Например, взглянув на то, что я дал, вам также необходимо добавить проверки свойств конструктора перед проверкой свойства объекта, так как в настоящее время это работает:

>>> function TestObject(a) { this.a = a; };
>>> var t = new TestObject("sandwich");
>>> map.put(t, "butter");
>>> map.get({a: "sandwich"})
"butter"
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...