@rimbu/graph
Version:
Immutable Graph data structures for TypeScript
564 lines (554 loc) • 20.8 kB
text/typescript
import type { Token } from '@rimbu/base';
import type { RMap } from '@rimbu/collection-types';
import type {
ArrayNonEmpty,
OptLazy,
OptLazyOr,
RelatedTo,
} from '@rimbu/common';
import type { Reducer, Stream, StreamSource, Streamable } from '@rimbu/stream';
import type { VariantValuedGraphBase } from '@rimbu/graph/custom';
import type {
GraphConnect,
GraphConnectNonEmpty,
Link,
ValuedGraphElement,
VariantGraphBase,
WithGraphValues,
} from '../../../common/index.mjs';
export interface ValuedGraphBase<
N,
V,
Tp extends ValuedGraphBase.Types = ValuedGraphBase.Types,
> extends VariantValuedGraphBase<N, V, Tp>,
GraphConnect<N, V, Tp> {
/**
* Returns the `context` associated to this collection instance.
*/
readonly context: WithGraphValues<Tp, N, V>['context'];
/**
* Returns a Map containing the nodes and connection values reachable from given `node1` node as keys,
* and their corresponding values.
* @param node1 - the node from which to find the connections
* @example
* ```ts
* const g = ArrowValuedGraphHashed.of([1, 2, 'a'], [2, 3, 'b'])
* g.getConnectionsFrom(1) // => HashMap(2 -> 'a')
* g.getConnectionsFrom(3) // => HashMap()
* ```
*/
getConnectionsFrom<UN = N>(
node1: RelatedTo<N, UN>
): WithGraphValues<Tp, N, V>['linkConnections'];
/**
* Returns the graph where given nodes `node1` and `node2` are connected with
* the given `value`.
* @param node1 - the first node
* @param node2 - the second node
* @param value - the connection value
* @example
* ```ts
* const g = ArrowValuedGraphHashed.of([1, 2, 'a'], [2, 3, 'b'])
* g.connect(3, 1, 'c').stream().toArray()
* // => [[1, 2, 'a'], [2, 3, 'b'], [3, 1, 'c']]
* ```
*/
connect(node1: N, node2: N, value: V): WithGraphValues<Tp, N, V>['nonEmpty'];
/**
* Returns the graph with the connection between given `node1` and `node2` modified according to given `options`.
* @param node1 - the first connection node
* @param node2 - the second connection node
* @param options - an object containing the following information:<br/>
* - ifNew: (optional) if the given connection is not present in the collection, this value or function will be used
* to generate a new connection. If a function returning the token argument is given, no new entry is created.<br/>
* - ifExists: (optional) if a value is associated with given connection, this function is called with the given value
* to return a new value. As a second argument, a `remove` token is given. If the function returns this token, the current
* connection is removed.
* @example
* ```ts
* const g = ArrowValuedGraphHashed.of([1, 2, 'a'], [2, 3, 'b'])
* g.modifyAt(3, 4, { ifNew: 'c' }).toArray()
* // => [[1, 2, 'a'], [2, 3, 'b'], [3, 4, 'c']]
* g.modifyAt(3, 4, { ifNew: (none) => 1 < 2 ? none : 'c' }).toArray()
* // => [[1, 2, 'a'], [2, 3, 'b']]
* g.modifyAt(1, 2, { ifExists: () => 'c' }).toArray()
* // => [[1, 2, 'c'], [2, 3, 'b']]
* g.modifyAt(1, 2, { ifExists: (v) => v + 'z' }).toArray()
* // => [[1, 2, 'az'], [2, 3, 'b']]
* g.modifyAt(2, 3, { ifExists: (v, remove) => v === 'a' ? v : remove }).toArray()
* // => [[1, 2, 'a']]
* ```
*/
modifyAt(
node1: N,
node2: N,
options: {
ifNew?: OptLazyOr<V, Token>;
ifExists?: ((value: V, remove: Token) => V | Token) | V;
}
): WithGraphValues<Tp, N, V>['normal'];
/**
* Returns a builder object containing the entries of this collection.
* @example
* ```ts
* const builder: ArrowValuedGraphHashed.Builder<number, string> = ArrowValuedGraphHashed.of([1, 2, 'a'], [2, 3, 'b']).toBuilder()
* ```
*/
toBuilder(): WithGraphValues<Tp, N, V>['builder'];
}
export namespace ValuedGraphBase {
export interface NonEmpty<
N,
V,
Tp extends ValuedGraphBase.Types = ValuedGraphBase.Types,
> extends VariantValuedGraphBase.NonEmpty<N, V, Tp>,
Omit<
GraphConnectNonEmpty<N, V, Tp>,
keyof VariantValuedGraphBase.NonEmpty<any, any, any>
>,
Omit<
ValuedGraphBase<N, V, Tp>,
| keyof VariantValuedGraphBase.NonEmpty<any, any, any>
| keyof GraphConnectNonEmpty<any, any, any>
>,
Streamable.NonEmpty<ValuedGraphElement<N, V>> {
/**
* Returns a non-empty `Stream` containing all graph elements of this collection as single tuples for isolated nodes
* and 3-valued tuples containing the source node, target node, and connection value for connections.
* @example
* ```ts
* ArrowValuedGraphHashed.of([1, 2, 'a'], [2, 3, 'b']).stream().toArray()
* // => [[1, 2, 'a'], [2, 3, 'b']]
* ```
*/
stream(): Stream.NonEmpty<ValuedGraphElement<N, V>>;
}
export interface Builder<
N,
V,
Tp extends ValuedGraphBase.Types = ValuedGraphBase.Types,
> {
/**
* Returns the `context` associated to this collection instance.
*/
readonly context: WithGraphValues<Tp, N, V>['context'];
/**
* Returns true if there are no entries in the builder.
* @example
* ```ts
* ArrowValuedGraphHashed
* .of([[1, 2, 'a'], [2, 3, 'b']])
* .toBuilder()
* .isEmpty
* // => false
* ```
*/
readonly isEmpty: boolean;
/**
* Returns the amount of nodes in the graph.
* @example
* ```ts
* ArrowValuedGraphHashed
* .of([[1, 2, 'a'], [2, 3, 'b']])
* .toBuilder()
* .nodeSize
* // => 3
* ```
*/
readonly nodeSize: number;
/**
* Returns the amount of connections in the graph.
* @example
* ```ts
* ArrowValuedGraphHashed
* .of([[1, 2, 'a'], [2, 3, 'b']])
* .toBuilder()
* .connectionsSize
* // => 2
* ```
*/
readonly connectionSize: number;
/**
* Returns true if the graph contains the given `node`.
* @param node - the node to search
* @example
* ```ts
* const b = ArrowValuedGraphHashed
* .of([[1, 2, 'a'], [2, 3, 'b']])
* .toBuilder()
* b.hasNode(1) // => true
* b.hasNode(6) // => false
* ```
*/
hasNode<UN = N>(node: RelatedTo<N, UN>): boolean;
/**
* Returns true if the graph has a connection between given nodes `node1` and `node2`.
* @param node1 - the first connection node
* @param node2 - the second connection node
* @example
* ```ts
* const b = ArrowValuedGraphHashed
* .of([[1, 2, 'a'], [2, 3, 'b']])
* .toBuilder()
* b.hasConnection(1, 2) // => true
* b.hasConnection(6, 7) // => false
* ```
*/
hasConnection<UN = N>(
node1: RelatedTo<N, UN>,
node2: RelatedTo<N, UN>
): boolean;
/**
* Returns the value associated with the connection between `node1` and `node2`, or given `otherwise` value if the key is not in the collection.
* @param node1 - the first connection node
* @param node2 - the second connection node
* @param otherwise - (default: undefined) the fallback value to return if the connection is not present
* @example
* ```ts
* const b = ArrowValuedGraphHashed
* .of([[1, 2, 'a'], [2, 3, 'b']])
* .toBuilder()
* m.getValue(2, 3) // => 'b'
* m.getValue(3, 4) // => undefined
* m.getValue(2, 3, 'none') // => 'b'
* m.getValue(3, 4, 'none') // => 'none'
* ```
*/
getValue<UN = N>(
node1: RelatedTo<N, UN>,
node2: RelatedTo<N, UN>
): V | undefined;
getValue<UN, O>(
node1: RelatedTo<N, UN>,
node2: RelatedTo<N, UN>,
otherwise: OptLazy<O>
): V | O;
/**
* Adds the given `node` to the graph.
* @param node - the node to add
* @returns true if the node was not already present
* @example
* ```ts
* const b = ArrowValuedGraphHashed
* .of([[1, 2, 'a'], [2, 3, 'b']])
* .toBuilder()
* b.addNode(6) // => true
* b.addNode(1) // => false
* ```
*/
addNode(node: N): boolean;
/**
* Adds the given `nodes` to the builder.
* @param nodes - a `StreamSource` containing the nodes to add
* @returns true if any of the nodes was not yet present
* @example
* ```ts
* const b = ArrowValuedGraphHashed
* .of([[1, 2, 'a'], [2, 3, 'b']])
* .toBuilder()
* b.addNodes([3, 4, 5]) // => true
* b.addNodes([1, 2]) // => false
* ```
*/
addNodes(nodes: StreamSource<N>): boolean;
/**
* Adds the given `element` graph element to the graph.
* @param element - an object representing either a single node or a valued connection
* @returns true if the element was not already in the graph
* @example
* ```ts
* const b = ArrowValuedGraphHashed
* .of([[1, 2, 'a'], [2, 3, 'b']])
* .toBuilder()
* b.addGraphElement([4]) // => true
* b.addGraphElement([3, 1, 'c']) // => true
* b.addGraphElement([1, 2, 'a']) // => false
* ```
*/
addGraphElement(element: ValuedGraphElement<N, V>): boolean;
/**
* Adds the graph elements in the given `elements` StreamSource to the graph.
* @param elements - a `StreamSource` containing elements that represent either a single node or a valued connection
* @returns true if the graph has changed
* @example
* ```ts
* const b = ArrowValuedGraphHashed
* .of([[1, 2, 'a'], [2, 3, 'b']])
* .toBuilder()
* b.addGraphElements([[4], [5]]) // => true
* b.addGraphElements([[3, 1, 'c'], [1]]) // => true
* b.addGraphElements([[1, 2, 'a'], [1]]) // => false
* ```
*/
addGraphElements(elements: StreamSource<ValuedGraphElement<N, V>>): boolean;
/**
* Removes the given `node`, and any of its connections, from the graph.
* @param node - the node to remove
* @returns true if the node was present
* @example
* ```ts
* const b = ArrowValuedGraphHashed
* .of([[1, 2, 'a'], [2, 3, 'b']])
* .toBuilder()
* b.removeNode(1) // => true
* b.removeNode(6) // => false
* ```
*/
removeNode<UN = N>(node: RelatedTo<N, UN>): boolean;
/**
* Removes the given `nodes`, and any of their connections, from the graph.
* @param nodes - a `StreamSource` containing the nodes to remove
* @returns true if any of the nodes were present
* @example
* ```ts
* const b = ArrowValuedGraphHashed
* .of([[1, 2, 'a'], [2, 3, 'b']])
* .toBuilder()
* b.removeNodes([1, 6, 7]) // => true
* b.removeNodes([6, 7]) // => false
* ```
*/
removeNodes<UN = N>(nodes: StreamSource<RelatedTo<N, UN>>): boolean;
/**
* Adds a connection between `node1` and `node2` to the graph with given `value`.
* @param node1 - the first connection node
* @param node2 - the second connection node
* @param value - the connection value
* @returns true if the connection did not exist, or if the given value differs from the previous value
* @example
* ```ts
* const b = ArrowValuedGraphHashed
* .of([[1, 2, 'a'], [2, 3, 'b']])
* .toBuilder()
* b.connect(3, 1, 'c') // => true
* b.connect(1, 2, 'a') // => false
* b.connect(1, 2, 'z') // => true
* ```
*/
connect(node1: N, node2: N, value: V): boolean;
/**
* Adds the connections in given `connections` `StreamSource` to the graph.
* @param connections - a `StreamSource` containing the connection definitions to add
* @returns true if any of the connections changed the collection
* @example
* ```ts
* const b = ArrowValuedGraphHashed
* .of([[1, 2, 'a'], [2, 3, 'b']])
* .toBuilder()
* b.connectAll([[1, 2, 'a'], [3, 1, 'c']]) // => true
* b.connectAll([[1, 2, 'a']]) // => false
* ```
*/
connectAll(
connections: StreamSource<WithGraphValues<Tp, N, V>['link']>
): boolean;
/**
* Modifies the graph at the connection between given `node1` and `node2` modified according to given `options`.
* @param node1 - the first connection node
* @param node2 - the second connection node
* @param options - an object containing the following information:<br/>
* - ifNew: (optional) if the given connection is not present in the collection, this value or function will be used
* to generate a new connection. If a function returning the token argument is given, no new entry is created.<br/>
* - ifExists: (optional) if a value is associated with given connection, this function is called with the given value
* to return a new value. As a second argument, a `remove` token is given. If the function returns this token, the current
* connection is removed.
* @returns true if the collection changed
* @example
* ```ts
* const b = ArrowValuedGraphHashed
* .of([[1, 2, 'a'], [2, 3, 'b']])
* .toBuilder()
* b.modifyAt(3, 4, { ifNew: 'c' }) // => true
* g.modifyAt(4, 5, { ifNew: (none) => 1 < 2 ? none : 'c' }) // => false
* g.modifyAt(1, 2, { ifNew: 'a' }) // => false
* g.modifyAt(1, 2, { ifExists: () => 'c' }) // => false
* g.modifyAt(1, 2, { ifExists: (v) => v + 'z' }) // => true
* g.modifyAt(2, 3, { ifExists: (v, remove) => v === 'a' ? v : remove })
* // => true
* ```
*/
modifyAt(
node1: N,
node2: N,
options: {
ifNew?: OptLazyOr<V, Token>;
ifExists?: (value: V, remove: Token) => V | Token;
}
): boolean;
/**
* Removes the connection between given `node1` and `node2` if the connection was present.
* @param node1 - the first connection node
* @param node2 - the second connection node
* @returns true if the collection changed
* @example
* ```ts
* const b = ArrowValuedGraphHashed
* .of([[1, 2, 'a'], [2, 3, 'b']])
* .toBuilder()
* b.disconnect(1, 2) // => true
* b.disconnect(3, 4) // => false
* ```
*/
disconnect<UN = N>(
node1: RelatedTo<N, UN>,
node2: RelatedTo<N, UN>
): boolean;
/**
* Removes all connections from the given `connections` `StreamSource` from the graph.
* @param connections - a `StreamSource` containing the tuples defining the nodes of the connections to remove
* @returns true if the collection changed
* @example
* ```ts
* const b = ArrowValuedGraphHashed
* .of([[1, 2, 'a'], [2, 3, 'b']])
* .toBuilder()
* b.disconnectAll([[1, 2], [3, 4]]) // => true
* b.disconnectAll([[3, 4], [5, 6]]) // => false
* ```
*/
disconnectAll<UN = N>(
connections: StreamSource<Link<RelatedTo<N, UN>>>
): boolean;
/**
* Returns an immutable graph containing the nodes and connections of this builder.
* @example
* ```ts
* const b = ArrowValuedGraphHashed
* .of([[1, 2, 'a'], [2, 3, 'b']])
* .toBuilder()
* const g: ArrowValuedGraphHashed<number, string> = b.build()
* ```
*/
build(): WithGraphValues<Tp, N, V>['normal'];
/**
* Returns an immutable graph containing the nodes and connections of this builder, where the values are mapped
* using the given `mapFun` function.
* @param mapFun - a function taking the value
* @example
* ```ts
* const b = ArrowValuedGraphHashed
* .of([[1, 2, 'a'], [2, 3, 'b']])
* .toBuilder()
* const g: ArrowValuedGraphHashed<number, string> = b.buildMapValues(v => v.toUpperCase())
* ```
*/
buildMapValues<V2>(
mapFun: (value: V, node1: N, node2: N) => V2
): WithGraphValues<Tp, N, V2>['normal'];
}
export interface Factory<Tp extends ValuedGraphBase.Types, UN = unknown> {
/**
* Returns the (singleton) empty instance of this type and context with given key and value types.
* @example
* ```ts
* ArrowValuedGraphHashed.empty<number, string>() // => ArrowValuedGraphHashed<number, string>
* ArrowValuedGraphHashed.empty<string, boolean>() // => ArrowValuedGraphHashed<string, boolean>
* ```
*/
empty<N extends UN, V>(): WithGraphValues<Tp, N, V>['normal'];
/**
* Returns an immutable valued Graph instance containing the graph elements from the given
* `graphElements`.
* @param graphElements - a non-empty array of graph elements that are either a single tuple containing a node, or a triplet containing
* two connection nodes and the connection value.
* @example
* ```ts
* ArrowValuedGraphHashed.of([1], [2], [3, 4, 'a']) // => ArrowValuedGraphHashed.NonEmpty<number, string>
* ```
*/
of<N extends UN, V>(
...graphElements: ArrayNonEmpty<ValuedGraphElement<N, V>>
): WithGraphValues<Tp, N, V>['nonEmpty'];
/**
* Returns an immutable valued Graph, containing the graph elements from each of the
* given `sources`.
* @param sources - an array of `StreamSource` instances containing graph elements to add
* @example
* ```ts
* ArrowValuedGraphHashed.from([[1], [2]], [[3, 4, 'c']]) // => ArrowValuedGraphHashed.NonEmpty<number, string>
* ```
*/
from<N extends UN, V>(
...sources: ArrayNonEmpty<StreamSource.NonEmpty<ValuedGraphElement<N, V>>>
): WithGraphValues<Tp, N, V>['nonEmpty'];
from<N extends UN, V>(
...sources: ArrayNonEmpty<StreamSource<ValuedGraphElement<N, V>>>
): WithGraphValues<Tp, N, V>['normal'];
/**
* Returns an empty builder instance.
* @example
* ```ts
* ArrowValuedGraphHashed.builder<number, string>() // => ArrowValuedGraphHashed.Builder<number, string>
* ```
*/
builder<N extends UN, V>(): WithGraphValues<Tp, N, V>['builder'];
/**
* Returns a `Reducer` that adds valued received graph elements to a ValuedGraph and returns the ValuedGraph as a result. When a `source` is given,
* the reducer will first create a graph from the source, and then add graph elements to it.
* @param source - (optional) an initial source of graph elements to add to
* @example
* ```ts
* const someSource: ValuedGraphElement<number, string>[] = [[1, 2, 'a'], [3], [5]];
* const result = Stream.of([1, 3, 'b'], [4, 3, 'c']).reduce(ArrowGraphSorted.reducer(someSource))
* result.toArray() // => [[1, 2, 'a'], [1, 3, 'b'], [4, 3, 'c'], [5]]
* ```
* @note uses a builder under the hood. If the given `source` is a ValuedGraph in the same context, it will directly call `.toBuilder()`.
*/
reducer<N extends UN, V>(
source?: StreamSource.NonEmpty<ValuedGraphElement<N, V>>
): Reducer<ValuedGraphElement<N, V>, WithGraphValues<Tp, N, V>['normal']>;
}
export interface Context<
UN,
Tp extends ValuedGraphBase.Types = ValuedGraphBase.Types,
> extends Factory<Tp, UN> {
readonly _fixedType: UN;
/**
* A string tag defining the specific collection type
* @example
* ```ts
* ArrowValuedGraphHashed.defaultContext().typeTag // => 'ArrowValuedGraphHashed'
* ```
*/
readonly typeTag: string;
/**
* The `context` instance used to create internal link maps
*/
readonly linkMapContext: WithGraphValues<Tp, UN, unknown>['linkMapContext'];
/**
* The `context` instance used to create internal connection maps
*/
readonly linkConnectionsContext: WithGraphValues<
Tp,
UN,
unknown
>['linkConnectionsContext'];
/**
* Returns true if the graphs created by this context are arrow (directed) graphs.
*/
readonly isDirected: boolean;
}
/**
* Utility interface that provides higher-kinded types for this collection.
*/
export interface Types extends VariantValuedGraphBase.Types {
readonly normal: ValuedGraphBase<this['_N'], this['_V']> &
VariantGraphBase<this['_N'], this['_V']>;
readonly nonEmpty: ValuedGraphBase.NonEmpty<this['_N'], this['_V']>;
readonly context: ValuedGraphBase.Context<this['_N']>;
readonly builder: ValuedGraphBase.Builder<this['_N'], this['_V']>;
readonly linkMap: RMap<this['_N'], RMap<this['_N'], this['_V']>>;
readonly linkMapNonEmpty: RMap.NonEmpty<
this['_N'],
RMap<this['_N'], this['_V']>
>;
readonly linkMapContext: RMap.Context<this['_N']>;
readonly linkConnectionsContext: RMap.Context<this['_N']>;
readonly linkMapBuilder: RMap.Builder<
this['_N'],
RMap.Builder<this['_N'], this['_V']>
>;
readonly linkConnectionsBuilder: RMap.Builder<this['_N'], this['_V']>;
readonly linkConnections: RMap<this['_N'], this['_V']>;
}
}