如何通过JavaScript中的值获取“Map”中的键?

81
我有一个像这样的JavaScript映射表。
let people = new Map();
people.set('1', 'jhon');
people.set('2', 'jasmein');
people.set('3', 'abdo');

我想要一种方法,通过其值返回一个键。

let jhonKey = people.getKey('jhon'); // jhonKey should be '1'

31
那你为什么存放它的时候反了呢? :-P - Bergi
33
为什么需要质疑一个需求? - Nikhil Kulkarni
12
也许是因为他还需要以另一种方式访问它。 - maxime schoeni
2
我建议与此处发布的顶部答案不同。而不是迭代,这会消耗CPU,存储另一个反转的映射。您可以创建一个包装类,具有方便的方法,并将其用于2个映射的读/写操作。内存比CPU更便宜。 - Maciej Krawczyk
我也遇到了完全相同的问题。对需求的质疑让我重新考虑了我的实现。事实证明,将存储方式反过来是最简单的解决方案,因为它不需要额外的处理(循环)。 - Fazwelington
12个回答

76
你可以使用一个for..of循环来直接遍历map.entries并获取键。

function getByValue(map, searchValue) {
  for (let [key, value] of map.entries()) {
    if (value === searchValue)
      return key;
  }
}

let people = new Map();
people.set('1', 'jhon');
people.set('2', 'jasmein');
people.set('3', 'abdo');

console.log(getByValue(people, 'jhon'))
console.log(getByValue(people, 'abdo'))


11
这种解决方案的一个优点可能是速度,因为没有数组转换。但为了真正理解ES6,最好将var更改为const - Keith
2
很好,顺便提一下,在“for of”中,“const”也适用。在这方面它与普通的“for”不同。 - Keith
2
只是我的个人偏好,更喜欢使用 let 而不是 const - Rajesh
2
你不需要使用 map.entries(),因为 map 本身就可以作为迭代器。我已经提交了修改。 - philraj

54
你可以将其转换为条目数组(使用[...people.entries()]),并在该数组中搜索它。

你可以将其转换为条目数组(使用[...people.entries()]),并在该数组中搜索它。

let people = new Map();
people.set('1', 'jhon');
people.set('2', 'jasmein');
people.set('3', 'abdo');
    
let jhonKeys = [...people.entries()]
        .filter(({ 1: v }) => v === 'jhon')
        .map(([k]) => k);

console.log(jhonKeys); // if empty, no key found otherwise all found keys.


2
实际上我们需要的是键而不是索引或值.. :) - Keith
5
我很好奇,你是否真的在工作中使用这种简短的代码,并且你是否会以某种方式调试你的代码? - Engineer
2
@NinaScholz 如果您直接解构映射值,则不需要数组访问和 || 逻辑:[...people.values()] - philraj
1
@philraj,我修改了答案。另一个解决方案可以是使用Array.from并映射值。 - Nina Scholz
2
@NinaScholz 奇怪,我不知道那个技巧。[编辑] { 1: v } 语法是什么?你是将键值对解构为对象吗?为什么不用 [k, v] 呢? - philraj
显示剩余6条评论

18

虽然已经有很多出色的答案,但你仍可以尝试使用下面的 "..." 和 Array.find

let people = new Map();
people.set('1', 'jhon');
people.set('2', 'jasmein');
people.set('3', 'abdo');

function getKey(value) {
  return [...people].find(([key, val]) => val == value)[0]
}

console.log('Jasmein - ', getKey('jasmein'))
console.log('Jhon - ', getKey('jhon')) 


1
但是如果您希望在两个方向上进行O(1)查找呢? - PirateApp

16

JavaScript MapObject

在使用 JavaScript Map 时,我喜欢 Nitish 的回答:(链接)

const map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

function getKey(val) {
  return [...map].find(([key, value]) => val === value)[0];
}

console.log(getKey('one'));   // 1
console.log(getKey('two'));   // 2
console.log(getKey('three')); // 3

对于JavaScript对象,您可以这样做:

const map = {
  1: 'one',
  2: 'two',
  3: 'three',
};

function getKey(val) {
  return Object.keys(map).find(key => map[key] === val);
}

console.log(getKey('one'));   // 1
console.log(getKey('two'));   // 2
console.log(getKey('three')); // 3


3
为了避免出现错误,如果找不到匹配的值,只需返回未定义即可。对于 Map 中的这个问题,函数可以是 [...map].find(([key, value]) => val === value)?.[0] - keymap
哪一个是性能优化的([...map].find 还是 map.foreach)? - Shanmugaraja_K

10

在这个方向上没有直接的选择信息的方法,所以如果你只有地图,需要像其他人建议的一样遍历整个集合。

如果 map/array/other 足够大,这样的循环会影响性能,并且反向查找的要求在项目中很常见,那么您可以使用一对 map/array/other 实现自己的结构,其中一个是按当前对象排序,另一个将键和值反转。

这样,反向查找与正常查找一样高效。当然,你需要做更多的工作,因为你需要实现每个方法,使其通过一个或两个基础对象传递,所以如果 map 很小和/或反向查找不经常使用,扫描-通过循环选项可能更可取,因为它更简单维护和可能更容易优化 JiT 编译器。

