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
key
exists 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
key
does not exist in data structure, initiate it with whatever default value you need. E.g. if your counting you want to initiate each non-existentkey
with 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 } ]