I currently have a need to temporarily swap out values in a JavaScript string, and therefore I will need to have a two-way map/hash thing.
For example, let's say I want to change \*
to __asterisk__
(this is just an example, it's not what I'm actually trying to do). I'll have to be able to map *
to __asterisk__
(to swap out the value in the original string), but then I'll also have to be able to map __asterisk__
back to *
(to get the original string back).
Here's some quick pseudo-ish code of the kind of thing that I'm looking for, so you can understand it better:
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'
This is what I've thought about and tried so far:
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 } }
The obvious problem with this is that the way to get by value is much too complicated, and I don't want to have to write out the whole thing every single time I have to reverse lookup.
I just wanted to know: Is there any way to solve this problem without resorting to looping through an object? If not, is there any way to make it easier or cleaner?
Use two objects. One object contains the * -> _asterisk_
mapping, the other object contains _asterisk_ -> *
.
var forwardMap = {'*': '__asterisk__', '%': '__percent__', ...};
var reverseMap = {};
for (var key in forwardMap) {
if (forwardMap.hasOwnProperty(key)) {
reverseMap[forwardMap[key]] = key;
}
}
With an extra internal object for reverse mapping. Best if we add a utility class ;) like this:
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]; };
Then you instantiate like this:
var twoWayMap = new TwoWayMap({
'*' : '__asterisk__',
'%' : '__percent__',
....
});
Then, for using it:
twoWayMap.get('*') //Returns '__asterisk__'
twoWayMap.revGet('__asterisk__') //Returns '*'
EDIT:
Equivalent with ES6 syntax
class TwoWayMap {
constructor(map) {
this.map = map;
this.reverseMap = {};
for(let key in map) {
const value = map[key];
this.reverseMap[value] = key;
}
}
get(key) { return this.map[key]; }
revGet(key) { return this.reverseMap[key]; }
}
Usage is the same
Hope this helps. Cheers
I'd just use a plain object:
var map = { '*': '__asterisk__', '__asterisk__': '*', .... }
If you don't want to have to write all those out, take a look at the implementation of underscore's _.invert(object)
here
I needed something similar, and created https://www.npmjs.com/package/bi-directional-map
It is implementing a 2-way map using 2 es6 Map, even though it doesn't have a much functionality, it is tested and since it's written typescript
comes with official typings.
Some might prefer a more succinct functional style...
const create2WayMap = (seedMap, mapName, reversemapName) => ({
[mapName]: { ...seedMap },
[reversemapName]: Object.keys(seedMap).reduce((map, key) => {
const value = seedMap[key]
return { ...map, [value]: key }
}, {})
})
usage:
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
similar to @harunurhan's answer (but much less verbose) I created a small class that takes in a Typescript Map and generates a readonly two-way 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);
}
}