Skip to content

Instantly share code, notes, and snippets.

@hathix
Last active October 25, 2021 14:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hathix/19596d3ae0ca045f06ba2dcf81bb06a1 to your computer and use it in GitHub Desktop.
Save hathix/19596d3ae0ca045f06ba2dcf81bb06a1 to your computer and use it in GitHub Desktop.
TypeScript immutable map
class ImmutableMap<T> {
// This is immutable, can't be changed in place
private innerMap: Map<string,T>;
public constructor(nativeMap = new Map<string,T>()) {
this.innerMap = nativeMap;
}
public keys() : string[] {
return Array.from(this.innerMap.keys());
}
public values() : T[] {
return Array.from(this.innerMap.values());
}
public entries(): [string,T][] {
return Array.from(this.innerMap.entries());
}
public get(key: string) : T|undefined {
return this.innerMap.get(key);
}
// Does not edit in place, returns a new one
public set(key: string, value: T) : ImmutableMap<T> {
// Create a copy
const newMap: Map<string,T> = new Map<string,T>(this.innerMap);
// Add to it
newMap.set(key, value);
// Return the new one
return new ImmutableMap<T>(newMap);
}
// Does not edit in place
public remove(key: string) : ImmutableMap<T> {
// Create a copy
const newMap: Map<string,T> = new Map<string,T>(this.innerMap);
// Remove from it
newMap.delete(key);
// Return the new one
return new ImmutableMap<T>(newMap);
}
public mapToArray<U>(fn: (key: string, value: T) => U) : U[] {
return this.entries().map(([key, value]: [string, T]) => {
return fn(key, value);
});
}
// Semantic alternative for mapToArray()
public forEach(fn: (key: string, value: T) => void) : void {
this.mapToArray<void>(fn);
}
public mapToMap<U>(fn: (key: string, value: T) => [string, U]) : ImmutableMap<U> {
// Create a new empty map and populate it with each new element
const newMap: Map<string, U> = new Map<string, U>();
this.entries().forEach(([key, value]: [string, T]) => {
// Transform it
const [newKey, newValue] = fn(key, value);
// Set it
newMap.set(newKey, newValue);
});
return new ImmutableMap<U>(newMap);
}
public filter(predicate: (key: string, value: T) => boolean) : ImmutableMap<T> {
const allowedKeys: string[] = this.keys().filter((key: string) => {
const value: T = this.innerMap.get(key)!;
return predicate(key, value);
});
// Build up a new map with these keys
const newMap: Map<string, T> = new Map<string, T>();
allowedKeys.forEach((key: string) => {
newMap.set(key, this.innerMap.get(key)!);
});
return new ImmutableMap<T>(newMap);
}
public toString() : string {
return this.entries().map(([key, value]: [string, T]) => {
return `${key} => ${value}`;
}).join("\n");
}
public print() : void {
console.log(this.toString());
}
}
// Testing
let m = new ImmutableMap<number>();
m = m.set("A", 1);
m = m.set("B", 2);
m = m.set("C", 3);
m.set("D", 4); // Shouldn't show up
// A => 1, B => 2, C => 3
console.log(m.toString());
// B => 2, C => 3
console.log(m.filter((k, v) => v >= 2).toString());
// B! => 12, C! => 13
console.log(m.filter((k, v) => v >= 2).mapToMap((k, v) => [k+"!", v+10]).toString());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment