Handling Undefined Key Values in Data Structures
In a lot of algorithm problems we use a hash table pattern to keep count of something as we iterate or traverse through a data structure. It is a very common scenario to have a situation that goes as follow:
- If
keyexists in data structure, e.g. Map, Object, Set, Array, increment the value, or add to the existing value, modify the value, etc. Any variation of that - If
keydoes not exist in data structure, initiate it with whatever default value you need. E.g. if your counting you want to initiate each non-existentkeywith a0(or1) value so you can just simply increment it later.
There are various ways of approaching this. We will show off some of them here:
If-else
We use a basic if-else statement and check if hash[element] evaluates to false, then use the ! operator to flip that to true. This only happens if the key we were looking for was not define in the object yet
let arr = ['a', 'b', 'c', 'a', 'd'];
let obj: Record<string, number> = {};
let map: Map<strinig, number> = new Map();
let set: Set<number> = new Set();
let list: [string, number][] = []
for (let elem of arr) {
if (!obj[elem]) {
obj[elem] = 1;
} else {
obj[elem] += 1;
}
}
for (let elem of arr) {
if (!(elem in obj)) {
obj[elem] = 1;
} else {
obj[elem] += 1;
}
}
// { a: 2, b: 1, c: 1, d: 1 }
for (let elem of arr) {
if (!map.has(elem)) {
map.set(elem, 1)
} else {
map.set(elem, map.get(elem) + 1)
}
}
// Map(4) { 'a' => 2, 'b' => 1, 'c' => 1, 'd' => 1 }
for (let elem of arr) {
if (!set.has(elem)) {
set.add(elem)
} else {
// do whatev you want
}
}
// Set(4) { 'a', 'b', 'c', 'd' }
Ternary Operator
for (let elem of arr) {
obj[elem] = obj[elem] ? obj[elem] + 1 : 1;
}
for (let elem of arr) {
obj[elem] = elem in obj ? obj[elem] + 1 : 1;
}
// { a: 2, b: 1, c: 1, d: 1 }
for (let elem of arr) {
map.set(elem, map.has(elem) ? map.get(elem) + 1 : 1)
}
// Map(4) { 'a' => 2, 'b' => 1, 'c' => 1, 'd' => 1 }
for (let elem of arr) {
set.has(elem) ? null : set.add(elem)
}
// Set(4) { 'a', 'b', 'c', 'd' }
Or || operator
for (let elem of arr) {
obj[elem] = obj[elem] + 1 || 1
}
// { a: 2, b: 1, c: 1, d: 1 }
for (let elem of arr) {
map.set(elem, map.has(elem) + 1 || 1)
}
// Map(4) { 'a' => 2, 'b' => 1, 'c' => 1, 'd' => 1 }
for (let elem of arr) {
set.add(elem)
}
// Set(4) { 'a', 'b', 'c', 'd' }
Using a Proxy object
We can intercept any method of a object by creating a proxy. This is great to create default values when accessing a property that does not yet exist.
let handler: ProxyHandler<Record<string, number>> = {
get: (target: Record<string, number>, key: string) =>
key in target ? target[key] += 1 : target[key] = 1,
};
let proxy = new Proxy(obj, handler);
for (let elem of arr) {
proxy[elem]
}
// Proxy [ { a: 2, b: 1, c: 1, d: 1 } ]