joshkaposh-graph
Version:
A collection of general purpose graph data structures and traversal algorithms
1,141 lines (958 loc) • 36.9 kB
text/typescript
import { DoubleEndedIterator, Iterator, done, iter, range } from 'joshkaposh-iterator';
import { is_some, type Option } from 'joshkaposh-option'
import { type EdgeType, type Node, type Edge, type GraphIx, DIRECTIONS, Direction, Incoming, Outgoing, Directed, Undirected, createNode, createEdge, index_twice } from './shared';
import { assert_some } from 'joshkaposh-iterator/src/util';
import { capacity, extend, reserve, swap, swap_remove } from '../array-helpers';
import { enumerate } from '../util';
import { type EdgeId, type EdgeWeight, type GraphBase, type NodeId, type GraphImpl, type Visitable, type VisitMap, EdgeRef, NodeRef } from '../visit';
import type { FixedBitSet } from 'fixed-bit-set';
import { VisitorFbs } from '../visit/visitor';
import { umax } from '../util'
export type DiGraph<N, E, Ix extends GraphIx = 32> = Graph<N, E, Directed, Ix>;
export function DiGraph<N, E>() {
return Graph.directed<N, E>()
}
export type UnGraph<N, E, Ix extends GraphIx = 32> = Graph<N, E, Undirected, Ix>;
export function UnGraph<N, E>() {
return Graph.undirected<N, E>()
}
export class Graph<N, E, Ty extends EdgeType, Ix extends GraphIx = 32> implements GraphImpl<number, number, N, E>, Visitable<number> {
//! typescript types, never added to graph
readonly NodeId!: number;
readonly EdgeId!: number;
readonly NodeWeight!: N;
readonly EdgeWeight!: E;
readonly NodeEnd: number;
readonly EdgeEnd: number;
//internal data
#ty: Ty;
#ix: Ix;
__nodes: Node<N>[];
__edges: Edge<E>[];
constructor(ty: Ty = Directed as Ty, ix: Ix = 32 as Ix, nodes: Node<N>[] = [], edges: Edge<E>[] = []) {
this.__nodes = nodes;
this.__edges = edges;
this.#ty = ty;
this.#ix = ix;
const max = umax(ix)
this.NodeEnd = max;
this.EdgeEnd = max;
}
static undirected<N, E, Ix extends GraphIx = 32>(size: Ix = 32 as Ix): Graph<N, E, Undirected, Ix> {
return new Graph(Undirected, size, [], [])
}
static directed<N, E, Ix extends GraphIx = 32>(size: Ix = 32 as Ix): Graph<N, E, Directed, Ix> {
return new Graph(Directed, size, [], [])
}
static with_capacity<N, E, Ty extends EdgeType, Ix extends GraphIx = 32>(ty: Ty = Directed as Ty, ix: Ix = 32 as Ix, _nodes: number, _edges: number): Graph<N, E, Ty, Ix> {
return new Graph(ty, ix, [], [])
}
to_node_index(n: NodeId<this>): number {
return n as number;
}
from_node_index(n: NodeId<this>): number {
return n as number;
}
to_edge_index(id: number): number {
return id
}
from_edge_index(ix: number): number {
return ix;
}
ty(): Ty {
return this.#ty;
}
visit_map(): VisitMap<number> {
return new VisitorFbs(this.node_count()) as VisitMap<number>
}
reset_map(map: VisitMap<number> & FixedBitSet): void {
map.clear();
map.grow(this.node_count());
}
clone(): Graph<N, E, Ty, Ix> {
return new Graph<N, E, Ty, Ix>(this.#ty, this.#ix, structuredClone(this.__nodes), structuredClone(this.__edges))
}
node_count(): number {
return this.__nodes.length;
}
edge_count(): number {
return this.__edges.length;
}
is_directed(): boolean {
return this.#ty.is_directed();
}
add_node(weight: N): number {
const max = this.NodeEnd;
const node = createNode(weight, [max, max]);
const node_idx = this.__nodes.length;
if (node_idx >= max) {
throw new Error(`Graph nodes length ${node_idx} cannot exceed ${max}`)
}
this.__nodes.push(node);
return node_idx
}
node_bound(): number {
return this.node_count();
}
edge_bound(): number {
return this.edge_count();
}
node_weight(a: number): Option<N> {
return this.__nodes[a].weight;
}
set_node_weight(a: number, w: N) {
this.__nodes[a].weight = w;
}
add_edge(a: number, b: number, weight: E): number {
const edge_idx = this.__edges.length;
const max = this.EdgeEnd;
if (edge_idx >= max) {
throw new Error(`Graph edges length ${edge_idx} cannot exceed ${max}`)
}
const pair = index_twice(this.__nodes, a, b);
const edge = createEdge(weight, [max, max], [a, b])
if (!is_some(pair)) {
// none
throw new Error('Graph::add_edge: node indices out of bounds')
} else if (!Array.isArray(pair.value)) {
// one
const an = pair.value!;
edge.__next = structuredClone(an.__next);
an.__next[0] = edge_idx;
an.__next[1] = edge_idx;
} else {
// both
const [an, bn] = pair.value;
edge.__next = [an.__next[0], bn.__next[1]];
an.__next[0] = edge_idx;
bn.__next[1] = edge_idx;
}
this.__edges.push(edge);
return edge_idx;
}
update_edge(a: number, b: number, weight: E): number {
const ix = this.find_edge(a, b);
if (is_some(ix)) {
this.__edges[ix].weight = weight;
return ix;
}
// since no edges exists, add it
return this.add_edge(a, b, weight)
}
edge_ref(e: number): Option<EdgeReference<E>> {
if (!this.__edges[e]) {
return null
}
const ed = this.__edges[e];
return new EdgeReference(e, ed.node, ed.weight)
}
edge_weight(e: number): Option<E> {
return this.__edges[e].weight;
}
set_edge_weight(e: number, w: E) {
this.__edges[e].weight = w;
}
edge_endpoints(e: number): Option<[number, number]> {
const ed = this.__edges[e];
return is_some(ed) ? [ed.source(), ed.target()] : null;
}
remove_node(a: number): Option<N> {
if (!this.__nodes[a]) {
return
}
for (const d of DIRECTIONS) {
// remove all edges from and to this node
const k = d.index();
let count = 0;
while (true) {
let next = this.__nodes[a].__next[k];
count++;
if (next === this.EdgeEnd) {
break
}
const ret = this.remove_edge(next);
if (!is_some(ret)) {
console.warn('Expected remove_edge return to be some edge weight', ret)
break
}
}
}
// Use swap_remove -- only the swapped-in node is going to change
// number<number>, so we only have to walk its edges and update them.
const node = swap_remove(this.__nodes, a)
const n = this.__nodes[a];
if (!n) {
return node!.weight
}
const swap_edges = n.__next;
// swapped element old index
// const old_index = this.__nodes.length;
const new_index = a;
// Adjust the starts of the out edges, and ends of the in edges.
for (const d of DIRECTIONS) {
const k = d.index();
const edges = edges_walker_mut(this.__edges, swap_edges[k], d);
let curr_edge;
while (is_some(curr_edge = edges.next_edge())) {
curr_edge.node[k] = new_index
}
}
return node!.weight;
}
// For edge 'e' with endpoints 'edge_node', replace links to it
// with links to 'edge_next'
__change_edge_links(edge_node: [number, number], e: number, edge_next: [number, number]) {
for (const d of DIRECTIONS) {
const k = d.index();
const node = this.__nodes[edge_node[k]]
if (!node) {
console.warn("Edge's endpoint dir=%d, index=%d not found", d.value, edge_node[k])
return;
}
const fst = node.__next[k];
if (fst === e) {
node.__next[k] = edge_next[k];
} else {
const edges = edges_walker_mut(this.__edges, fst, d)
let curedge
while (is_some(curedge = edges.next_edge())) {
if (curedge.__next[k] === e) {
curedge.__next[k] = edge_next[k];
break; // the edge can only be present once in the list.
}
}
}
}
}
remove_edge(e: number): Option<E> {
// every edge is part of two lists,
// outgoing and incoming edges.
// Remove it from both
const ed = this.__edges[e];
if (!ed) {
return
}
const edge_node = ed.node;
const edge_next = ed.__next;
// Remove the edge from its in and out lists by replacing it with
// a link to the next in the list.
this.__change_edge_links(edge_node, e, edge_next);
const r = this.#remove_edge_adjust_indices(e)
return r
}
#remove_edge_adjust_indices(e: number): Option<E> {
// swap_remove the edge -- only the removed edge
// and the edge swapped into place are affected and need updating
// indices.
const edge = swap_remove(this.__edges, e)!
const ed = this.__edges[e];
if (!ed) {
return edge!.weight
}
const swap = ed.node
const swapped_e = this.__edges.length;
this.__change_edge_links(swap as [number, number], swapped_e, [e, e])
return edge!.weight
}
/// Return an iterator of all nodes with an edge starting from `a`.
///
/// - `Directed`: Outgoing edges from `a`.
/// - `Undirected`: All edges from or to `a`.
///
/// Produces an empty iterator if the node doesn't exist.<br>
/// Iterator element type is `number<number>`.
///
/// Use [`.neighbors(a).detach()`][1] to get a neighbor walker that does
/// not borrow from the graph.
///
/// [1]: struct.Neighbors.html#method.detach
neighbors(a: number) {
return this.neighbors_directed(a, Outgoing)
}
/// Return an iterator of all neighbors that have an edge between them and
/// `a`, in the specified direction.
/// If the graph's edges are undirected, this is equivalent to *.neighbors(a)*.
///
/// - `Directed`, `Outgoing`: All edges from `a`.
/// - `Directed`, `Incoming`: All edges to `a`.
/// - `Undirected`: All edges from or to `a`.
///
/// Produces an empty iterator if the node doesn't exist.<br>
/// Iterator element type is `number<number>`.
///
/// For a `Directed` graph, neighbors are listed in reverse order of their
/// addition to the graph, so the most recently added edge's neighbor is
/// listed first. The order in an `Undirected` graph is arbitrary.
///
/// Use [`.neighbors_directed(a, dir).detach()`][1] to get a neighbor walker that does
/// not borrow from the graph.
///
/// [1]: struct.Neighbors.html#method.detach
neighbors_directed(a: number, dir: Direction) {
const it = this.neighbors_undirected(a);
if (this.is_directed()) {
const k = dir.index();
it.__next[1 - k] = this.NodeEnd; // number::end()
it.skip_start = this.NodeEnd // number::end()
}
return it;
}
/// Return an iterator of all neighbors that have an edge between them and
/// `a`, in either direction.
/// If the graph's edges are undirected, this is equivalent to *.neighbors(a)*.
///
/// - `Directed` and `Undirected`: All edges from or to `a`.
///
/// Produces an empty iterator if the node doesn't exist.<br>
/// Iterator element type is `number<number>`.
///
/// Use [`.neighbors_undirected(a).detach()`][1] to get a neighbor walker that does
/// not borrow from the graph.
///
/// [1]: struct.Neighbors.html#method.detach
///
neighbors_undirected(a: number) {
const n = this.__nodes[a];
const next = !n ? [this.NodeEnd, this.NodeEnd] : structuredClone(n.__next)
return new Neighbors(a, this.__edges, next as [number, number])
}
/// Return an iterator of all edges of `a`.
///
/// - `Directed`: Outgoing edges from `a`.
/// - `Undirected`: All edges connected to `a`.
///
/// Produces an empty iterator if the node doesn't exist.<br>
/// Iterator element type is `EdgeReference<E>`.
edges(a: number): Edges<E, Ty> {
return this.edges_directed(a, Outgoing)
}
/// Return an iterator of all edges of `a`, in the specified direction.
///
/// - `Directed`, `Outgoing`: All edges from `a`.
/// - `Directed`, `Incoming`: All edges to `a`.
/// - `Undirected`, `Outgoing`: All edges connected to `a`, with `a` being the source of each
/// edge.
/// - `Undirected`, `Incoming`: All edges connected to `a`, with `a` being the target of each
/// edge.
///
/// Produces an empty iterator if the node `a` doesn't exist.<br>
/// Iterator element type is `EdgeReference<E>`.
edges_directed(a: number, dir: Direction): Edges<E, Ty> {
const n = this.__nodes[a];
const next = !n ? [this.NodeEnd, this.NodeEnd] : n.__next
return new Edges(a, this.__edges, dir, next as [number, number], this.#ty)
}
/// Return an iterator over all the edges connecting `a` and `b`.
///
/// - `Directed`: Outgoing edges from `a`.
/// - `Undirected`: All edges connected to `a`.
///
/// Iterator element type is `EdgeReference<E>`
edges_connecting(a: number, b: number): EdgesConnecting<E, Ty> {
return new EdgesConnecting(b, this.edges_directed(a, Direction.Outgoing()))
}
/// Lookup if there is an edge from `a` to `b`.
///
/// Computes in **O(e')** time, where **e'** is the number of edges
/// connected to `a` (and `b`, if the graph edges are undirected).
contains_edge(a: number, b: number): boolean {
return is_some(this.find_edge(a, b))
}
/**
*
* @param a number
* @param b number
* @returns number if one was found
*/
find_edge(a: number, b: number): Option<number> {
if (!this.is_directed()) {
const ed = this.find_edge_undirected(a, b);
return is_some(ed) ? ed[0] : null;
} else {
const node = this.__nodes[a];
const ret = !node ? null : this.__find_edge_directed_from_node(node, b);
return ret;
}
}
__find_edge_directed_from_node(node: Node<N>, b: number): Option<number> {
let edix = node.__next[0];
let edge;
while (is_some(edge = this.__edges[edix])) {
if (edge.node[1] === b) {
return edix;
}
edix = edge.__next[0];
}
return
}
find_edge_undirected(a: number, b: number): Option<[number, Direction]> {
const node = this.__nodes[a];
return !node ?
null :
this.__find_edge_undirected_from_node(node, b);
}
__find_edge_undirected_from_node(node: Node<N>, b: number): Option<[number, Direction]> {
for (const d of DIRECTIONS) {
const k = d.index();
let edix = node.__next[k];
let edge;
while (is_some(edge = this.__edges[edix])) {
if (edge.node[1 - k] === b
) {
return [edix, d];
}
edix = edge.__next[k];
}
}
return
}
/// Return an iterator over either the nodes without edges to them
/// (`Incoming`) or from them (`Outgoing`).
///
/// An *internal* node has both incoming and outgoing edges.
/// The nodes in `.externals(Incoming)` are the source nodes and
/// `.externals(Outgoing)` are the sinks of the graph.
///
/// For a graph with undirected edges, both the sinks and the sources are
/// just the nodes without edges.
///
/// The whole iteration computes in **O(|V|)** time.
externals(dir: Direction): Externals<N, Ty> {
return new Externals(iter(this.__nodes).enumerate(), dir, this.EdgeEnd, this.#ty)
}
node_indices(): DoubleEndedIterator<number> {
return range(0, this.node_count())
}
node_weights(): DoubleEndedIterator<N> {
return iter(this.__nodes).map(node => node.weight)
}
node_identifiers(): DoubleEndedIterator<number> {
return this.node_indices();
}
node_references(): Iterator<NodeRef<this['NodeId'], N>> {
return new NodeReferences(iter(this.__nodes).enumerate()).map(([i, w]) => NodeRef(i, w))
}
edge_indices(): DoubleEndedIterator<number> {
return range(0, this.edge_count())
}
edge_references(): Iterator<EdgeRef<this['NodeId'], this['EdgeId'], E>> {
return new EdgeReferences(iter(this.__edges).enumerate())
}
edge_weights(): DoubleEndedIterator<E> {
return iter(this.__edges).map(edge => edge.weight)
}
raw_nodes(): Node<N>[] {
return this.__nodes;
}
raw_edges(): Edge<E>[] {
return this.__edges
}
into_nodes_edges(): [Node<N>[], Edge<E>[]] {
return [this.__nodes, this.__edges];
}
first_edge(a: number, dir: Direction): Option<number> {
const node = this.__nodes[a];
if (!node) {
return
}
const edix = node.__next[dir.index()];
return edix === this.NodeEnd ? null : edix;
}
next_edge(e: number, dir: Direction): Option<number> {
const node = this.__edges[e];
if (!node) {
return
}
const edix = node.__next[dir.index()];
return edix === this.NodeEnd ? null : edix
}
/// Index the `Graph` by two indices, any combination of
/// node or edge indices is fine.
///
/// **Panics** if the indices are equal or if they are out of bounds.
///
// index_twice_mut(i: number, j: number) {
// // T = i
// // U = j
// // assert!(T::is_node_index() != U::is_node_index() || i.index() != j.index());
// return
// }
// Reverse the direction of all edges
reverse() {
for (const edge of this.__edges) {
swap(edge.node, 0, 1)
swap(edge.__next, 0, 1)
}
for (const node of this.__nodes) {
swap(node.__next, 0, 1)
}
}
// Remove all nodes and edges
clear() {
this.__nodes.length = 0;
this.__edges.length = 0;
}
// Remove all edges
clear_edges() {
this.__edges.length = 0;
for (const node of this.__nodes) {
node.__next = [this.NodeEnd, this.NodeEnd]
}
}
capacity(): [number, number] {
return [capacity(this.__nodes.length), capacity(this.__edges.length)]
}
reserve_nodes(additional: number) {
reserve(this.__nodes, additional)
}
reserve_edges(additional: number) {
reserve(this.__edges, additional)
}
/// Keep all nodes that return `true` from the `visit` closure,
/// remove the others.
///
/// `visit` is provided a proxy reference to the graph, so that
/// the graph can be walked and associated data modified.
///
/// The order nodes are visited is not specified.
retain_nodes(visit: (graph: this, node_index: number) => boolean) {
for (const index of this.node_indices().rev()) {
if (!visit(this, index)) {
const ret = this.remove_node(index);
assert_some(ret);
}
}
}
/// Keep all edges that return `true` from the `visit` closure,
/// remove the others.
///
/// `visit` is provided a proxy reference to the graph, so that
/// the graph can be walked and associated data modified.
///
/// The order edges are visited is not specified.
retain_edges(visit: (graph: this, edge_index: number) => boolean) {
for (const index of this.edge_indices().rev()) {
if (!visit(this, index)) {
const ret = this.remove_edge(index);
assert_some(ret);
}
}
}
static from_edges<N, E, Ty extends EdgeType, Ix extends GraphIx = 32>(ty: Ty, ix: Ix, default_node_weight: () => N, iterable: Iterable<[N, N, E]>): Graph<N, E, Ty, Ix> {
const g = Graph.with_capacity(ty, ix, 0, 0) as Graph<N, E, Ty, Ix>;
g.extend_with_edges(iterable, default_node_weight);
return g;
}
/// Extend the graph from an iterable of edges.
///
/// Node weights `N` are set to default values.
/// Edge weights `E` may either be specified in the list,
/// or they are filled with default values.
///
/// Nodes are inserted automatically to match the edges.
extend_with_edges<I extends Iterable<[N, N, E]>>(iterable: I, default_node_weight: () => N) {
const it = iter(iterable);
const [low] = it.size_hint();
reserve(this.__edges, low);
for (const elt of it) {
const [source, target, weight] = elt
const nx = Math.max(source as number, target as number);
while (nx >= this.node_count()) {
// ! make user who creates graph provide functions for default N/E weights
// this.add_node(N::default())
this.add_node(default_node_weight())
}
this.add_edge(source as number, target as number, weight as E);
}
}
map<N2, E2>(node_map: (node_index: number, weight: N) => N2, edge_map: (edge_index: number, weight: E) => E2): Graph<N2, E2, Ty, Ix> {
const g = Graph.with_capacity(this.#ty, this.#ix, this.node_count(), this.edge_count()) as Graph<N2, E2, Ty, Ix>;
extend(g.__nodes, enumerate(iter(this.__nodes)).map(([i, node]) => createNode(
node_map(i, node.weight,),
node.__next
)))
extend(g.__edges, enumerate(iter(this.__edges)).map(([i, edge]) => createEdge(
edge_map(i, edge.weight),
edge.__next,
edge.node
)))
return g
}
/// Create a new `Graph` by mapping nodes and edges.
/// A node or edge may be mapped to `None` to exclude it from
/// the resulting graph.
///
/// Nodes are mapped first with the `node_map` closure, then
/// `edge_map` is called for the edges that have not had any endpoint
/// removed.
///
/// The resulting graph has the structure of a subgraph of the original graph.
/// If no nodes are removed, the resulting graph has compatible node
/// indices; if neither nodes nor edges are removed, the result has
/// the same graph indices as `self`.
filter_map<N2, E2>(node_map: (node_index: number, weight: N) => Option<N2>, edge_map: (node_index: number, weight: E) => Option<E2>): Graph<N2, E2, Ty> {
const g = Graph.with_capacity(this.#ty, this.#ix, 0, 0) as unknown as Graph<N2, E2, Ty>
// mapping from old node index to new node index, end represents removed.
const max = this.NodeEnd;
const node_index_map = Array.from({ length: this.node_count() }, () => max);
for (const [i, node] of enumerate(iter(this.__nodes))) {
const nw = node_map(i, node.weight);
if (is_some(nw)) {
node_index_map[i] = g.add_node(nw);
}
}
for (const [i, edge] of enumerate(iter(this.__edges))) {
const source = node_index_map[edge.source()];
const target = node_index_map[edge.target()];
// source !== number::end() && target !== number::end()
if (source !== max && target !== max) {
const ew = edge_map(i, edge.weight);
g.add_edge(source, target, ew as E2)
}
}
return g
}
into_edge_type<NewTy extends EdgeType>(ty: NewTy): Graph<N, E, NewTy, Ix> {
return new Graph(ty, this.#ix, this.__nodes, this.__edges)
}
}
class Externals<N, Ty extends EdgeType> extends Iterator<number> {
#EdgeEnd: number;
#iter: Iterator<[number, Node<N>]>;
#dir: Direction;
#ty: Ty;
constructor(iter: Iterator<[number, Node<N>]>, dir: Direction, edge_end: number, ty: Ty) {
super()
this.#iter = iter;
this.#dir = dir;
this.#ty = ty;
this.#EdgeEnd = edge_end;
}
into_iter(): Iterator<number> {
this.#iter.into_iter();
return this;
}
next(): IteratorResult<number> {
const k = this.#dir.index();
while (true) {
const n = this.#iter.next();
if (n.done) {
return done();
} else {
const [index, node] = n.value;
// node.next[k] == number::end()
// && (Ty::is_directed() || node.next[1 - k] == number::end())
if (
node.__next[k] === this.#EdgeEnd
&& (this.#ty.is_directed() || node.__next[1 - k] === this.#EdgeEnd)
) {
return {
done: false,
value: index
};
} else {
continue
}
}
}
}
size_hint(): [number, Option<number>] {
const [_, upper] = this.#iter.size_hint();
return [0, upper];
}
}
class Neighbors<E> extends Iterator<number> {
skip_start: number;
__edges: Edge<E>[];
__next: [number, number];
#start: [number, number];
constructor(skip_start: number, edges: Edge<E>[], next: [number, number]) {
super();
this.skip_start = skip_start;
this.__edges = edges;
this.__next = next;
this.#start = structuredClone(next);
}
into_iter(): Iterator<number> {
this.__next = this.#start;
return this;
}
next(): IteratorResult<number> {
let edge = this.__edges[this.__next[0]]
if (edge) {
this.__next[0] = edge.__next[0];
return { done: false, value: edge.node[1] }
}
while (is_some(edge = this.__edges[this.__next[1]])) {
this.__next[1] = edge.__next[1];
if (edge.node[0] !== this.skip_start) {
return { done: false, value: edge.node[0] }
}
}
return done()
}
/// Return a “walker” object that can be used to step through the
/// neighbors and edges from the origin node.
///
/// Note: The walker does not borrow from the graph, this is to allow mixing
/// edge walking with mutating the graph's weights.
detach(): WalkNeighbors {
return new WalkNeighbors(this.skip_start, this.__next);
}
}
class EdgesWalkerMut<E> {
#edges: Edge<E>[];
#next: number;
__dir: Direction;
constructor(edges: Edge<E>[], next: number, dir: Direction) {
this.#edges = edges;
this.#next = next;
this.__dir = dir;
}
next_edge(): Option<Edge<E>> {
const e = this.next();
return is_some(e) ? e[1] : null
}
next(): Option<[number, Edge<E>]> {
const this_index = this.#next;
const k = this.__dir.index();
const edge = this.#edges[this.#next];
if (edge) {
this.#next = edge.__next[k];
return [this_index, edge];
}
return null
}
}
function edges_walker_mut<E>(edges: Edge<E>[], next: number, dir: Direction) {
return new EdgesWalkerMut(edges, next, dir)
}
/// Iterator over the edges of from or to a node
class Edges<E, Ty extends EdgeType> extends Iterator<EdgeReference<E>> {
// starting
skip_start: number;
#edges: Edge<E>[];
#next: [number, number];
#direction: Direction
#ty: Ty;
constructor(skip_start: number, edges: Edge<E>[], direction: Direction, next: [number, number], ty: Ty) {
super();
this.skip_start = skip_start;
this.#edges = edges
this.#next = next;
this.#direction = direction;
this.#ty = ty;
}
into_iter(): Iterator<EdgeReference<E>> {
return this;
}
next(): IteratorResult<EdgeReference<E>> {
// type direction | iterate over reverse
// |
// Directed Outgoing | outgoing no
// Directed Incoming | incoming no
// Undirected Outgoing | both incoming
// Undirected Incoming | both outgoing
// For iterate_over, "both" is represented as None.
// For reverse, "no" is represented as None.
const [iterate_over, reverse] = this.#ty.is_directed() ? [this.#direction, null] : [null, this.#direction.opposite()]
if (!iterate_over || iterate_over?.value === Outgoing.value) {
const i = this.#next[0];
const e = this.#edges[i];
if (e) {
const { node: node, weight, __next: next } = e;
this.#next[0] = next[0];
return {
done: false, value: new EdgeReference(
i,
reverse?.value === Outgoing.value ? swap_pair(node) : node,
weight
)
}
}
}
if (!iterate_over || iterate_over?.value === Incoming.value) {
let edge
while (is_some(edge = this.#edges[this.#next[1]])) {
const { node: node, weight, __next: next } = edge;
const edge_index = this.#next[1];
this.#next[1] = next[1];
if (!iterate_over && node[0] === this.skip_start) {
continue;
}
return {
done: false, value: new EdgeReference(
edge_index,
reverse?.value === Incoming.value ? swap_pair(node) : node,
weight
)
}
}
}
return done();
}
clone(): Edges<E, Ty> {
return new Edges(this.skip_start, this.#edges, this.#direction, this.#next, this.#ty)
}
}
export class EdgesConnecting<E, Ty extends EdgeType> extends Iterator<EdgeReference<E>> {
#target_node: number;
#edges: Edges<E, Ty>;
constructor(target_node: number, edges: Edges<E, Ty>) {
super()
this.#target_node = target_node;
this.#edges = edges;
}
into_iter(): Iterator<EdgeReference<E>> {
return this;
}
next(): IteratorResult<EdgeReference<E>> {
const target_node = this.#target_node;
const res = this.#edges.find(ed => ed.node[1] === target_node);
return res ? { done: false, value: res } : done()
}
size_hint(): [number, Option<number>] {
const [_, upper] = this.#edges.size_hint();
return [0, upper]
}
}
export function swap_pair<T>(x: [T, T]) {
swap(x, 0, 1);
return x;
}
export class WalkNeighbors {
skip_start: number;
#next: [number, number]
constructor(skip_start: number, next: [number, number]) {
this.skip_start = skip_start;
this.#next = next;
}
clone() {
return new WalkNeighbors(this.skip_start, structuredClone(this.#next));
}
next<N, E, Ty extends EdgeType>(g: Graph<N, E, Ty>): IteratorResult<[number, number]> {
// first any outgoing edges
let edge = g.__edges[this.#next[0]];
if (edge) {
const ed = this.#next[0];
this.#next[0] = edge.__next[0];
return { done: false, value: [ed, edge.node[1]] }
}
// then incoming edges
// For an "undirected" iterator (traverse both incoming
// and outgoing edge lists), make sure we don't double
// count selfloops by skipping them in the incoming list.
while (is_some(edge = g.__edges[this.#next[1]])) {
const ed = this.#next[1];
this.#next[1] = edge.__next[1];
if (edge.node[0] !== this.skip_start) {
return { done: false, value: [ed, edge.node[0]] }
}
}
return done()
}
next_node<N, E, Ty extends EdgeType>(g: Graph<N, E, Ty>) {
const n = this.next(g);
return n.done ? done() : n.value[1];
}
next_edge<N, E, Ty extends EdgeType>(g: Graph<N, E, Ty>) {
const n = this.next(g);
return n.done ? done() : n.value[0];
}
}
export class EdgeReference<E> {
#index: number;
#weight: E;
node: [number, number];
constructor(index: number, node: [number, number], weight: E) {
this.#index = index
this.node = node;
this.#weight = weight;
}
eq(rhs: EdgeReference<E>): boolean {
return this.#index === rhs.#index && this.#weight === rhs.weight;
}
weight(): E {
return this.#weight;
}
source(): number {
return this.node[0]
}
target(): number {
return this.node[1]
}
id(): number {
return this.#index;
}
clone(): EdgeReference<E> {
return new EdgeReference(this.#index, this.node, this.#weight)
}
}
class EdgeReferences<G extends GraphBase<any, any>> extends DoubleEndedIterator<EdgeRef<NodeId<G>, EdgeId<G>, EdgeWeight<G>>> {
#iter: ReturnType<DoubleEndedIterator<Edge<EdgeWeight<G>>>['enumerate']>
constructor(iter: ReturnType<DoubleEndedIterator<Edge<EdgeWeight<G>>>['enumerate']>) {
super();
this.#iter = iter;
}
into_iter(): DoubleEndedIterator<EdgeRef<NodeId<G>, EdgeId<G>, EdgeWeight<G>>> {
return this;
}
next(): IteratorResult<EdgeRef<NodeId<G>, EdgeId<G>, EdgeWeight<G>>> {
const n = this.#iter.next();
if (n.done) {
return done()
} else {
const [i, edge] = n.value
return { done: false, value: EdgeRef(i, edge.source(), edge.target(), edge.weight) }
}
}
next_back(): IteratorResult<EdgeRef<NodeId<G>, EdgeId<G>, EdgeWeight<G>>> {
const n = this.#iter.next_back();
if (n.done) {
return done()
} else {
const [i, edge] = n.value
return { done: false, value: EdgeRef(i, edge.source(), edge.target(), edge.weight) }
}
}
size_hint(): [number, Option<number>] {
return this.#iter.size_hint();
}
}
class NodeReferences<N> extends DoubleEndedIterator<[number, N]> {
#iter: DoubleEndedIterator<[number, Node<N>]>;
constructor(iter: DoubleEndedIterator<[number, Node<N>]>) {
super()
this.#iter = iter;
}
into_iter(): DoubleEndedIterator<[number, N]> {
return this
}
next(): IteratorResult<[number, N]> {
const n = this.#iter.next();
if (n.done) {
return done();
} else {
const [i, node] = n.value;
return {
done: false,
value: [i as number, node.weight]
}
}
}
next_back(): IteratorResult<[number, N]> {
const n = this.#iter.next_back();
if (n.done) {
return done();
} else {
const [i, node] = n.value;
return {
done: false,
value: [i as number, node.weight]
}
}
}
size_hint(): [number, Option<number>] {
return this.#iter.size_hint()
}
}