Skip to content

Commit

Permalink
feat(data-structures/unstable): `@std/data-structures/bidirectional-m…
Browse files Browse the repository at this point in the history
…ap` (denoland#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 <[email protected]>
  • Loading branch information
Liam-Tait and iuioiua authored Sep 5, 2024
1 parent 67e9cfa commit 7c0e917
Show file tree
Hide file tree
Showing 4 changed files with 539 additions and 0 deletions.
223 changes: 223 additions & 0 deletions data_structures/bidirectional_map.ts
Original file line number Diff line number Diff line change
@@ -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";
}
Loading

0 comments on commit 7c0e917

Please sign in to comment.