Add `MapUtil.merge` for combining maps (#794)

See the docstring for details.

Test plan: Unit tests.
This commit is contained in:
Dandelion Mané 2018-09-06 14:53:49 -07:00 committed by GitHub
parent 513820c177
commit 417265a4d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 73 additions and 0 deletions

View File

@ -122,3 +122,27 @@ export function mapEntries<K, V, InK, InV>(
}
return result;
}
/**
* Merge maps without mutating the arguments.
*
* Merges multiple maps, returning a new map which has every key from
* the source maps, with their corresponding values. None of the inputs
* are mutated. In the event that multiple maps have the same key, an
* error will be thrown.
*/
export function merge<K, V>(
maps: $ReadOnlyArray<Map<$Subtype<K>, $Subtype<V>>>
): Map<K, V> {
const result = new Map();
let updates = 0;
for (const map of maps) {
for (const [key, value] of map.entries()) {
result.set(key, value);
if (result.size !== ++updates) {
throw new Error(`Maps have duplicate key: ${String(key)}`);
}
}
}
return result;
}

View File

@ -245,4 +245,53 @@ describe("util/map", () => {
expect(output).toEqual(new Map().set(11, "wat").set(12, "wat"));
});
});
describe("merge", () => {
it("combines two simple maps", () => {
const a = new Map().set("a", 1);
const b = new Map().set("b", 2);
const c = new Map().set("c", 3);
expect(MapUtil.merge([a, b, c])).toEqual(
new Map()
.set("a", 1)
.set("b", 2)
.set("c", 3)
);
});
it("treats empty map as an identity", () => {
const m = new Map().set("a", 11).set("b", 22);
expect(MapUtil.merge([new Map(), m, new Map()])).toEqual(m);
});
it("errors if there are any duplicate keys", () => {
const a = new Map().set("a", null);
expect(() => MapUtil.merge([a, a])).toThrowError("duplicate key");
});
it("handles null and undefined appropriately", () => {
const a = new Map().set(undefined, undefined);
const b = new Map().set(null, null);
expect(MapUtil.merge([a, b])).toEqual(
new Map().set(undefined, undefined).set(null, null)
);
});
it("merge works on empty list", () => {
expect(MapUtil.merge([])).toEqual(new Map());
});
it("allows upcasting the type parameters", () => {
const numberMap: Map<number, number> = new Map().set(1, 2);
const stringMap: Map<string, string> = new Map().set("one", "two");
type NS = number | string;
const _unused_polyMap: Map<NS, NS> = MapUtil.merge([
numberMap,
stringMap,
]);
});
it("produces expected type errors", () => {
const numberMap: Map<number, number> = new Map().set(1, 2);
const stringMap: Map<string, string> = new Map().set("one", "two");
// $ExpectFlowError
const _unused_badMap: Map<string, number> = MapUtil.merge([
numberMap,
stringMap,
]);
});
});
});