Skip to content

feat!: improve semantics of map.intersection and map.difference #22

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 14 additions & 11 deletions src/__tests__/map.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ describe('DeepMap', () => {
});

describe('union method', () => {
it('the union of two maps contains all unique keys and associated values', async () => {
it('the union of two maps contains all key-value pairs from first map and any unique keys from second map', async () => {
const map1 = new DeepMap([
[new K('1'), new V('1')],
[new K('2'), new V('2')],
Expand Down Expand Up @@ -293,19 +293,19 @@ describe('DeepMap', () => {
});

describe('intersection method', () => {
it('the intersection of two maps contains all shared keys and associated values', async () => {
it('the intersection of two maps contains all shared key-value pairs', async () => {
const map1 = new DeepMap([
[new K('1'), new V('1')],
[new K('2'), new V('2')],
[new K('3'), new V('3')],
]);
const map2 = new DeepMap([
[new K('2'), new V('999')],
[new K('3'), new V('3')],
[new K('1'), new V('1')],
[new K('2'), new V('999')], // same key but different value -- not a match
[new K('4'), new V('4')],
]);
const intersectionMap = map1.intersection(map2);
expect([...intersectionMap.entries()]).toStrictEqual([
[new K('2'), new V('2')], // value for common key is the value from the caller
]);
expect([...intersectionMap.entries()]).toStrictEqual([[new K('1'), new V('1')]]);
});

it('the intersection of equal maps is the same as either input', async () => {
Expand All @@ -325,18 +325,21 @@ describe('DeepMap', () => {
});

describe('difference method', () => {
it('the difference of two maps contains all keys from first map not in second', async () => {
it('the difference of two maps contains all key-value pairs from first map not in second', async () => {
const map1 = new DeepMap([
[new K('1'), new V('1')],
[new K('2'), new V('2')],
[new K('3'), new V('3')],
]);
const map2 = new DeepMap([
[new K('2'), new V('999')],
[new K('3'), new V('3')],
[new K('1'), new V('1')],
[new K('2'), new V('999')], // same key but different value -- not a match
[new K('4'), new V('4')],
]);
const differenceMap = map1.difference(map2);
expect([...differenceMap.entries()]).toStrictEqual([
[new K('1'), new V('1')], // value for common key is the value from the caller
[new K('2'), new V('2')],
[new K('3'), new V('3')],
]);
});

Expand Down
32 changes: 23 additions & 9 deletions src/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,44 +125,50 @@ export class DeepMap<K, V, TxK = K, TxV = V> extends Map<K, V> implements Compar
}

/**
* @param other the map to compare against
* @returns true if the entries of `other` are the same as this map
*/
equals(other: this): boolean {
return this.size === other.size && this.contains(other);
}

/**
* @param other the map to compare against
* @returns true if the entries of `other` are all contained in this map
*/
contains(other: this): boolean {
return [...other.entries()].every(([otherKey, otherVal]) => {
const thisVal = this.get(otherKey);
return thisVal !== undefined && this.normalizeValue(thisVal) === this.normalizeValue(otherVal);
});
return [...other.entries()].every(([key, val]) => this.keyValuePairIsPresentIn(key, val, this));
}

/**
* @param other the map to compare against
* @returns a new map whose keys are the union of keys between `this` and `other` maps.
*
* NOTE: If both maps prescribe the same key, the value from `this` will be retained.
* NOTE: If both maps prescribe the same key, the key-value pair from `this` will be retained.
*/
union(other: this): DeepMap<K, V, TxK, TxV> {
return new DeepMap([...other.entries(), ...this.entries()], this.options);
}

/**
* @returns a new map containing all key-value pairs in `this` whose keys are also in `other`.
* @param other the map to compare against
* @returns a new map containing all key-value pairs in `this` that are also present in `other`.
*/
intersection(other: this): DeepMap<K, V, TxK, TxV> {
const intersectingPairs = [...this.entries()].filter(([key, _value]) => other.has(key));
const intersectingPairs = [...this.entries()].filter(([key, val]) =>
this.keyValuePairIsPresentIn(key, val, other)
);
return new DeepMap(intersectingPairs, this.options);
}

/**
* @returns a new map containing all key-value pairs in `this` whose keys are not also in `other`.
* @param other the map to compare against
* @returns a new map containing all key-value pairs in `this` that are not present in `other`.
*/
difference(other: this): DeepMap<K, V, TxK, TxV> {
const differencePairs = [...this.entries()].filter(([key, _value]) => !other.has(key));
const differencePairs = [...this.entries()].filter(
([key, val]) => !this.keyValuePairIsPresentIn(key, val, other)
);
return new DeepMap(differencePairs, this.options);
}

Expand All @@ -175,4 +181,12 @@ export class DeepMap<K, V, TxK = K, TxV = V> extends Map<K, V> implements Compar
protected normalizeValue(input: V): Normalized<TxV> {
return this.normalizer.normalizeValue(input);
}

/**
* @returns true if the key is present in the provided map w/ the specified value
*/
private keyValuePairIsPresentIn(key: K, val: V, mapToCheck: this): boolean {
const checkVal = mapToCheck.get(key);
return checkVal !== undefined && this.normalizeValue(checkVal) === this.normalizeValue(val);
}
}