无论如何,需要注意的一件事是,多个键可能具有相同的值。如果可能出现这种情况,那么在遍历 map 时,您需要决定是否可以任意返回可能的键(可能是第一个)或者是否要返回一组键。而且如果为可能具有重复值的数据实现反向索引,也需要考虑相同的问题。


4

这里是一个正确类型化的Typescript解决方案,不会不必要地创建数组。

function find_map_value<K, V>(m: Map<K, V>, predicate: (v: V) => boolean): [K, V] | undefined {
  for (const [k, v] of m) {
    if (predicate(v)) {
        return [k, v];
    }
  }
  return undefined;
}

如果你想获取所有值,可以使用生成器:

function* find_all_map_values<K, V>(m: Map<K, V>, predicate: (v: V) => boolean): Generator<[K, V]> {
  for (const [k, v] of m) {
    if (predicate(v)) {
        yield [k, v];
    }
  }
}

这只返回第一个匹配条目。如果有多个键映射到相同的目标值,则将忽略其他匹配键。 - jsejcksn
1
是的,这是正确的。如果你想要它们全部,只需将return更改为yield并相应地更新返回类型。 - Timmmm

3

可以将Map反转,使键变为值,值变为键,然后将原始值作为键查找。以下是一个示例:

let myMap = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

let invertedMap = new Map([...myMap.entries()].map(
  ([key, value]) => ([value, key]))
);

console.log(invertedMap.get('one'))
// => 1

但是当该值是重复的时,会发生什么? - Brooklyn99
@Brooklyn99 在使用重复值作为键时存在问题,因为键需要唯一性。然而,在这种情况下,即使存在重复值,invertedMap 也将反映最后一个重复值的值。 - IliasT

3
为什么不直接利用 map 内置的 iterator 原型/实例引用 查找目标值呢?将其注入到原型链/填充解决方案可以使其在代码中通用。

Map.prototype.getKey = function(targetValue){
  let iterator = this[Symbol.iterator]()
  for (const [key, value] of iterator) {
    if(value === targetValue)
      return key;
  }
}

const people = new Map();
people.set('1', 'jhon');
people.set('2', 'jasmein');
people.set('3', 'abdo');

const jhonKey = people.getKey('jhon');
console.log(`The key for 'jhon' is: ${jhonKey}`);

对于那些好奇为什么我要添加另一个答案的人。这些答案中的大部分(除了Rajesh's answer,但我添加到了原型链中)都在以数据复制的方式寻找值,使用了展开运算符甚至直接创建了数组。请注意,Object.keys()也非常低效。
请注意,我使用for..of来迭代可迭代对象。如果需要,可以简写为for(const [key, value] of this){...}

在forEach迭代之外拥有结果变量对我来说感觉不太好 - 结果应该是const,找到的键也应该是const。@Timmmm的答案似乎是更好的版本。 - oldwizard
@oldwizard -- 我已根据您的反馈进行了更新,请注意您所引用的答案是 TypeScript,而问题标记为 JavaScript。 - Arthur Weborg

1
延续Maciej Krawczyk在这里提出的建议,这是一个通用的循环地图实现。
class ReferenceMap {
  #left = new Map();
  #right = new Map();

  constructor(iterable = []) {
    this.#left = new Map(iterable);
    this.#right = new Map(ReferenceMap.swapKeyValues(iterable));
  }

  has(key) {
    return this.#left.has(key) || this.#right.has(key);
  }

  get(key) {
    return this.#left.has(key) ? this.#left.get(key) : this.#right.get(key);
  }

  set(key, value) {
    this.#left.set(key, value);
    this.#right.set(value, key);
  }

  delete(key) {
    if (this.#left.has(key)) {
      let ref = this.#left.get(key);
      this.#left.delete(key);
      this.#right.delete(ref);
    } else if (this.#right.has(key)) {
      let ref = this.#right.get(key);
      this.#right.delete(key);
      this.#left.delete(ref);
    }
  }

  entries() {
    return this.#left.entries();
  }

  keys() {
    return this.#left.keys();
  }

  values() {
    return this.#left.values();
  }

  [Symbol.iterator]() {
    return this.entries();
  }

  get size() {
    return this.#left.size;
  }

  static * swapKeyValues(entries) {
    for (let [key, value] of entries) yield [value, key];
  }
}

0

缓存

这个问题有点不正确,因为一个值可以分配给多个键。因此,对于给定的值,结果应该是一个键数组(而不是单个键)。如果您经常进行这样的搜索,可以使用以下缓存生成器进行反向映射。

let genRevMapCache = map => [...map.entries()].reduce((a,[k,v]) => {
  if(!a.get(v)) a.set(v,[]);
  a.get(v).push(k);
  return a;
}, new Map() );

let genRevMapCache = map => [...map.entries()].reduce((a,[k,v]) => {
  if(!a.get(v)) a.set(v,[]);
  a.get(v).push(k);
  return a;
}, new Map() );


// TEST

let people = new Map();
people.set('1', 'jhon');
people.set('2', 'jasmein');
people.set('3', 'abdo');
people.set('4', 'jhon');

let cache = genRevMapCache(people);

console.log('jasmein', cache.get('jasmein'));
console.log('jhon', cache.get('jhon'));


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接