【问题标题】:How can I create a two-way mapping in JavaScript, or some other way to swap out values?如何在 JavaScript 中创建双向映射,或以其他方式交换值?
【发布时间】:2014-01-30 23:47:51
【问题描述】:

我目前需要临时换出 JavaScript 字符串中的值,因此我需要有一个双向映射/哈希值。

例如,假设我想将\* 更改为__asterisk__(这只是一个示例,并不是我真正想要做的)。我必须能够将* 映射到__asterisk__(以换出原始字符串中的值),但是我还必须能够将__asterisk__ 映射回*(到取回原来的字符串)。

这是我正在寻找的那种东西的一些快速的伪代码,所以你可以更好地理解它:

var myString = 'this is \* a test';

// ???
var twoWayMap = new TwoWayMap('*' <---> '__asterisk__', '%' <---> '__percent__', ...);

var newString = myString.replace(/\\(.)/g, function(m, c) {
    return twoWayMap.getKey(c);
});
// newString is now 'this is __asterisk__ a test'

// ... later in the code ...

var oldString = newString.replace(/__([^_]+)__/g, function(m, c) {
    return twoWayMap.getValue(c);
});
// oldString is now 'this is * a test'

这是我迄今为止的想法和尝试:

var twoWayMap = {'*': '__asterisk__', '%': '__percent__', ...};

// getKey would be like this:
twoWayMap[c];
// getValue would be like:
var val; for (var x in twoWayMap) { if (twoWayMap[x] === c) { val = x; break } }

这样做的明显问题是按值获取的方法太复杂了,我不想每次必须反向查找时都写出整个内容。

我只是想知道:有没有什么方法可以解决这个问题而不需要循环访问一个对象?如果没有,有什么方法可以让它更容易或更干净?

【问题讨论】:

  • 您可以通过键入来指定第一个地图,然后使用初始化代码构建反向地图,这样在运行时您就有了两个方向的地图。
  • @Doorknob 我加了一个答案,我觉得很优雅,请检查一下
  • @EdgarVillegasAlvarado 我知道;我已经投票了。
  • 你原来的解决方案还不错,对于非时间关键的东西......

标签: javascript data-structures map


【解决方案1】:

带有用于反向映射的额外内部对象。最好添加一个实用程序类;)如下:

ES6 语法(向下滚动查看 ES5 语法)

class TwoWayMap {
    constructor(map) {
       this.map = map;
       this.reverseMap = {};
       for(const key in map) {
          const value = map[key];
          this.reverseMap[value] = key;   
       }
    }
    get(key) { return this.map[key]; }
    revGet(key) { return this.reverseMap[key]; }
}

然后你像这样实例化:

const twoWayMap = new TwoWayMap({
   '*' : '__asterisk__', 
    '%' : '__percent__',
   ....
});

最后,使用它:

twoWayMap.get('*')   //Returns '__asterisk__'
twoWayMap.revGet('__asterisk__')  //Returns '*'

奖励:如果您还需要设置/取消设置方法,您可以(在类内)轻松完成,例如:

set(key, value) { this.map[key] = value; }
unset(key) { delete this.map[key] }
// same for revSet and revUnset, just use this.reverseMap instead

等效于 ES5(旧 js)语法:

function TwoWayMap(map) {
   this.map = map;
   this.reverseMap = {};
   for(var key in map) {
      var value = map[key];
      this.reverseMap[value] = key;   
   }
}
TwoWayMap.prototype.get = function(key){ return this.map[key]; };
TwoWayMap.prototype.revGet = function(key){ return this.reverseMap[key]; };

用法同理:

var twoWayMap = new TwoWayMap({
   '*' : '__asterisk__', 
    '%' : '__percent__',
   ....
});
twoWayMap.get('*')   //Returns '__asterisk__'
twoWayMap.revGet('__asterisk__')  //Returns '*'

希望这会有所帮助。干杯

【讨论】:

  • 这应该是公认的答案。 Edgar,您介意更新它并添加 ES6 语法代码吗?
  • @xpt 当然,我编辑了我的答案。如果您需要添加/删除方法,请告诉我
  • 如果您考虑在set() 方法中同时设置reverseMap 中的相应键,请确保执行检查,以免最终出现两个键在的情况map 指向同一个值,因为reverseMap 中的对应值只能指向一个键。
  • 我不明白这不是公认的答案。
【解决方案2】:

所以这里有点 es17 糖:

const twoWayObject = (obj) => {
 const objEntries = Object.entries(obj);
 return Object.fromEntries([
 ...objEntries, 
 ...objEntries.map(e=>[...e].reverse())
 ]);
};
console.log(twoWayObject({ a: 1, b: 2 })); // { a: 1, 1: a, b: 2, 2: b }

Object.entries 接受一个对象并返回一个条目数组, 一个条目是 [key, value] 对数组。 Object.fromEntries 做相反的事情 - 它接受一个条目数组并返回一个对象,其中每个键 ~> 值。

结合这两种方法,可以非常简洁地编写从一个对象到另一个对象的函数映射。

