Форум программистов, компьютерный форум, киберфорум
JavaScript для начинающих
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.73/15: Рейтинг темы: голосов - 15, средняя оценка - 4.73
 Аватар для damix
53 / 47 / 22
Регистрация: 04.11.2013
Сообщений: 404
Записей в блоге: 2

Map - Объекты в качестве ключей

28.02.2021, 23:28. Показов 3645. Ответов 16

Студворк — интернет-сервис помощи студентам
Пример отсюда
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let john = { name: "John" };
 
// давайте сохраним количество посещений для каждого пользователя
let visitsCountMap = new Map();
 
// объект john - это ключ для значения в объекте Map
visitsCountMap.set(john, 123);
 
alert(visitsCountMap.get(john)); // 123
 
// Но если добавить
 
let currentUser = { name: "John" };
 
visitsCountMap.set(currentUser, 124);
 
alert(visitsCountMap.get(john)); // 123, а ожидаю получить 124
Map, видимо, объекты сравнивает по ссылке.
If x and y are the same Object value, return true. Otherwise, return false.
https://tc39.es/ecma262/#sec-samevaluezero

А как принято в Javascript делать ассоциативный массив, у которого ключами являются объекты?
0
Лучшие ответы (1)
Programming
Эксперт
39485 / 9562 / 3019
Регистрация: 12.04.2006
Сообщений: 41,671
Блог
28.02.2021, 23:28
Ответы с готовыми решениями:

