Skip to content

Commit

Permalink
feat(adjacency): add IGraph.degree() & impls
Browse files Browse the repository at this point in the history
BREAKING CHANGE: replace .valence() w/ more flexible .degree() methods

- add IGraph.degree() with same default behavior as .valence(),
  but supporting diff degree types (in/out/inout)
- add .degree() impls for all
- remove old .valence() methods
- update tests
  • Loading branch information
postspectacular committed Feb 20, 2021
1 parent 0c4c112 commit 9fb02ac
Showing 7 changed files with 43 additions and 25 deletions.
12 changes: 8 additions & 4 deletions packages/adjacency/src/api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Fn2, Pair } from "@thi.ng/api";

export type DegreeType = "in" | "out" | "both";
export type DegreeType = "in" | "out" | "inout";

/**
* @typeParam T - vertex type (default: `number`)
@@ -42,14 +42,18 @@ export interface IGraph<T = number> {
*/
hasEdge(from: T, to: T): boolean;
/**
* Returns number of outgoing edges for given vertex.
* Returns number of edges for given vertex. By default only outgoing edges
* are counted, but can be customized via given {@link DegreeType}. Note: In
* undirected graphs the `type` has no relevance and essentially is always
* `"inout"`.
*
* @param id
* @param type
*/
valence(id: T): number;
degree(id: T, type?: DegreeType): number;
/**
* Returns neighbor IDs for given vertex, i.e. those vertices connected via
* edges starting from given vertex (or, in undirected graphs, the other
* edges starting *from* given vertex (or, in undirected graphs, the other
* vertices of edges which the given vertex is part of).
*
* @param id
24 changes: 14 additions & 10 deletions packages/adjacency/src/binary.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { popCount } from "@thi.ng/binary";
import { BitMatrix } from "@thi.ng/bitfield";
import type { Edge, IGraph } from "./api";
import type { DegreeType, Edge, IGraph } from "./api";
import { into, invert, toDot } from "./utils";

/**
* Adjacency matrix representation for both directed and undirected graphs and
* using a compact bit matrix to store edges. Each edge requires only 1 bit
* in directed graphs or 2 bits in undirected graphs. E.g. this is allows
* storing 16384 directed edges in just 2KB of memory (128 * 128 / 8 = 2048).
*/
export class AdjacencyBitMatrix implements IGraph<number> {
mat: BitMatrix;
protected undirected: boolean;
@@ -66,14 +71,13 @@ export class AdjacencyBitMatrix implements IGraph<number> {
return this.mat.at(from, to) !== 0;
}

valence(id: number) {
let res = 0;
const { data, stride } = this.mat;
id *= stride;
for (let i = id + stride; --i >= id; ) {
data[i] !== 0 && (res += popCount(data[i]));
}
return res;
degree(id: number, type: DegreeType = "out") {
let degree = 0;
if (this.undirected || type !== "in")
degree += this.mat.popCountRow(id);
if (!this.undirected && type !== "out")
degree += this.mat.popCountColumn(id);
return degree;
}

neighbors(id: number) {
15 changes: 10 additions & 5 deletions packages/adjacency/src/list.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { DCons } from "@thi.ng/dcons";
import type { Edge, IGraph } from "./api";
import type { DegreeType, Edge, IGraph } from "./api";
import { into, invert, toDot } from "./utils";

export class AdjacencyList implements IGraph<number> {
vertices: DCons<number>[] = [];
indegree: number[] = [];
numE = 0;
numV = 0;
protected numE = 0;
protected numV = 0;

constructor(edges?: Iterable<Edge>) {
edges && into(this, edges);
@@ -83,9 +83,14 @@ export class AdjacencyList implements IGraph<number> {
return vertex ? !!vertex.find(to) : false;
}

valence(id: number): number {
degree(id: number, type: DegreeType = "out") {
let degree = 0;
const vertex = this.vertices[id];
return vertex ? vertex.length : 0;
if (vertex) {
if (type !== "in") degree += vertex.length;
if (type !== "out") degree += this.indegree[id];
}
return degree;
}

neighbors(id: number): Iterable<number> {
10 changes: 7 additions & 3 deletions packages/adjacency/src/sparse.ts
Original file line number Diff line number Diff line change
@@ -61,8 +61,12 @@ export class AdjacencyMatrix extends CSR implements IGraph<number> {
return this.m;
}

valence(id: number): number {
return this.nnzRow(id);
degree(id: number, type: DegreeType = "out") {
let degree = 0;
this.ensureIndex(id, id);
if (this.undirected || type !== "in") degree += this.nnzRow(id);
if (!this.undirected && type !== "out") degree += this.nnzCol(id);
return degree;
}

neighbors(id: number): number[] {
@@ -100,7 +104,7 @@ export class AdjacencyMatrix extends CSR implements IGraph<number> {
res.setAt(i, i, this.nnzCol(i));
}
break;
case "both":
case "inout":
for (let i = this.m; --i >= 0; ) {
res.setAt(i, i, this.nnzRow(i) + this.nnzCol(i));
}
3 changes: 2 additions & 1 deletion packages/adjacency/test/binary.ts
Original file line number Diff line number Diff line change
@@ -14,7 +14,8 @@ describe("adjacency (bitmatrix)", () => {
assert(m.hasEdge(1, 2));
assert.deepStrictEqual(m.neighbors(1), [2]);
assert.deepStrictEqual(m.neighbors(2), []);
assert.deepStrictEqual(m.valence(1), 1);
assert.strictEqual(m.degree(1), 1);
assert.strictEqual(m.degree(2), 0);
assert.deepStrictEqual([...m.edges()], [[1, 2]]);
console.log(m.toString());
});
2 changes: 1 addition & 1 deletion packages/adjacency/test/list.ts
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ describe("adjacency (list)", () => {
assert(!m.hasEdge(0, 2));
assert.deepStrictEqual(m.neighbors(1), [2]);
assert.deepStrictEqual(m.neighbors(2), [0]);
assert.deepStrictEqual(m.valence(1), 1);
assert.strictEqual(m.degree(1), 1);
assert.deepStrictEqual(
[...m.edges()],
[
2 changes: 1 addition & 1 deletion packages/adjacency/test/sparse.ts
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ describe("adjacency (sparse)", () => {
assert(m.hasEdge(1, 2));
assert.deepStrictEqual(m.neighbors(1), [2]);
assert.deepStrictEqual(m.neighbors(2), []);
assert.deepStrictEqual(m.valence(1), 1);
assert.strictEqual(m.degree(1), 1);
assert.deepStrictEqual([...m.edges()], [[1, 2]]);
});

0 comments on commit 9fb02ac

Please sign in to comment.