Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multiple edge types in Graph.hasEdge #8550

Merged
merged 15 commits into from Oct 19, 2022
43 changes: 38 additions & 5 deletions packages/core/graph/src/AdjacencyList.js
Expand Up @@ -18,6 +18,7 @@ opaque type EdgeAddress = number;
export type SerializedAdjacencyList<TEdgeType> = {|
nodes: Uint32Array,
edges: Uint32Array,
edgeTypes: Uint8Array,
|};

// eslint-disable-next-line no-unused-vars
Expand All @@ -40,6 +41,7 @@ const SHRINK_FACTOR = 0.5;
export default class AdjacencyList<TEdgeType: number = 1> {
#nodes /*: NodeTypeMap<TEdgeType | NullEdgeType> */;
#edges /*: EdgeTypeMap<TEdgeType | NullEdgeType> */;
#edgeTypes /* Uint8Array */;

constructor(
opts?:
Expand All @@ -48,11 +50,13 @@ export default class AdjacencyList<TEdgeType: number = 1> {
) {
let nodes;
let edges;
let edgeTypes;

if (opts?.nodes) {
({nodes, edges} = opts);
({nodes, edges, edgeTypes} = opts);
this.#nodes = new NodeTypeMap(nodes);
this.#edges = new EdgeTypeMap(edges);
this.#edgeTypes = edgeTypes;
} else {
let {
nodeCapacity = NodeTypeMap.MIN_CAPACITY,
Expand All @@ -68,6 +72,7 @@ export default class AdjacencyList<TEdgeType: number = 1> {
);
this.#nodes = new NodeTypeMap(nodeCapacity);
this.#edges = new EdgeTypeMap(edgeCapacity);
this.#edgeTypes = new Uint8Array(0);
}
}

Expand All @@ -87,6 +92,7 @@ export default class AdjacencyList<TEdgeType: number = 1> {
return {
nodes: this.#nodes.data,
edges: this.#edges.data,
edgeTypes: this.#edgeTypes,
};
}

Expand Down Expand Up @@ -254,6 +260,10 @@ export default class AdjacencyList<TEdgeType: number = 1> {
// The edge is already in the graph; do nothing.
if (edge !== null) return false;

if (!this.#edgeTypes.includes(type)) {
mattcompiles marked this conversation as resolved.
Show resolved Hide resolved
this.#edgeTypes = new Uint8Array([...this.#edgeTypes, type]);
}

let capacity = this.#edges.capacity;
// We add 1 to account for the edge we are adding.
let count = this.#edges.count + 1;
Expand Down Expand Up @@ -329,10 +339,33 @@ export default class AdjacencyList<TEdgeType: number = 1> {
hasEdge(
from: NodeId,
to: NodeId,
type: TEdgeType | NullEdgeType = 1,
type:
| AllEdgeTypes
| TEdgeType
| NullEdgeType
| Array<TEdgeType | NullEdgeType> = 1,
): boolean {
let hash = this.#edges.hash(from, to, type);
return this.#edges.addressOf(hash, from, to, type) !== null;
let hasEdge = (type: TEdgeType | NullEdgeType) => {
let hash = this.#edges.hash(from, to, type);
return this.#edges.addressOf(hash, from, to, type) !== null;
};

if (type === ALL_EDGE_TYPES || Array.isArray(type)) {
let types: Iterable<TEdgeType | NullEdgeType> =
// $FlowFixMe[incompatible-type-arg] this.edgeTypes will only contain valid edge types
Array.isArray(type) ? type : this.#edgeTypes;

for (let currType of types) {
if (hasEdge(currType)) {
return true;
}
}

return false;
}

// $FlowFixMe[incompatible-call] ALL_EDGE_TYPES has already been handled
return hasEdge(type);
}

/**
Expand Down Expand Up @@ -1076,7 +1109,7 @@ export class EdgeTypeMap<TEdgeType> extends SharedTypeMap<
hash: EdgeHash,
from: NodeId,
to: NodeId,
type: TEdgeType,
type: TEdgeType | NullEdgeType,
): EdgeAddress | null {
let address = this.head(hash);
while (address !== null) {
Expand Down
6 changes: 5 additions & 1 deletion packages/core/graph/src/Graph.js
Expand Up @@ -104,7 +104,11 @@ export default class Graph<TNode, TEdgeType: number = 1> {
hasEdge(
from: NodeId,
to: NodeId,
type?: TEdgeType | NullEdgeType = 1,
type?:
| TEdgeType
| NullEdgeType
| Array<TEdgeType | NullEdgeType>
| AllEdgeTypes = 1,
): boolean {
return this.adjacencyList.hasEdge(from, to, type);
}
Expand Down
31 changes: 31 additions & 0 deletions packages/core/graph/test/AdjacencyList.test.js
Expand Up @@ -5,6 +5,7 @@ import path from 'path';
import {Worker} from 'worker_threads';

import AdjacencyList, {NodeTypeMap, EdgeTypeMap} from '../src/AdjacencyList';
import {ALL_EDGE_TYPES} from '../src/Graph';
import {toNodeId} from '../src/types';

describe('AdjacencyList', () => {
Expand Down Expand Up @@ -243,6 +244,36 @@ describe('AdjacencyList', () => {
AdjacencyList.prototype.hash = originalHash;
});

it('hasEdge should accept "ALL_EDGE_TYPES"', () => {
let graph = new AdjacencyList();
let a = graph.addNode();
let b = graph.addNode();
let c = graph.addNode();

graph.addEdge(a, b, 1);
graph.addEdge(b, c, 2);

assert.ok(!graph.hasEdge(a, b, 2));
assert.ok(graph.hasEdge(a, b, ALL_EDGE_TYPES));
assert.ok(!graph.hasEdge(b, c, 1));
assert.ok(graph.hasEdge(b, c, ALL_EDGE_TYPES));
});

it('hasEdge should accept an array of edge types', () => {
let graph = new AdjacencyList();
let a = graph.addNode();
let b = graph.addNode();
let c = graph.addNode();

graph.addEdge(a, b, 1);
graph.addEdge(b, c, 2);

assert.ok(!graph.hasEdge(a, b, [2, 3]));
assert.ok(graph.hasEdge(a, b, [1, 2]));
assert.ok(!graph.hasEdge(b, c, [1, 3]));
assert.ok(graph.hasEdge(b, c, [2, 3]));
});

describe('deserialize', function () {
this.timeout(10000);

Expand Down