Typescript/Javascript:使用元组作为 Map 的键

问题描述 投票:0回答:5

在我的代码中遇到这个奇怪的错误,当使用元组作为我的键时,我无法找到从 Map 获取恒定时间查找的方法。

希望这能说明问题,以及我现在使用的解决方法只是为了让它发挥作用:

你好.ts:

let map: Map<[number, number], number> = new Map<[number, number], number>()
    .set([0, 0], 48);

console.log(map.get([0,0])); // prints undefined

console.log(map.get(String([0, 0]))); //  compiler:  error TS2345: Argument of type 
// 'string' is not assignable to parameter of type '[number, number]'.

//the work-around:
map.forEach((value: number, key: [number, number]) => {
    if(String(key) === String([0, 0])){
        console.log(value); // prints 48
    }
})

要编译(转换?)我正在使用:

tsc hello.ts -target es6

tsc 版本 2.1.6

尝试了多种方法来使 Map.get() 方法正常工作,但没有取得太大成功。

javascript typescript
5个回答
42
投票

在 JavaScript(以及作为扩展的 TypeScript)中,没有两个数组是相等的,除非它们引用同一个数组(即,更改一个数组的元素也会更改另一个数组的元素)。如果您创建一个具有相同元素的新数组,它不会认为它等于任何现有数组。

因为 Map 在查找元素时会考虑这种相等性,所以如果您以数组作为键存储值,则只有再次传入完全相同的数组引用作为键才能再次获取该值:

const map: Map<[ number, number], number> = new Map<[ number, number ], number>();

const a: [ number, number ] = [ 0, 0 ];
const b: [ number, number ] = [ 0, 0 ];

// a and b have the same value, but refer to different arrays so are not equal
a === b; // = false

map.set(a, 123);
map.get(a); // = 123
map.get(b); // = undefined

一个简单的解决方法是使用字符串或数字作为键,因为当它们具有相同的值时,它们总是被认为是相等的:

const map: Map<string, number> = new Map<string, number>();

const a: [ number, number ] = [ 0, 0 ];
const b: [ number, number ] = [ 0, 0 ];

const astr: string = a.join(','); // = '0,0'
const bstr: string = b.join(','); // = '0,0'

// astr and bstr have the same value, and are strings so they are always equal
astr === bstr; // = true

map.set(astr, 123);
map.get(astr); // = 123
map.get(bstr); // = 123

11
投票

我会创建自己的类来执行此操作,以便我可以轻松使用所有地图方法:

class MyMap {
    private map = new Map<string, number>();

    set(key: [number, number], value: number): this {
        this.map.set(JSON.stringify(key), value);
        return this;
    }

    get(key: [number, number]): number | undefined {
        return this.map.get(JSON.stringify(key));
    }

    clear() {
        this.map.clear();
    }

    delete(key: [number, number]): boolean {
        return this.map.delete(JSON.stringify(key));
    }

    has(key: [number, number]): boolean {
        return this.map.has(JSON.stringify(key));
    }

    get size() {
        return this.map.size;
    }

    forEach(callbackfn: (value: number, key: [number, number], map: Map<[number, number], number>) => void, thisArg?: any): void {
        this.map.forEach((value, key) => {
            callbackfn.call(thisArg, value, JSON.parse(key), this);
        });
    }
}

操场上的代码

如您所见,例如

forEach
会自动为您提供
[number, number]
形式的密钥,而不是您需要解析的字符串。

使用示例:

let map = new MyMap();
map.set([1, 2], 4);
console.log(map.get([1, 2])) // 4

map.set([3, 4], 20);
map.forEach((v, k) => console.log(k, v));
// prints:
// [1, 2] 4
// [3, 4] 20

4
投票

在某些情况下(例如,当元组中的第二个值取决于第一个值时)我认为可以使用嵌套映射来代替:

// situation: a map from a tuple of (tableId, rowId) to the row's title

// instead of Map<[number, number], string> where the first number is
// tableId and the second number is rowId, we can have:
const rowTitleMap = Map<number, Map<number, string>>

const title = rowTitleMap.get(2)?.get(4) // can be string or undefined

1
投票

我不知道这是否适用于 Typescript,或者是否存在其他缺点,但这对我来说似乎是一种简单易用的方法,它将保留键作为元组:

const key_map_key_string = (tuple) => JSON.stringify(tuple);

const string_identical_tuple_key = (map_for_keying = new Map()) => {
  let key_map = new Map();
  [...map_for_keying.keys()].forEach((key) => key_map.set(key_map_key_string(key), key));

  return (tuple) => {
    const res = key_map.get(key_map_key_string(tuple));
    if(res) return res;

    key_map.set(key_map_key_string(tuple), tuple);
    return tuple;
  };
};

const test = () => {
  let a_map = new Map([
    [[1, 2], 'value1'],
    [[3, 4], 'value2']
  ]);
  
  const get_key = string_identical_tuple_key(a_map);
  
  console.log(a_map.get( get_key([1, 2]) ) === 'value1');
  
  a_map.set(get_key([5, 6]), 'value3');
  
  console.log(a_map.get( get_key([5, 6]) ) === 'value3');
  
  a_map.set(get_key([3, 4]), 'value4');
  
  console.log(JSON.stringify([...a_map]));
};

test();


0
投票

正如其他一些答案中提到的,您看到这种行为是因为包含相同值序列的两个不同数组实例不被视为相等,因为它们是通过引用进行比较的:

const array1 = [0, 0]
const array2 = [0, 0]
console.log(array1 === array2)
//=> false

Map
使用相同的相等概念将键与值关联起来:

const array1 = [0, 0]
const array2 = [0, 0]

const map = new Map([[array1, 48]])

console.log(map.get(array1))
//=> 48

console.log(map.get(array2))
//=> undefined

您可以使用

keyalesce
(它为相同的值序列返回相同的键)来解决此问题。

const array1 = [0, 0]
const array2 = [0, 0]

const map = new Map([[keyalesce(array1), 48]])

console.log(map.get(keyalesce(array1)))
//=> 48

console.log(map.get(keyalesce(array2)))
//=> 48

请参阅这篇文章了解

keyalesce
内部如何工作

© www.soinside.com 2019 - 2024. All rights reserved.