Let's say I have 2 arrays each of which holds a few different objects
class Object1 {}
class Object2 {}
class Object3 {}
class Object4 {}
class Object5 {}
class Object6 {}
const arrayA = [
new Object1(),
new Object2(),
new Object3()
]
const arrayB = [
new Object4(),
new Object1(),
new Object2(),
new Object6()
]
I need to mutate arrayB so that it holds instances of the same classes as arrayA in the same order.
But I need to reuse any instances in arrayB of classes that are also present in arrayA.
The desired output here would be:
arrayB = [
instanceOfObject1AlreadyInArrayB,
instanceOfObject2AlreadyInArrayB,
new Object3()
]
How would you solve this with the best possible big O notation?
Are there any comparable algorithm names I can research?
One approach here is to copy arrayB
into a Map
from each element's constructor
to its value. Then we can iterate over arrayA
's elements and for each one, if its constructor
is a key in the Map
we use the existing value, otherwise we construct a new instance.
The time complexity of this is essentially just O(a+b) where a is the length of arrayA
and b is the length of arrayB
, assuming that operations on a Map
are constant-time O(1). Technically the spec only requires that it be sublinear, so then all we can say for sure is that the time complexity of the whole process is strictly less than O((a+b)2), so at the least it scales better than iterating arrayB
for each element of arrayA
(e.g., arrayA.map(a => arrayB.find(⋯)
) , which would be O(a×b).
And because apparently you want arrayB
to be mutated, we can clear arrayB
after copying its contents to the Map
, and then just push the elements onto it. This is O(a) and thus doesn't affect the overall time complexity.
Here is an example:
// not necessary, just adding a common constructor so that we can
// see exactly when new instances are being constructed
class Parent {
constructor() {
console.log("constructed " + this.constructor.name)
}
}
class Object1 extends Parent { }
class Object2 extends Parent { }
class Object3 extends Parent { }
class Object4 extends Parent { }
class Object5 extends Parent { }
class Object6 extends Parent { }
console.log("making arrayA")
const arrayA = [
new Object1(),
new Object2(),
new Object3()
]
console.log("making arrayB")
const arrayB = [
new Object4(),
new Object1(),
new Object2(),
new Object6()
]
const map = new Map();
arrayB.forEach(v => map.set(v.constructor, v));
arrayB.length = 0;
console.log("recreating arrayB contents")
arrayA.forEach(v => arrayB.push(map.get(v.constructor) ?? new v.constructor()));
console.log(JSON.stringify(arrayB.map(x => x.constructor.name)))
If you run that snippet you'll see that arrayB
ends up with instances of Object1
, Object2
, and Object3
in that order, but only the Object3
constructor gets invoked; the Object1
and Object2
instances from arrayB
are re-used.