这个 sn-p 实体化一个对象,并创建一个新的条目数组,其中的条目被反转(不可变)。这两个数组是spread(也用于反转的点...运算符)到另一个数组中。所以现在我们有一个条目数组,其中包括原始对象条目及其反转。所以现在对于每个 [a, b],我们也有 [b, a]。在这个数组上调用 object.fromEntries 然后创建我们的双向映射。

兼容

请记住,这是新语法(旧浏览器/旧版本节点不支持传播和对象方法(尤其是 fromEntries)(例如,我认为 fromEntries 已添加到节点 v12))。所以可能需要小心/通天。

更新 - TypeScript

让我们强制 TypeScript 在使用谨慎的对象类型(具有编译时确定的键和值)时了解我们的双向对象,使用此 InvertResult 类型 - 我们的函数签名将如下所示:

<T extends Record<string, string>>(obj: T): T & InvertResult<T>

记得在传入对象时添加const assertion——否则TS会将键和值加宽到&lt;string, string&gt;,酷炫的类型安全就会消失。

因此,使用上面的示例,现在 TS 将正确识别任何键的确切类型及其反向,即:

obj[1] // "a"

【讨论】:

    【解决方案3】:

    也可以使用lodash.invert包创建原图的倒版。

    import invert from "lodash.invert";
    
    const map = { one: 1, two: 2, three: 3 };
    const reverseMap = invert(map);
    
    console.log(map.one); // 1
    console.log(map.two); // 2
    console.log(map.three); // 3
    
    console.log(reverseMap[1]); // 'one'
    console.log(reverseMap[2]); // 'two'
    console.log(reverseMap[3]); // 'three'
    

    【讨论】:

      【解决方案4】:

      另一种可能的解决方案:

      
      class TwoWayMap extends Map {
        constructor(entries) {
          super(entries);
          if (Array.isArray(entries)) {
            entries.forEach(entry => {
              super.set(entry[1], entry[0]);
            });
          }
        }
        set(key, value) {
          super.set(key, value);
          super.set(value, key);
          return this;
        }
      }
      
      
      const twoWayMap = new TwoWayMap([['a', 1]]);
      twoWayMap.set('b', 2);
      
      twoWayMap.get('a'); // 1
      twoWayMap.get(1); // a
      twoWayMap.get('b'); // 2
      twoWayMap.get(2); // b
      

      【讨论】:

      • 如果两个键的值相同,就会出现问题。
      【解决方案5】:

      类似于@harunurhan 的回答(但不那么冗长)我创建了一个小类,它接受 Typescript Map 并生成只读的双向地图:

      export class TwoWayReadonlyMap<T, K> {
        map: Map<T, K>;
        reverseMap: Map<K, T>;
        constructor(map: Map<T, K>) {
          this.map = map;
          this.reverseMap = new Map<K, T>();
          map.forEach((value, key) => {
            this.reverseMap.set(value, key);
          });
        }
        get(key: T) {
          return this.map.get(key);
        }
        revGet(key: K) {
          return this.reverseMap.get(key);
        }
      }
      

      【讨论】:

        【解决方案6】:

        有些人可能更喜欢更简洁的功能风格...

        const create2WayMap = (seedMap, mapName, reversemapName) => ({
          [mapName]: { ...seedMap },
          [reversemapName]: Object.keys(seedMap).reduce((map, key) => {
            const value = seedMap[key]
            return { ...map, [value]: key }
          }, {})
        })
        

        用法:

        const myIDMap = create2WayMap(
          {
            1: 'SomeString',
            2: 'Another String',
            3: 'Another'
          },
          'idStrings',
          'idNumbers'
        )
        
        let id = 2
        const str = myIDMap.idStrings[id] // yields 'Another String'
        id = myIDMap.idNumbers[str] // yields 2
        

        【讨论】:

          【解决方案7】:

          我需要类似的东西,并创建了https://www.npmjs.com/package/bi-directional-map

          它正在使用 2 个 es6 Map 实现 2-way map,尽管它没有太多功能,但它已经过测试,并且由于它是 typescript 编写的,因此带有官方类型。

          【讨论】:

            【解决方案8】:

            我只是使用一个普通的对象:

            var map = { '*': '__asterisk__', '__asterisk__': '*', .... }
            

            如果你不想把这些都写出来,看看下划线的_.invert(object)here的实现

            【讨论】:

            • 你至少可以发布有效的 Javascript 语法。
            • 是的......我不确定= 和之后的类似数组的语法在那里做什么:P
            • 是啊...最近写太多lua了。 ://
            【解决方案9】:

            使用两个对象。一个对象包含* -&gt; _asterisk_ 映射,另一个对象包含_asterisk_ -&gt; *

            var forwardMap = {'*': '__asterisk__', '%': '__percent__', ...};
            var reverseMap = {};
            for (var key in forwardMap) {
                if (forwardMap.hasOwnProperty(key)) {
                    reverseMap[forwardMap[key]] = key;
                }
            }
            

            【讨论】:

            • 那么我必须手动将每个键/值对反向复制到另一个对象吗? -_-
            • 您不必手动操作。如果你实现一个类,添加映射的方法可以更新两个对象。或者编写一个映射,然后使用循环创建另一个映射。
            猜你喜欢
            • 1970-01-01
            • 2016-01-08
            • 2011-12-08
            • 1970-01-01
            • 1970-01-01
            • 2010-09-23
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多