mirror of
https://github.com/denoland/std.git
synced 2024-11-21 20:50:22 +00:00
feat(data-structures/unstable): @std/data-structures/bidirectional-map
(#5910)
* feat(data-structures/unstable): `@std/data-structures/BidirectionalMap` * add type params * fmt * sort mod exports * add header * add missing comments * fmt * close example code block * add missing comments * fmt * close code block * tweaks * tweak --------- Co-authored-by: Asher Gomez <ashersaupingomez@gmail.com>
This commit is contained in:
parent
67e9cfa0ff
commit
7c0e917b93
223
data_structures/bidirectional_map.ts
Normal file
223
data_structures/bidirectional_map.ts
Normal file
@ -0,0 +1,223 @@
|
||||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||
// This module is browser compatible.
|
||||
|
||||
/**
|
||||
* An extension of {@linkcode Map} that allows lookup by both key and value.
|
||||
*
|
||||
* Keys and values must be unique. Setting an existing key updates its value.
|
||||
* Setting an existing value updates its key.
|
||||
*
|
||||
* @experimental **UNSTABLE**: New API, yet to be vetted.
|
||||
*
|
||||
* @typeParam K The type of the keys in the map.
|
||||
* @typeParam V The type of the values in the map.
|
||||
*
|
||||
* @example Usage
|
||||
* ```ts
|
||||
* import { BidirectionalMap } from "@std/data-structures/bidirectional-map";
|
||||
* import { assertEquals } from "@std/assert";
|
||||
*
|
||||
* const map = new BidirectionalMap([["one", 1]]);
|
||||
*
|
||||
* assertEquals(map.get("one"), 1);
|
||||
* assertEquals(map.getReverse(1), "one");
|
||||
* ```
|
||||
*
|
||||
* @example Inserting a value that already exists
|
||||
* ```ts
|
||||
* import { BidirectionalMap } from "@std/data-structures/bidirectional-map";
|
||||
* import { assertEquals } from "@std/assert";
|
||||
*
|
||||
* const map = new BidirectionalMap();
|
||||
* map.set(1, "one");
|
||||
* map.set(2, "one");
|
||||
*
|
||||
* assertEquals(map.size, 1);
|
||||
* assertEquals(map.get(1), undefined);
|
||||
* assertEquals(map.getReverse("one"), 2);
|
||||
* ```
|
||||
*/
|
||||
export class BidirectionalMap<K, V> extends Map<K, V> {
|
||||
#reverseMap: Map<V, K>;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param entries An iterable of key-value pairs for the initial entries.
|
||||
*/
|
||||
constructor(entries?: readonly (readonly [K, V])[] | null) {
|
||||
super();
|
||||
this.#reverseMap = new Map<V, K>();
|
||||
if (entries) {
|
||||
for (const [key, value] of entries) {
|
||||
this.set(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all entries.
|
||||
*
|
||||
* @example Usage
|
||||
* ```ts
|
||||
* import { BidirectionalMap } from "@std/data-structures/bidirectional-map";
|
||||
* import { assertEquals } from "@std/assert";
|
||||
*
|
||||
* const map = new BidirectionalMap([["one", 1]]);
|
||||
* map.clear();
|
||||
* assertEquals(map.size, 0);
|
||||
* ```
|
||||
*/
|
||||
override clear() {
|
||||
super.clear();
|
||||
this.#reverseMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new element with a specified key and value. If an entry with the
|
||||
* same key or value already exists, the entry will be updated.
|
||||
*
|
||||
* @param key The key to set.
|
||||
* @param value The value to associate with the key.
|
||||
*
|
||||
* @returns The instance.
|
||||
*
|
||||
* @example Usage
|
||||
* ```ts
|
||||
* import { BidirectionalMap } from "@std/data-structures/bidirectional-map";
|
||||
* import { assertEquals } from "@std/assert";
|
||||
*
|
||||
* const map = new BidirectionalMap();
|
||||
* map.set("one", 1);
|
||||
*
|
||||
* assertEquals(map.get("one"), 1);
|
||||
* assertEquals(map.getReverse(1), "one");
|
||||
* ```
|
||||
*/
|
||||
override set(key: K, value: V): this {
|
||||
const oldValue = super.get(key);
|
||||
if (oldValue !== undefined) {
|
||||
this.#reverseMap.delete(oldValue);
|
||||
}
|
||||
const oldKey = this.#reverseMap.get(value);
|
||||
if (oldKey !== undefined) {
|
||||
super.delete(oldKey);
|
||||
}
|
||||
super.set(key, value);
|
||||
this.#reverseMap.set(value, key);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key associated with the specified value. If no key is
|
||||
* associated with the specified value, `undefined` is returned.
|
||||
*
|
||||
* @param value The value to search for.
|
||||
* @returns The key associated with the specified value, or `undefined` if no
|
||||
* key is found.
|
||||
*
|
||||
* @example Usage
|
||||
* ```ts
|
||||
* import { BidirectionalMap } from "@std/data-structures/bidirectional-map";
|
||||
* import { assertEquals } from "@std/assert";
|
||||
*
|
||||
* const map = new BidirectionalMap([["one", 1]]);
|
||||
*
|
||||
* assertEquals(map.getReverse(1), "one");
|
||||
* ```
|
||||
*/
|
||||
getReverse(value: V): K | undefined {
|
||||
return this.#reverseMap.get(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the element with the specified key. If the element does not exist,
|
||||
* the instance remains unchanged.
|
||||
*
|
||||
* @param key The key of the element to remove.
|
||||
*
|
||||
* @returns `true` if an element in the instance existed and has been removed,
|
||||
* or `false` if the element does not exist.
|
||||
*
|
||||
* @example Usage
|
||||
* ```ts
|
||||
* import { BidirectionalMap } from "@std/data-structures/bidirectional-map";
|
||||
* import { assertEquals } from "@std/assert";
|
||||
*
|
||||
* const map = new BidirectionalMap([["one", 1]]);
|
||||
* map.delete("one");
|
||||
*
|
||||
* assertEquals(map.size, 0);
|
||||
* ```
|
||||
*/
|
||||
override delete(key: K): boolean {
|
||||
const value = super.get(key);
|
||||
if (value === undefined) return false;
|
||||
return super.delete(key) && this.#reverseMap.delete(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the element with the specified value. If the element does not
|
||||
* exist, the instance remains unchanged.
|
||||
*
|
||||
* @param value The value of the element to remove.
|
||||
* @returns `true` if an element in the instance existed and has been removed,
|
||||
* or `false` if the element does not exist.
|
||||
*
|
||||
* @example Usage
|
||||
* ```ts
|
||||
* import { BidirectionalMap } from "@std/data-structures/bidirectional-map";
|
||||
* import { assertEquals } from "@std/assert";
|
||||
*
|
||||
* const map = new BidirectionalMap([["one", 1]]);
|
||||
*
|
||||
* map.deleteReverse(1);
|
||||
*
|
||||
* assertEquals(map.get("one"), undefined);
|
||||
* assertEquals(map.getReverse(1), undefined);
|
||||
* assertEquals(map.size, 0);
|
||||
* ```
|
||||
*/
|
||||
deleteReverse(value: V): boolean {
|
||||
const key = this.#reverseMap.get(value);
|
||||
if (key === undefined) return false;
|
||||
return super.delete(key) && this.#reverseMap.delete(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an element with the specified value exists.
|
||||
*
|
||||
* @param value The value to search for.
|
||||
*
|
||||
* @returns `true` if an element with the specified value exists, otherwise
|
||||
* `false`.
|
||||
*
|
||||
* @example Usage
|
||||
* ```ts
|
||||
* import { BidirectionalMap } from "@std/data-structures/bidirectional-map";
|
||||
* import { assertEquals } from "@std/assert";
|
||||
*
|
||||
* const map = new BidirectionalMap([["one", 1]]);
|
||||
*
|
||||
* assertEquals(map.hasReverse(1), true);
|
||||
* ```
|
||||
*/
|
||||
hasReverse(value: V): boolean {
|
||||
return this.#reverseMap.has(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* A String value that is used in the creation of the default string description of an object.
|
||||
* Called by the built-in method `Object.prototype.toString`.
|
||||
*
|
||||
* @example Usage
|
||||
* ```ts
|
||||
* import { BidirectionalMap } from "@std/data-structures/bidirectional-map";
|
||||
* import { assertEquals } from "@std/assert";
|
||||
*
|
||||
* const map = new BidirectionalMap();
|
||||
* assertEquals(map.toString(), "[object BidirectionalMap]");
|
||||
* ```
|
||||
*/
|
||||
readonly [Symbol.toStringTag] = "BidirectionalMap";
|
||||
}
|
314
data_structures/bidirectional_map_test.ts
Normal file
314
data_structures/bidirectional_map_test.ts
Normal file
@ -0,0 +1,314 @@
|
||||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||
import { assert, assertEquals } from "@std/assert";
|
||||
import { BidirectionalMap } from "./bidirectional_map.ts";
|
||||
|
||||
Deno.test("BidirectionalMap is an instance of Map", () => {
|
||||
const biMap = new BidirectionalMap();
|
||||
assert(biMap instanceof Map);
|
||||
});
|
||||
|
||||
Deno.test("BidirectionalMap.set() removes values", () => {
|
||||
const map = new BidirectionalMap<number, string>();
|
||||
map.set(1, "one");
|
||||
map.set(2, "one");
|
||||
|
||||
assertEquals(map.size, 1);
|
||||
assertEquals(map.get(1), undefined);
|
||||
assertEquals(map.get(2), "one");
|
||||
assertEquals(map.getReverse("one"), 2);
|
||||
});
|
||||
|
||||
Deno.test("BidirectionalMap.clear()", () => {
|
||||
const map = new BidirectionalMap([
|
||||
["one", 1],
|
||||
["two", 2],
|
||||
["three", 3],
|
||||
]);
|
||||
map.clear();
|
||||
|
||||
assertEquals(map.size, 0);
|
||||
assertEquals(map.has("one"), false);
|
||||
assertEquals(map.hasReverse(1), false);
|
||||
});
|
||||
|
||||
Deno.test("BidirectionalMap.delete() removes the key-value pair", () => {
|
||||
const map = new BidirectionalMap([
|
||||
["one", 1],
|
||||
["two", 2],
|
||||
["three", 3],
|
||||
]);
|
||||
|
||||
assertEquals(map.delete("two"), true);
|
||||
assertEquals(map.size, 2);
|
||||
assertEquals(map.has("two"), false);
|
||||
assertEquals(map.hasReverse(2), false);
|
||||
});
|
||||
|
||||
Deno.test(
|
||||
"BidirectionalMap.delete() returns false if the key does not exist",
|
||||
() => {
|
||||
const map = new BidirectionalMap([
|
||||
["one", 1],
|
||||
["two", 2],
|
||||
["three", 3],
|
||||
]);
|
||||
|
||||
assertEquals(map.delete("four"), false);
|
||||
assertEquals(map.size, 3);
|
||||
},
|
||||
);
|
||||
|
||||
Deno.test("BidirectionalMap.deleteReverse() removes the value-key pair", () => {
|
||||
const map = new BidirectionalMap([
|
||||
["one", 1],
|
||||
["two", 2],
|
||||
["three", 3],
|
||||
]);
|
||||
|
||||
assertEquals(map.deleteReverse(2), true);
|
||||
assertEquals(map.size, 2);
|
||||
assertEquals(map.has("two"), false);
|
||||
assertEquals(map.hasReverse(2), false);
|
||||
});
|
||||
|
||||
Deno.test(
|
||||
"BidirectionalMap.deleteReverse() returns false if the value does not exist",
|
||||
() => {
|
||||
const map = new BidirectionalMap([
|
||||
["one", 1],
|
||||
["two", 2],
|
||||
["three", 3],
|
||||
]);
|
||||
|
||||
assertEquals(map.deleteReverse(4), false);
|
||||
assertEquals(map.size, 3);
|
||||
},
|
||||
);
|
||||
|
||||
Deno.test(
|
||||
"BidirectionalMap.forEach() iterates over the key-value pairs",
|
||||
() => {
|
||||
const map = new BidirectionalMap([
|
||||
["one", 1],
|
||||
["two", 2],
|
||||
["three", 3],
|
||||
]);
|
||||
const result: [string, number][] = [];
|
||||
map.forEach((value, key) => {
|
||||
result.push([key, value]);
|
||||
});
|
||||
|
||||
assertEquals(result, [
|
||||
["one", 1],
|
||||
["two", 2],
|
||||
["three", 3],
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
Deno.test(
|
||||
"BidirectionalMap.get() returns the value associated with the key",
|
||||
() => {
|
||||
const map = new BidirectionalMap([
|
||||
["one", 1],
|
||||
["two", 2],
|
||||
["three", 3],
|
||||
]);
|
||||
|
||||
assertEquals(map.get("one"), 1);
|
||||
assertEquals(map.get("two"), 2);
|
||||
assertEquals(map.get("three"), 3);
|
||||
},
|
||||
);
|
||||
|
||||
Deno.test(
|
||||
"BidirectionalMap.getReverse() returns the key associated with the value",
|
||||
() => {
|
||||
const map = new BidirectionalMap([
|
||||
["one", 1],
|
||||
["two", 2],
|
||||
["three", 3],
|
||||
]);
|
||||
|
||||
assertEquals(map.getReverse(1), "one");
|
||||
assertEquals(map.getReverse(2), "two");
|
||||
assertEquals(map.getReverse(3), "three");
|
||||
},
|
||||
);
|
||||
|
||||
Deno.test("BidirectionalMap.has() returns true if the key exists", () => {
|
||||
const map = new BidirectionalMap([
|
||||
["one", 1],
|
||||
["two", 2],
|
||||
["three", 3],
|
||||
]);
|
||||
|
||||
assertEquals(map.has("one"), true);
|
||||
assertEquals(map.has("two"), true);
|
||||
assertEquals(map.has("three"), true);
|
||||
assertEquals(map.has("four"), false);
|
||||
});
|
||||
|
||||
Deno.test(
|
||||
"BidirectionalMap.hasReverse() returns true if the value exists",
|
||||
() => {
|
||||
const map = new BidirectionalMap([
|
||||
["one", 1],
|
||||
["two", 2],
|
||||
["three", 3],
|
||||
]);
|
||||
|
||||
assertEquals(map.hasReverse(1), true);
|
||||
assertEquals(map.hasReverse(2), true);
|
||||
assertEquals(map.hasReverse(3), true);
|
||||
assertEquals(map.hasReverse(4), false);
|
||||
},
|
||||
);
|
||||
|
||||
Deno.test("BidirectionalMap.set() adds a new key-value pair", () => {
|
||||
const map = new BidirectionalMap();
|
||||
map.set("one", 1);
|
||||
map.set("two", 2);
|
||||
map.set("three", 3);
|
||||
|
||||
assertEquals(map.size, 3);
|
||||
assertEquals(map.get("one"), 1);
|
||||
assertEquals(map.get("two"), 2);
|
||||
assertEquals(map.get("three"), 3);
|
||||
assertEquals(map.getReverse(1), "one");
|
||||
assertEquals(map.getReverse(2), "two");
|
||||
assertEquals(map.getReverse(3), "three");
|
||||
});
|
||||
|
||||
Deno.test(
|
||||
"BidirectionalMap.set() updates the value if the key already exists",
|
||||
() => {
|
||||
const map = new BidirectionalMap();
|
||||
map.set("one", 1);
|
||||
map.set("one", 2);
|
||||
|
||||
assertEquals(map.size, 1);
|
||||
assertEquals(map.get("one"), 2);
|
||||
assertEquals(map.getReverse(2), "one");
|
||||
},
|
||||
);
|
||||
|
||||
Deno.test(
|
||||
"BidirectionalMap.set() twice with the same key updates the value",
|
||||
() => {
|
||||
const map = new BidirectionalMap();
|
||||
map.set("key", "value");
|
||||
map.set("key", "secondValue");
|
||||
|
||||
assertEquals(map.get("key"), "secondValue");
|
||||
assertEquals(map.getReverse("value"), undefined);
|
||||
assertEquals(map.getReverse("secondValue"), "key");
|
||||
},
|
||||
);
|
||||
|
||||
Deno.test(
|
||||
"BidirectionalMap.set() returns the BidirectionalMap instance",
|
||||
() => {
|
||||
const map = new BidirectionalMap();
|
||||
const result = map.set(1, "one");
|
||||
|
||||
assertEquals(result, map);
|
||||
},
|
||||
);
|
||||
|
||||
Deno.test("BidirectionalMap.size returns the number of key-value pairs", () => {
|
||||
const map = new BidirectionalMap([
|
||||
["one", 1],
|
||||
["two", 2],
|
||||
["three", 3],
|
||||
["four", 4],
|
||||
]);
|
||||
|
||||
assertEquals(map.size, 4);
|
||||
});
|
||||
|
||||
Deno.test(
|
||||
"BidirectionalMap.entries() returns an iterator of key-value pairs",
|
||||
() => {
|
||||
const map = new BidirectionalMap([
|
||||
["one", 1],
|
||||
["two", 2],
|
||||
["three", 3],
|
||||
]);
|
||||
const result = Array.from(map.entries());
|
||||
|
||||
assertEquals(result, [
|
||||
["one", 1],
|
||||
["two", 2],
|
||||
["three", 3],
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
Deno.test("BidirectionalMap.keys() returns an iterator of keys", () => {
|
||||
const map = new BidirectionalMap([
|
||||
["one", 1],
|
||||
["two", 2],
|
||||
["three", 3],
|
||||
]);
|
||||
const result = Array.from(map.keys());
|
||||
|
||||
assertEquals(result, ["one", "two", "three"]);
|
||||
});
|
||||
|
||||
Deno.test("BidirectionalMap.values() returns an iterator of values", () => {
|
||||
const map = new BidirectionalMap([
|
||||
["one", 1],
|
||||
["two", 2],
|
||||
["three", 3],
|
||||
]);
|
||||
const result = Array.from(map.values());
|
||||
|
||||
assertEquals(result, [1, 2, 3]);
|
||||
});
|
||||
|
||||
Deno.test(
|
||||
"BidirectionalMap[Symbol.iterator]() returns an iterator of key-value pairs",
|
||||
() => {
|
||||
const map = new BidirectionalMap([
|
||||
["one", 1],
|
||||
["two", 2],
|
||||
["three", 3],
|
||||
]);
|
||||
const result = Array.from(map[Symbol.iterator]());
|
||||
|
||||
assertEquals(result, [
|
||||
["one", 1],
|
||||
["two", 2],
|
||||
["three", 3],
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
Deno.test("BidirectionalMap[Symbol.toStringTag] is 'BidirectionalMap'", () => {
|
||||
const map = new BidirectionalMap();
|
||||
|
||||
assertEquals(map.toString(), "[object BidirectionalMap]");
|
||||
assertEquals(
|
||||
Object.prototype.toString.call(map),
|
||||
"[object BidirectionalMap]",
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test(
|
||||
"BidirectionalMap.get() have the same references as BidirectionalMap.getReverse()",
|
||||
() => {
|
||||
const key = { name: "Pīwakawaka" };
|
||||
const value = { favourite: false };
|
||||
const map = new BidirectionalMap([[key, value]]);
|
||||
map.get(key)!.favourite = true;
|
||||
map.getReverse(value)!.name = "Tūī";
|
||||
assertEquals(key.name, "Tūī");
|
||||
|
||||
assertEquals(value.favourite, true);
|
||||
assertEquals(map.get(key)!.favourite, true);
|
||||
assertEquals(map.getReverse(value)!.name, "Tūī");
|
||||
const entries = Array.from(map.entries());
|
||||
assertEquals(entries, [[{ name: "Tūī" }, { favourite: true }]]);
|
||||
},
|
||||
);
|
@ -3,6 +3,7 @@
|
||||
"version": "1.0.2",
|
||||
"exports": {
|
||||
".": "./mod.ts",
|
||||
"./bidirectional-map": "./bidirectional_map.ts",
|
||||
"./binary-heap": "./binary_heap.ts",
|
||||
"./binary-search-tree": "./binary_search_tree.ts",
|
||||
"./comparators": "./comparators.ts",
|
||||
|
@ -25,6 +25,7 @@
|
||||
* @module
|
||||
*/
|
||||
|
||||
export * from "./bidirectional_map.ts";
|
||||
export * from "./binary_heap.ts";
|
||||
export * from "./binary_search_tree.ts";
|
||||
export * from "./comparators.ts";
|
||||
|
Loading…
Reference in New Issue
Block a user