Массив, где в качестве ключей названия стран, а в качестве значений – массивы с городами
Обращение к опытным php программистам. Вывел в цикле значения массива: $capitals = array( 'Россия' => array('Москва',...

Возможно ли создать контейнер std::map, в котором в качестве значения была бы ссылка на std::map?
Здравствуйте. Возможно ли создать контейнер std::map, в котором в качестве значения была бы ссылка на std map? Например: std::map...

Порядок ключей в Map
Как сделать чтобы порядок записей был такой же как и при записи. А то он их видимо сортирует по хэшу или еще как-то и они перемешиваются....

16
22 / 15 / 8
Регистрация: 20.02.2019
Сообщений: 128
28.02.2021, 23:57
Хм. всегда ассоциативный массив понимал как: массив, в котором в качестве ключей применяются строки. Где хранятся пара: ключ, значение. Ну и в этом массиве значение завязано с ключом, и доступ к этому значению производится по имени ключа.
0
Эксперт JS
 Аватар для DrType
6553 / 3624 / 1075
Регистрация: 07.09.2019
Сообщений: 5,877
Записей в блоге: 1
28.02.2021, 23:57
В этом и состоит особенность структуры данных Map, что в качестве ключей могут выступать именно объекты (а john и currentUser, как мы понимаем, суть различные объекты). Если бы мы использовали в качестве хранилища обычный объект JS, у которого в качестве ключей могут выступать только строки, то всё было бы иначе:
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
let john = { name: "John" };
// давайте сохраним количество посещений для каждого пользователя
let visitsCountMap = {};
// строковое значение, к которому приводится объект john - это ключ для значения в объекте
visitsCountMap[john] = 123;
console.log(visitsCountMap[john]); // 123
// Но если добавить
let currentUser = { name: "John" };
visitsCountMap[currentUser] = 124;
console.log(visitsCountMap[john]); // 124
for(let key in visitsCountMap){
  console.log(typeof key) //string
}
1
22 / 15 / 8
Регистрация: 20.02.2019
Сообщений: 128
01.03.2021, 00:11
Цитата Сообщение от DrType Посмотреть сообщение
В этом и состоит особенность структуры данных Map, что в качестве ключей могут выступать именно объекты
Только не пойму одну вещь: допустим чтобы получить какой-либо объект из Map, логика построенная внутри него должна сравнить значение всего объекта выступающего ключом в коллекции ну и собственно передаваемый объект допустим в том же методе "get"? Так как этот объект в теории преобразован в строку методом на подобии "JSON.stringify()"? Ну и теперь мы можем сравнивать его как строку получается...
0
 Аватар для Tavashi
1172 / 762 / 194
Регистрация: 21.05.2016
Сообщений: 1,858
01.03.2021, 00:52
Цитата Сообщение от Demolition_Man Посмотреть сообщение
объекта выступающего ключом в коллекции
Для этого нужно переопределять метод toString() для объекта используемого в качестве ключа.

Добавлено через 5 минут
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Human (name, sex) {
  this.name = name;
  this.sex = sex;
}
 
Human.prototype.toString = function () {
  return this.name + this.sex;
};
 
let visitsCountMap = new Map();
let john = new Human("john", "male");
let jenn = new Human("jenn", "female");
 
visitsCountMap.set(john, 18);
visitsCountMap.set(jenn, 17);
 
console.log((visitsCountMap.get(john)));
console.log((visitsCountMap.get(jenn)));
Добавлено через 20 минут
Цитата Сообщение от damix Посмотреть сообщение
// 123, а ожидаю получить 124
Может я не совсем вопрос понял, но почему вы ожидаете 124? У нас есть два объекта john и currentUser, которые используются в качестве ключа. То есть, обратившись к разным объектам (ключам) получаем разные значения, обратившись к одинаковым ключам - одинаковы значения. Другими словами, в вашем примере:
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let john = { name: "John" };
 
// давайте сохраним количество посещений для каждого пользователя
let visitsCountMap = new Map();
 
// объект john - это ключ для значения в объекте Map
visitsCountMap.set(john, 123);
 
alert(visitsCountMap.get(john)); // 123
 
// Но если добавить
 
let currentUser = { name: "John" };
 
visitsCountMap.set(currentUser, 124);
 
alert(visitsCountMap.get(john)); // 123, как и ожидалось для объекта john
alert(visitsCountMap.get(currentUser)); // 124, как и ожидалось для объекта currentUser
2
22 / 15 / 8
Регистрация: 20.02.2019
Сообщений: 128
01.03.2021, 00:52
А почему не так?
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Func() {
  return function(name, sex) {
    this.name = name;
    this.sex = sex;
  }
}
 
const Human = Func()
 
let visitsCountMap = new Map();
let john = new Human("john", "male");
let jenn = new Human("jenn", "female");
 
visitsCountMap.set(john, 18);
visitsCountMap.set(jenn, 17);
 
console.log((visitsCountMap.get(john)));
console.log((visitsCountMap.get(jenn)));
0
 Аватар для Tavashi
1172 / 762 / 194
Регистрация: 21.05.2016
Сообщений: 1,858
01.03.2021, 00:55
Вот если это:
JavaScript
1
let currentUser = { name: "John" }
Поменять на это:
JavaScript
1
let currentUser = john;
Тогда да, здесь надо ожидать 124:
JavaScript
1
alert(visitsCountMap.get(john)); // 124
0
 Аватар для damix
53 / 47 / 22
Регистрация: 04.11.2013
Сообщений: 404
Записей в блоге: 2
01.03.2021, 14:53  [ТС]
Цитата Сообщение от Tavashi Посмотреть сообщение
Может я не совсем вопрос понял, но почему вы ожидаете 124? У нас есть два объекта john и currentUser, которые используются в качестве ключа. То есть, обратившись к разным объектам (ключам) получаем разные значения, обратившись к одинаковым ключам - одинаковы значения.
Потому что эти объекты одинаковые по значениям. Вопрос в том, как правильно делать что-то такое что может быстро вернуть по ключу значение, где этот ключ является чем-то таким, состоящим из нескольких примитивов.

Добавлено через 2 часа 7 минут
DrType, Tavashi, т.е. нормальный обход - приводить объекты к примитивам (чаще всего к строкам)?

Добавлено через 9 минут
Цитата Сообщение от Tavashi Посмотреть сообщение
Для этого нужно переопределять метод toString() для объекта используемого в качестве ключа.
Ваш пример не будет работать.
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Human (name, sex) {
      this.name = name;
      this.sex = sex;
    }
     
    Human.prototype.toString = function () {
      return this.name + this.sex;
    };
     
    let visitsCountMap = new Map();
    let john = new Human("john", "male");
    let jenn = new Human("jenn", "female");
     
    visitsCountMap.set(john, 18);
    visitsCountMap.set(jenn, 17);
     
    console.log((visitsCountMap.get(john)));
    console.log((visitsCountMap.get(jenn)));
    
    let testUser = new Human("john", "male");
    visitsCountMap.set(testUser, 19);
    
    console.log(visitsCountMap.get(john)); // 18, а надо 19
А вот так будет
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    function Human (name, sex) {
      this.name = name;
      this.sex = sex;
    }
     
    Human.prototype.toString = function () {
      return this.name + this.sex;
    };
     
    let visitsCountMap = new Map();
    let john = new Human("john", "male");
    let jenn = new Human("jenn", "female");
     
    visitsCountMap.set(String(john), 18);
    visitsCountMap.set(String(jenn), 17);
     
    console.log((visitsCountMap.get(String(john))));
    console.log((visitsCountMap.get(String(jenn))));
    
    let testUser = new Human("john", "male");
    visitsCountMap.set(String(testUser), 19);
    
    console.log(visitsCountMap.get(String(john))); // 19
Добавлено через 8 минут
DrType, в вашем примере все ключи будут считаться одинаковыми, потому что
JavaScript
1
2
3
    for(let key in visitsCountMap){
      console.log(key) // [object Object]
    }
1
Эксперт JS
 Аватар для DrType
6553 / 3624 / 1075
Регистрация: 07.09.2019
Сообщений: 5,877
Записей в блоге: 1
01.03.2021, 14:59
Цитата Сообщение от damix Посмотреть сообщение
DrType, в вашем примере все ключи будут считаться одинаковыми, потому что
JavaScript
1
2
3
    for(let key in visitsCountMap){
      console.log(key) // [object Object]
    }
Вы совершенно правы, тоже понял это. Плохой пример.
Как вариант — использовать JSON.stringify():
JavaScript
1
visitsCountMap[JSON.stringify(john)] = 123;
Тогда ключи будут идентичны, если преобразуемые объекты эквивалентны по своим свойствам.
1
Эксперт JS
6496 / 3907 / 2006
Регистрация: 14.06.2018
Сообщений: 6,781
01.03.2021, 18:26
Лучший ответ Сообщение было отмечено damix как решение

Решение

damix, хорошая возможность поизучать HashMap в разделе "Алгоритмы и структуры данных".
В C# было всё коряво с изучением, потому что там учебный материал обычно рассчитан на сачков.

А в JS только незамутненная задача с нуля, только хардкор.
скрипт на TypeScript
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
interface IEqualityComparer<T> {
    equals(v1: T, v2: T): boolean;
    getHashCode?(v: T): number;
}
interface IMap<TKey, TValue> {
    // Свойства
    size: number;
    // Методы
    clear(): void;
    delete(key: TKey): boolean;
    get(key: TKey): TValue | undefined;
    has(key: TKey): boolean;
    set(key: TKey, value: TValue): IMap<TKey, TValue>;
    // Методы-итераторы
    [Symbol.iterator](): Generator<[TKey, TValue], void, unknown>;
    forEach(callbackFn: (currentValue?: TValue, currentKey?: TKey, set?: HashMap<TKey, TValue>) => void, thisArg?: any): void;
    keys(): Generator<TKey, void, unknown>;
    values(): Generator<TValue, void, unknown>;
}
/**
 * Слот в классе Map.
 * Видимость должна быть в пределах модуля.
 */
class MapSlot<TKey, TValue>
{
    hashCode!: number;
    key!: TKey;
    value!: TValue;
    next!: number;
    constructor(hashCode: number, key: TKey, value: TValue, next: number) {
        this.hashCode = hashCode;
        this.key = key;
        this.value = value;
        this.next = next;
    }
}
 
class HashMap<TKey, TValue> implements IMap<TKey, TValue> {
    buckets!: number[];
    slots!: MapSlot<TKey, TValue>[];
    count!: number; // Пока количество элементов равно нулю
    // freeList!: number;
    comparer!: IEqualityComparer<TKey>;
 
    constructor();
    constructor(iterable: Iterable<[TKey, TValue]>);
    constructor(comparer: IEqualityComparer<TKey>);
    constructor(iterable: Iterable<[TKey, TValue]>, comparer: IEqualityComparer<TKey>);
    constructor(arg1?: any, arg2?: any) {
        this.clear(arg1, arg2);
    }
 
    /**
     * Удалить все элементы коллекции.
     * Компаратор выставить по умолчанию или из параметра.
     * (В качестве расширенной возможности можно заново загрузить Iterable)
     */
    clear(): void;
    clear(iterable: Iterable<[TKey, TValue]>): void;
    clear(comparer: IEqualityComparer<TKey>): void;
    clear(iterable: Iterable<[TKey, TValue]>, comparer: IEqualityComparer<TKey>): void;
    clear(arg1?: any, arg2?: any): void {
        let iterable: Iterable<[TKey, TValue]> | undefined;
        let comparer: IEqualityComparer<TKey> | undefined;
 
        if (arg1 && Symbol.iterator in arg1) {
            iterable = arg1;
            if (arg2 && "equals" in arg2)
                comparer = arg2;
        }
        else if (arg1 && "equals" in arg1)
            comparer = arg1;
 
        if (comparer == null) comparer = {
            getHashCode: HashMap.internalGetHashCode,
            equals(v1: any, v2: any) { return v1 === v2 || (v1 !== v1 && v2 !== v2); } // NaN !== NaN
        };
        if (!("getHashCode" in comparer)) comparer.getHashCode = HashMap.internalGetHashCode;
        this.comparer = comparer;
        this.buckets = new Array<number>(7);
        this.buckets.fill(0); // В JS элементы массива автоматически не создаются 
        this.slots = new Array<MapSlot<TKey, TValue>>(7);
        this.count = 0;
 
        if (iterable) {
            for (let e of iterable) {
                this.set(e[0], e[1]);
            }
        }
    }
    /**
     * Количество элементов коллекции.
     */
    get size() {
        let count = 0;
        for (let i = 0; i < this.count; i++) {
            if (this.slots[i].hashCode !== -1) ++count;
        }
        return count;
    }
    /**
     * Если элемент в коллекции, удалить его и вернуть true, иначе вернуть false.
     * @param {TElement} value Элемент, который нужно удалить
     * @returns {boolean} Был ли удаляемый элемент
     */
    delete(key: TKey): boolean {
        let hashCode = (<(v: TKey) => number>this.comparer.getHashCode)(key);
        let bucket = hashCode % this.buckets.length;
        let last = -1;
        for (let i = this.buckets[bucket] - 1; i >= 0; last = i, i = this.slots[i].next) {
            if (this.slots[i].hashCode === hashCode && this.comparer.equals(this.slots[i].key, key)) {
                if (last < 0) {
                    this.buckets[bucket] = this.slots[i].next + 1;
                }
                else {
                    this.slots[last].next = this.slots[i].next;
                }
                this.slots[i].hashCode = -1;
 
                return true;
            }
        }
        return false;
    }
 
    /**
     * Вернуть определенный элемент коллекции по ключу. 
     * @param {TKey} key Ключ, по которому осуществляется поиск элемента
     * @returns {TValue|undefined} Найденный элемент или undefined
     */
    get(key: TKey): TValue | undefined {
        let hashCode = (<(v: TKey) => number>this.comparer.getHashCode)(key);
 
        for (let i = this.buckets[hashCode % this.buckets.length] - 1; i >= 0; i = this.slots[i].next) {
            if (this.slots[i].hashCode === hashCode && this.comparer.equals(this.slots[i].key, key))
                return this.slots[i].value;
        }
        return undefined;
    }
    /**
     * Определить, содержится ли элемент в коллекции. 
     * @param {TElement} value Проверяемый элемент
     * @returns {boolean}
     */
    has(key: TKey): boolean {
        let hashCode = (<(v: TKey) => number>this.comparer.getHashCode)(key);
 
        for (let i = this.buckets[hashCode % this.buckets.length] - 1; i >= 0; i = this.slots[i].next) {
            if (this.slots[i].hashCode === hashCode && this.comparer.equals(this.slots[i].key, key)) return true;
        }
        return false;
    }
    /**
     * Добавить или обновить элемент коллекции по ключу.
     * @param {TKey} key Ключ добавляемого элемента.
     * @param {TKey} value Значение добавляемого элемента.
     * @returns {IMap<TKey, TValue>} Свой объект коллекции.
     */
    set(key: TKey, value: TValue): IMap<TKey, TValue> {
        let hashCode = (<(v: TKey) => number>this.comparer.getHashCode)(key);
 
        for (let i = this.buckets[hashCode % this.buckets.length] - 1; i >= 0; i = this.slots[i].next) {
            if (this.slots[i].hashCode === hashCode && this.comparer.equals(this.slots[i].key, key)) {
                this.slots[i].value = value;
                return this;
            }
        }
        // Если элемента по ключу раньше не было
        let index: number;
 
        if (this.count == this.slots.length) this.resize();
        index = this.count;
        this.count++;
 
        let bucket = hashCode % this.buckets.length;
        this.slots[index] = new MapSlot(hashCode, key, <TValue>value, this.buckets[bucket] - 1);
 
        this.buckets[bucket] = index + 1;
        // console.log(this.buckets);
        // console.log(this.slots);
        return this;
    }
 
    // Методы-итераторы
 
    [Symbol.iterator](): Generator<[TKey, TValue], void, unknown> {
        return <Generator<[TKey, TValue], void, unknown>>this.entries();
    }
    /**
     * Возвращает новый объект Iterator, который содержит массив [key, value] 
     * для каждого элемента в объекте HashMap в порядке вставки.
     */
    * entries(): Generator<(TKey | TValue)[], void, unknown> {
        for (let i = 0; i < this.count; i++) {
            let e = this.slots[i];
            if (e.hashCode !== -1) yield [e.key, e.value];
        }
    }
    /**
     * Выполнить функцию callbackFn для каждого элемента коллекции.
     * @param callbackFn {(currentValue?: TValue, currentKey?: TKey, set?: HashMap<TKey, TValue>) => void} Выполняемая функция
     * @param thisArg {any} Аргумент this
     */
    forEach(callbackFn: (currentValue?: TValue, currentKey?: TKey, set?: HashMap<TKey, TValue>) => void, thisArg?: any): void {
        for (let i = 0; i < this.count; i++) {
            let e = this.slots[i];
            if (e.hashCode !== -1) callbackFn.call(thisArg, e.value, e.key, this);
        }
    }
    /**
     * Вернуть новый Iterator для ключей коллекции.
     */
    * keys(): Generator<TKey, void, unknown> {
        for (let i = 0; i < this.count; i++) {
            let e = this.slots[i];
            if (e.hashCode !== -1) yield e.key;
        }
    }
    /**
     * Вернуть новый Iterator для значений коллекции.
     */
    * values(): Generator<TValue, void, unknown> {
        for (let i = 0; i < this.count; i++) {
            let e = this.slots[i];
            if (e.hashCode !== -1) yield e.value;
        }
    }
 
    // Приватные методы
    private static internalGetHashCode<TKey>(value: TKey): number {
        if (!value) return 0; // == null, undefined, "", 0
 
        function adler32(buf: number[]) {
            let count = buf.length;
            let s1 = 1;
            let s2 = 0;
            for (let i = 0; i < count; ++i) {
                s1 = (s1 + buf[i]) % 65521;
                s2 = (s2 + s1) % 65521;
            }
            return (s2 << 16) + s1;
        }
        let str = JSON.stringify(value);
        if (!str) return 0; // == null, undefined, "", 0
        let array = [];
        for (let i = 0; i < str.length; i++) {
            let c = str[i];
            let c1 = c.charCodeAt(0);
            let c2 = c.charCodeAt(1);
            array.push(c1 & 255, c1 >> 8, c2 & 255, c2 >> 8);
        }
        return adler32(array);
    }
    private resize(): void {
        let newSize = this.count * 2 + 1;
        let newBuckets = new Array<number>(newSize);
        newBuckets.fill(0); // В JS элементы массива автоматически не создаются 
        let newSlots = this.slots.slice(); // new Array<MapSlot<TElement>>(newSize);
        newSlots.length = newSize;
 
        for (let i = 0; i < this.count; i++) {
            let bucket = newSlots[i].hashCode % newSize;
            newSlots[i].next = newBuckets[bucket] - 1;
            newBuckets[bucket] = i + 1;
        }
        this.buckets = newBuckets;
        this.slots = newSlots;
    }
}

Скрипт, транспилированный в JS:
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
/**
 * Слот в классе Map.
 * Видимость должна быть в пределах модуля.
 */
class MapSlot {
    constructor(hashCode, key, value, next) {
        this.hashCode = hashCode;
        this.key = key;
        this.value = value;
        this.next = next;
    }
}
class HashMap {
    constructor(arg1, arg2) {
        this.clear(arg1, arg2);
    }
    clear(arg1, arg2) {
        let iterable;
        let comparer;
        if (arg1 && Symbol.iterator in arg1) {
            iterable = arg1;
            if (arg2 && "equals" in arg2)
                comparer = arg2;
        }
        else if (arg1 && "equals" in arg1)
            comparer = arg1;
        if (comparer == null)
            comparer = {
                getHashCode: HashMap.internalGetHashCode,
                equals(v1, v2) { return v1 === v2 || (v1 !== v1 && v2 !== v2); } // NaN !== NaN
            };
        if (!("getHashCode" in comparer))
            comparer.getHashCode = HashMap.internalGetHashCode;
        this.comparer = comparer;
        this.buckets = new Array(7);
        this.buckets.fill(0); // В JS элементы массива автоматически не создаются 
        this.slots = new Array(7);
        this.count = 0;
        if (iterable) {
            for (let e of iterable) {
                this.set(e[0], e[1]);
            }
        }
    }
    /**
     * Количество элементов коллекции.
     */
    get size() {
        let count = 0;
        for (let i = 0; i < this.count; i++) {
            if (this.slots[i].hashCode !== -1)
                ++count;
        }
        return count;
    }
    /**
     * Если элемент в коллекции, удалить его и вернуть true, иначе вернуть false.
     * @param {TElement} value Элемент, который нужно удалить
     * @returns {boolean} Был ли удаляемый элемент
     */
    delete(key) {
        let hashCode = this.comparer.getHashCode(key);
        let bucket = hashCode % this.buckets.length;
        let last = -1;
        for (let i = this.buckets[bucket] - 1; i >= 0; last = i, i = this.slots[i].next) {
            if (this.slots[i].hashCode === hashCode && this.comparer.equals(this.slots[i].key, key)) {
                if (last < 0) {
                    this.buckets[bucket] = this.slots[i].next + 1;
                }
                else {
                    this.slots[last].next = this.slots[i].next;
                }
                this.slots[i].hashCode = -1;
                return true;
            }
        }
        return false;
    }
    /**
     * Вернуть определенный элемент коллекции по ключу.
     * @param {TKey} key Ключ, по которому осуществляется поиск элемента
     * @returns {TValue|undefined} Найденный элемент или undefined
     */
    get(key) {
        let hashCode = this.comparer.getHashCode(key);
        for (let i = this.buckets[hashCode % this.buckets.length] - 1; i >= 0; i = this.slots[i].next) {
            if (this.slots[i].hashCode === hashCode && this.comparer.equals(this.slots[i].key, key))
                return this.slots[i].value;
        }
        return undefined;
    }
    /**
     * Определить, содержится ли элемент в коллекции.
     * @param {TElement} value Проверяемый элемент
     * @returns {boolean}
     */
    has(key) {
        let hashCode = this.comparer.getHashCode(key);
        for (let i = this.buckets[hashCode % this.buckets.length] - 1; i >= 0; i = this.slots[i].next) {
            if (this.slots[i].hashCode === hashCode && this.comparer.equals(this.slots[i].key, key))
                return true;
        }
        return false;
    }
    /**
     * Добавить или обновить элемент коллекции по ключу.
     * @param {TKey} key Ключ добавляемого элемента.
     * @param {TKey} value Значение добавляемого элемента.
     * @returns {IMap<TKey, TValue>} Свой объект коллекции.
     */
    set(key, value) {
        let hashCode = this.comparer.getHashCode(key);
        for (let i = this.buckets[hashCode % this.buckets.length] - 1; i >= 0; i = this.slots[i].next) {
            if (this.slots[i].hashCode === hashCode && this.comparer.equals(this.slots[i].key, key)) {
                this.slots[i].value = value;
                return this;
            }
        }
        // Если элемента по ключу раньше не было
        let index;
        if (this.count == this.slots.length)
            this.resize();
        index = this.count;
        this.count++;
        let bucket = hashCode % this.buckets.length;
        this.slots[index] = new MapSlot(hashCode, key, value, this.buckets[bucket] - 1);
        this.buckets[bucket] = index + 1;
        // console.log(this.buckets);
        // console.log(this.slots);
        return this;
    }
    // Методы-итераторы
    [Symbol.iterator]() {
        return this.entries();
    }
    /**
     * Возвращает новый объект Iterator, который содержит массив [key, value]
     * для каждого элемента в объекте HashMap в порядке вставки.
     */
    *entries() {
        for (let i = 0; i < this.count; i++) {
            let e = this.slots[i];
            if (e.hashCode !== -1)
                yield [e.key, e.value];
        }
    }
    /**
     * Выполнить функцию callbackFn для каждого элемента коллекции.
     * @param callbackFn {(currentValue?: TValue, currentKey?: TKey, set?: HashMap<TKey, TValue>) => void} Выполняемая функция
     * @param thisArg {any} Аргумент this
     */
    forEach(callbackFn, thisArg) {
        for (let i = 0; i < this.count; i++) {
            let e = this.slots[i];
            if (e.hashCode !== -1)
                callbackFn.call(thisArg, e.value, e.key, this);
        }
    }
    /**
     * Вернуть новый Iterator для ключей коллекции.
     */
    *keys() {
        for (let i = 0; i < this.count; i++) {
            let e = this.slots[i];
            if (e.hashCode !== -1)
                yield e.key;
        }
    }
    /**
     * Вернуть новый Iterator для значений коллекции.
     */
    *values() {
        for (let i = 0; i < this.count; i++) {
            let e = this.slots[i];
            if (e.hashCode !== -1)
                yield e.value;
        }
    }
    // Приватные методы
    static internalGetHashCode(value) {
        if (!value)
            return 0; // == null, undefined, "", 0
        function adler32(buf) {
            let count = buf.length;
            let s1 = 1;
            let s2 = 0;
            for (let i = 0; i < count; ++i) {
                s1 = (s1 + buf[i]) % 65521;
                s2 = (s2 + s1) % 65521;
            }
            return (s2 << 16) + s1;
        }
        let str = JSON.stringify(value);
        if (!str)
            return 0; // == null, undefined, "", 0
        let array = [];
        for (let i = 0; i < str.length; i++) {
            let c = str[i];
            let c1 = c.charCodeAt(0);
            let c2 = c.charCodeAt(1);
            array.push(c1 & 255, c1 >> 8, c2 & 255, c2 >> 8);
        }
        return adler32(array);
    }
    resize() {
        let newSize = this.count * 2 + 1;
        let newBuckets = new Array(newSize);
        newBuckets.fill(0); // В JS элементы массива автоматически не создаются 
        let newSlots = this.slots.slice(); // new Array<MapSlot<TElement>>(newSize);
        newSlots.length = newSize;
        for (let i = 0; i < this.count; i++) {
            let bucket = newSlots[i].hashCode % newSize;
            newSlots[i].next = newBuckets[bucket] - 1;
            newBuckets[bucket] = i + 1;
        }
        this.buckets = newBuckets;
        this.slots = newSlots;
    }
}
Скрипт для исходной задачи:
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
    let john = { name: "John" };
    // давайте сохраним количество посещений для каждого пользователя
    // let visitsCountMap = new HashMap(); // 123
    // 124
    let visitsCountMap = new HashMap({ equals(a, b) { return a?.name === b?.name; } });
    // объект john - это ключ для значения в объекте Map
    visitsCountMap.set(john, 123);
    alert(visitsCountMap.get(john)); // 123
    // Но если добавить
    let currentUser = { name: "John" };
    visitsCountMap.set(currentUser, 124);
    alert(visitsCountMap.get(john)); // ожидаю получить 124
Добавлено через 4 минуты
Алгоритм коллекций с хэшами взят у Microsoft: https://referencesource.micros... 34c0932864
Простенький корявенький алгоритм расчета хэшкода взят из Википедии: https://ru.wikipedia.org/wiki/Adler-32
(Для чисел лучше взять алгоритм, заточенный под числа "с магическими числами")
2
 Аватар для Tavashi
1172 / 762 / 194
Регистрация: 21.05.2016
Сообщений: 1,858
02.03.2021, 00:27
Цитата Сообщение от damix Посмотреть сообщение
Потому что эти объекты одинаковые по значениям.
Да, но это не делает эти объекты равными, то есть:
JavaScript
1
2
3
4
let john = { name: "John" };
let currentUser = { name: "John" };
 
console.log(john === currentUser); // false
Цитата Сообщение от damix Посмотреть сообщение
Вопрос в том, как правильно делать что-то такое что может быстро вернуть по ключу значение, где этот ключ является чем-то таким, состоящим из нескольких примитивов.
Можно использовать WeakMap, если используются ценные ключи.
0
 Аватар для damix
53 / 47 / 22
Регистрация: 04.11.2013
Сообщений: 404
Записей в блоге: 2
30.07.2021, 21:25  [ТС]
Цитата Сообщение от Tavashi Посмотреть сообщение
Да, но это не делает эти объекты равными
Не надо мне документацию пересказывать.

И все таки, как нормально решить проблему? Без toString(), JSON.stringify() или другого убожества. Единственная страница с этим же вопросом вот
https://esdiscuss.org/topic/maps-with-object-keys
Так сделали они эти value types в ES7? Или единственный способ - сторонняя реализация Map?

Цитата Сообщение от Demolition_Man Посмотреть сообщение
Хм. всегда ассоциативный массив понимал как: массив, в котором в качестве ключей применяются строки.
Нет, это массив, в котором ключами может быть что угодно, что можно хешануть или сравнить, либо упорядочить и сравнить; в зависимости от того, какая реализация: хеш-таблицы или двоичные деревья. С объектами можно сделать всё перечисленное.
0
Эксперт JS
6496 / 3907 / 2006
Регистрация: 14.06.2018
Сообщений: 6,781
31.07.2021, 05:46
Цитата Сообщение от damix Посмотреть сообщение
Или единственный способ - сторонняя реализация Map?
Да, единственный.
Сторонний класс HashMap описан в сообщении #10.
1
 Аватар для Tavashi
1172 / 762 / 194
Регистрация: 21.05.2016
Сообщений: 1,858
02.08.2021, 17:41
Цитата Сообщение от damix Посмотреть сообщение
Не надо мне документацию пересказывать.
И все таки, как нормально решить проблему? Без toString(), JSON.stringify() или другого убожества. Единственная страница с этим же вопросом вот
https://esdiscuss.org/topic/maps-with-object-keys
Так сделали они эти value types в ES7? Или единственный способ - сторонняя реализация Map?
Ссылочку с обсуждением 7-ми летней давности полностью не читал, но маловероятно что она здесь вообще нужна. Нам нужно в первую очередь определиться с постановкой задачи. Из вашего примера видно, что вы используете два разных объекта, но хотите трактовать их как одинаковые. То есть, мы вводим свое, кастомное, правило идентичности объектов. Если использовать "содержимое" объектов как критерий эквивалентности, то можно сделать что-то вроде этого:
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function MyMap() {
  this.container = {};
  this.set = function(key, val) {
    this.container[key.name] = val;
  };
  this.get = function(key) {
    return this.container[key.name];
  };
}
 
// ваш юз кейс:
 
let john = { name: "John" };
 
// давайте сохраним количество посещений для каждого пользователя
let visitsCountMap = new MyMap();
 
// объект john - это ключ для значения в объекте Map
visitsCountMap.set(john, 123);
 
alert(visitsCountMap.get(john)); // 123
 
// Но если добавить
 
let currentUser = { name: "John" };
 
visitsCountMap.set(currentUser, 124);
 
alert(visitsCountMap.get(john)); // 124
Как второй вариант, если вы хотите использовать некий универсальный идентификатор объекта, то можно можно при добавлении объекта в контейнер дописывать в него последовательно id. То есть, объявляем контейнер массивом и записываем в него объекты c id, которые затем достаем через этот id. Это будет более универсальный подход, то минус его в том, что объекты мутируются. Впрочем, если ваши соглашения и предметная область это позволяет, то я бы выбрал его.

Не по теме:

Как третий вариант, написать пропозал по добавлении желаемого функционала и ждать =)

0
 Аватар для damix
53 / 47 / 22
Регистрация: 04.11.2013
Сообщений: 404
Записей в блоге: 2
02.08.2021, 20:51  [ТС]
Цитата Сообщение от Tavashi Посмотреть сообщение
Ссылочку с обсуждением 7-ми летней давности полностью не читал, но маловероятно что она здесь вообще нужна.
Маловероятно что нужно здесь вот это
JavaScript
1
2
3
4
5
6
7
8
9
function MyMap() {
  this.container = {};
  this.set = function(key, val) {
    this.container[key.name] = val;
  };
  this.get = function(key) {
    return this.container[key.name];
  };
}
Просто самое бестолковое, что можно придумать, работает только если в объекте одно свойство и то строка.

Добавлено через 2 минуты
Если уж руками делать hashmap то как по ссылке
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var HashMap = function() { this._map = new Map(); };
HashMap.set = function(key, value) {
  var hash = key.hashCode();
  var list = this._map.get(hash);
  if (!list) { list = []; this._map.set(hash, list); }
  for (var i=0; i<list.length; i++) {
     if (list[i].key.equals(key)) {
        list[i].value = value;
        return;
     }
  }
  list[i].push({key: key, value: value });
};
// etc
0
 Аватар для Tavashi
1172 / 762 / 194
Регистрация: 21.05.2016
Сообщений: 1,858
03.08.2021, 02:15
damix, вы не понимаете, вам все равно нужно определять правило по которому вы будет считать объекты равными. Выше был как пример, чтобы было понятно о чем речь. Ваш же пример, который по ссылке, без этого определения тоже работать не будет. Если вы хотите структуру, которая бы магическим образом угадывала ваши мысли что считать эквивалентными объектами, то такой еще нет.
0
 Аватар для damix
53 / 47 / 22
Регистрация: 04.11.2013
Сообщений: 404
Записей в блоге: 2
11.11.2024, 15:39  [ТС]
Цитата Сообщение от amr-now Посмотреть сообщение
скрипт на TypeScript
Это хорошая реализация, спасибо!
Но тут главное, что непонятно было, это как правильно сравнивать объекты по значениям в JS.
Ну не может же это всерьез быть через JSON или другие строки. Наверняка же кто-то уже стороннюю либу написал для этого.

Сделал такое дополнение
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class HashMapFactory {
    constructor() { }
    
    static createHashMap() {
        return new HashMap({ equals(a, b) {
            for (let prop of Object.keys(b)) {
                if (!a.propertyIsEnumerable(prop)) {
                    return false
                }
            }
            
            for (let prop of Object.keys(a)) {
                if (!b.propertyIsEnumerable(prop)) {
                    return false
                }
                
                if (a[prop] !== b[prop]) {
                    return false
                }
            }
            
            return true
        } })
    }
}
Создает коллекцию, считающую равными объекты, у которых совпадают по значениям собственные перечислимые свойства. Вроде работает.

P.S.
Стороннюю либу таки написали https://lodash.com/docs/4.17.15#isEqual но и там
Object objects are compared by their own, not inherited, enumerable properties.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
inter-admin
Эксперт
29715 / 6470 / 2152
Регистрация: 06.03.2009
Сообщений: 28,500
Блог
11.11.2024, 15:39
Помогаю со студенческими работами здесь

Найти пересечение множеств ключей двух map
Добрый день! Имеется 2 map'а: map&lt;string, double&gt; map1; map&lt;string, double&gt; map2; Требуется сложить ключи, являющиеся общими...

Dictionary с адресами классов в качестве ключей
Предположим есть такой код: MyClass x, y; У класса MyClass есть конструктор по умолчанию, так что x и y равны. Я хочу, чтобы...

Словарь в котором в качестве ключей выступают символы 'м'и 'ж'
Здравствуйте, помогите с задачей пожалуйста. Словарь, в котором в качестве ключей выступают символы «м» и «ж», а в качестве...

Словарь в котором в качестве ключей выступают символы 'м'и 'ж'
Создать словарь в котором в качестве ключей выступают символы 'м' и 'ж',а в качестве соответствующих им значений списки мальчиков и девочек...

Объекты в Tiled MAP
Сделал карту в Tiled MAP, с помощью TinyXML считываю в мой проект и там с помощью SFML уже отрисовываю. Все пошло без проблем, и...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
17
Ответ Создать тему
Новые блоги и статьи
Ритм жизни
kumehtar 27.02.2026
Иногда приходится жить в ритме, где дел становится всё больше, а вовлечения в происходящее — всё меньше. Плотный график не даёт вниманию закрепиться ни на одном событии. Утро начинается с быстрых,. . .
SDL3 для Web (WebAssembly): Сборка библиотек SDL3 и Box2D из исходников с помощью CMake и Emscripten
8Observer8 27.02.2026
Недавно вышла версия SDL 3. 4. 2 библиотеки SDL3. На странице официальной релиза доступны исходники, готовые DLL (для x86, x64, arm64), а также библиотеки для разработки под Android, MinGW и Visual. . .
SDL3 для Web (WebAssembly): Реализация движения на Box2D v3 - трение и коллизии с повёрнутыми стенами
8Observer8 20.02.2026
Содержание блога Box2D позволяет легко создать главного героя, который не проходит сквозь стены и перемещается с заданным трением о препятствия, которые можно располагать под углом, как верхнее. . .
Конвертировать закладки radiotray-ng в m3u-плейлист
damix 19.02.2026
Это можно сделать скриптом для PowerShell. Использование . \СonvertRadiotrayToM3U. ps1 <path_to_bookmarks. json> Рядом с файлом bookmarks. json появится файл bookmarks. m3u с результатом. # Check if. . .
Семь CDC на одном интерфейсе: 5 U[S]ARTов, 1 CAN и 1 SSI
Eddy_Em 18.02.2026
Постепенно допиливаю свою "многоинтерфейсную плату". Выглядит вот так: https:/ / www. cyberforum. ru/ blog_attachment. php?attachmentid=11617&stc=1&d=1771445347 Основана на STM32F303RBT6. На борту пять. . .
Камера Toupcam IUA500KMA
Eddy_Em 12.02.2026
Т. к. у всяких "хикроботов" слишком уж мелкий пиксель, для подсмотра в ESPriF они вообще плохо годятся: уже 14 величину можно рассмотреть еле-еле лишь на экспозициях под 3 секунды (а то и больше),. . .
И ясному Солнцу
zbw 12.02.2026
И ясному Солнцу, и светлой Луне. В мире покоя нет и люди не могут жить в тишине. А жить им немного лет.
«Знание-Сила»
zbw 12.02.2026
«Знание-Сила» «Время-Деньги» «Деньги -Пуля»
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru