Options
All
  • Public
  • Public/Protected
  • All
Menu

Lets you add edges to a directed acyclic graph and be told whether this edge introduces a cycle. If it would, it is not added. Useful when trying to build an acyclic graph.

Based on the paper:

A Dynamic Topological Sort Algorithm for Directed Acyclic Graphs
   DAVID J. PEARCE / PAUL H. J. KELLY
   Journal of Experimental Algorithmics (JEA)
   Volume 11, 2006, Article No. 1.7
   ACM New York, NY, USA

Documentation

See here for documentation

Install

The drill:

npm install --save incremental-cycle-detect

Typings for Typescript are available (this is written in typescript).

Use the dist.js or dist.min.js for browser usage if you must.

Exposes a global object window.IncrementalCycleDetect with the same methods you can when importing this lib:

import * as IncrementalCycleDetect from "incremental-cycle-detect";

Usage

The main purpose of this library is to add edges to a directed acyclic graph and be told when that makes the graph cyclic.

const { GenericGraphAdapter } = require("incremental-cycle-detect");
const graph = GenericGraphAdapter.create();
graph.addEdge(0, 1) // => true
graph.addEdge(1, 2) // => true
graph.addEdge(2, 3) // => true
graph.addEdge(3, 0) // => false because this edge introduces a cycle
// The edge (3,0) was not added.

graph.deleteEdge(2, 3);
graph.addEdge(3, 0) // => true, no cycle because we deleted edge (2,3)

The main algorithm is implemented by CycleDetectorImpl. To allow for this lib to work with different graph data structures, its methods take a GraphAdapter object for accessing the graph. You must called it every time an edge is added or removed, see the docs for GraphAdapter for more details.

For convenience this library also provide a few graph data structures that ready to be used. The following all implement the methods from CommonAdapter:

  • GenericGraphAdapter: Uses Maps to associate data with a vertex, allowing any type of vertex. In the above example, you could use strings, booleans, objects etc. instead of numbers. Seems to perform pretty well.
  • MultiGraphAdapter Similar to GenericGraphAdapter, but allows for multiple edges between two vertices. Edges are identified by an additional label.
  • GraphlibAdapter: For the npm module graphlib. Vertices are strings. Does not support multigraphs currently.

Example for using the GraphlibAdapter:

const { Graph } = require("graphlib");
const graph = GraphlibAdapter.create({graphlib: Graph});
graph.addEdge(0, 1) // => true

You can add vertices explicitly, but it is not required. They are added if they do not exist.

See the documentation linked above for all methods available.

Performance

Incremental cycle detection performs better than checking for cycles from scratch every time you add an edge. Tests done with benchmark. Compared with running a full topological sort with graphlib (via alg.isAcyclic(graph)) each time a vertex is added. Measured time is the time that was needed for creating a new graph and adding n vertices, checking for a cycle after each edge insertion.

incremental-cycle-detection(insert 15000, RandomSource) x 38.21 ops/sec ±1.78% (47 runs sampled)

incremental-cycle-detection-multi(insert 15000, RandomSource) x 31.58 ops/sec ±2.77% (52 runs sampled)

graphlib(insert15000, RandomSource) x 0.19 ops/sec ±1.83% (5 runs sampled)

(node v8.9.4, graph with 200 vertices, 15000 random -- same for each algorithm -- edges added)

Also, even inserting into graphlib without checking for cycles seems to be slower:

graphlib-no-cycle-check (insert 15000, RandomSource) x 21.59 ops/sec ±6.63% (37 runs sampled)

JavaScript environment

Some parts need Map. You can either

import * as Map from "core-js/es6/map";
const graph = GenericGraphAdapter.create({mapConstructor: Map}):

Use your own graph data structure

As mentioned above, You can also use the CycleDetector (implemented by PearceKellyDetector) directly and roll your own graph data structure. See the docs.

Essentially, you need to call the CycleDetector every time you add modify the graph. Then it tells you whether adding an edge is allowed. You can also use an existing GraphAdapter (see above) as the starting point.

Build

May not to work on Windows.

git clone https://github.com/blutorange/js-incremental-cycle-detect
cd js-incremental-cycle-detection
npm install
npm run build

Test

git clone https://github.com/blutorange/js-incremental-cycle-detect
cd js-incremental-cycle-detection
npm install
npm run test

Change log

I use the following keywords:

  • Added A new feature that is backwards-compatible.
  • Changed A change that is not backwards-compatible.
  • Fixed A bug or error that was fixed.

From newest to oldest:

0.4.0

  • Fixed bug with MultiGraphAdapter: #getLabelledEdge() updated incorrectly.
  • Added an additional method #contractLabeledEdge, While #contractEdge contract all edges (irrespective of their label) between two vertices, #contractEdge contracts just one particular edge with a specific label. Note that since cycle are forbidden, a certain labeled edge between two vertices can only be contracted if it is the only edge between those two vertices. #contractLabeled refuses two contract in such a case, while #contractVertices contract all edges. You can check with #canContractEdge and #canContractLabeledEdge.
  • Removed deprecated MultiGraphAdapter#addLabeledEdge(from: TVertex, to: TVertex, label?: TEdgeLabel, data?: TEdgeData): boolean

0.3.0

  • Added Algorithm#findWeaklyConnectedComponents.
  • Added a getEdgesWithData, getEdgesWithDataFrom, getEdgesWithDataTo method when both the edge and its data are needed.
  • Added a clone and map method for creating a copy of a graph.
  • Changed the graph adapter implementations so that instances are now created with the factory method create instead of the constructor. This was necessary for the clone method.

0.2.2

  • Added two methods for accessing edge data of incoming / outgoing edges: getEdgeDataFrom, getEdgeDataTo
  • Added a method for checking whether an edge can be added: canAddEdge.

0.2.1

  • Fixed typings for typescript.

0.2.0

  • Added the method getOrder to the graph adapters. It allows you to access the topological order of each vertex.
  • Added a MultiGraphAdapter data structure that allows for multiple edges between two vertices.
  • Changed GenericGraphAdapter, it now only allows for one kind of edge data to be compatible with the CommonAdapter interface. You can use objects if you need to store more data.
  • Added more test cases for the MultiGraphAdapter and fixed some bugs, updated dependencies.

0.1.1

  • 0.1.1 Fixed package.json and dependencies (was missing tslib).

0.1.0

  • 0.1.0 Initial version.

Index

Type aliases

GraphFactory

GraphFactory: function

Type declaration

GraphlibConstructor

GraphlibConstructor: object

Type declaration

LabelGenerator

LabelGenerator: TypedTriFunction<TVertex, TVertex, TEdgeData, Maybe<TEdgeLabel>>

MultiGraphEdgeData

MultiGraphEdgeData: Map<Maybe<TEdgeLabel>, Maybe<TEdgeData>>

Variables

Private Const DummyDetector

DummyDetector: (Anonymous class) = new class implements CycleDetector<any> {map<TAnotherClonedVertex>(): CycleDetector<TAnotherClonedVertex> {return DummyDetector;}createVertexData(g: GraphAdapter<any>): VertexData {return DummyVertexData;}canAddEdge(g: GraphAdapter<any>, from: any, to: any): boolean {return true;}isReachable(g: GraphAdapter<any>, source: any, target: any): boolean {return false;}onVertexDeletion(g: GraphAdapter<any>, vertex: any): void {/***/}supportsOrder(): boolean {return false;}// and target, merging both vertices results in a cycle.getOrder(g: GraphAdapter<any>, vertex: any): number {return -1;}}()
internal

Functions

Private assign

  • assign<TFirst, TSecond>(target: TFirst, source: TSecond): TFirst & TSecond
  • internal

    Type parameters

    • TFirst

    • TSecond

    Parameters

    • target: TFirst
    • source: TSecond

    Returns TFirst & TSecond

Private canContractEdge

  • canContractEdge<TVertex, TEdgeData>(adapter: CommonAdapter<TVertex, TEdgeData>, from: TVertex, to: TVertex): boolean
  • internal

    Type parameters

    • TVertex

    • TEdgeData

    Parameters

    • adapter: CommonAdapter<TVertex, TEdgeData>
    • from: TVertex
    • to: TVertex

    Returns boolean

combineIterators

  • combineIterators<T>(...iterators: Iterator<T>[]): Iterator<T>
  • Type parameters

    • T

    Parameters

    • Rest ...iterators: Iterator<T>[]

    Returns Iterator<T>

Private contractEdge

  • contractEdge<TVertex, TEdgeData>(adapter: CommonAdapter<TVertex, TEdgeData>, from: TVertex, to: TVertex, vertexMerger?: BinaryOperator<TVertex>, edgeMerger?: BinaryOperator<TEdgeData>): boolean
  • internal

    Type parameters

    • TVertex

    • TEdgeData

    Parameters

    • adapter: CommonAdapter<TVertex, TEdgeData>
    • from: TVertex
    • to: TVertex
    • Default value vertexMerger: BinaryOperator<TVertex> = takeFirst
    • Optional edgeMerger: BinaryOperator<TEdgeData>

    Returns boolean

Private createArrayIterator

  • createArrayIterator<T>(array: (undefined | T)[]): Iterator<T> & object
  • internal

    Type parameters

    • T

    Parameters

    • array: (undefined | T)[]

    Returns Iterator<T> & object

Private createChainedIterator

  • createChainedIterator<T>(...its: Iterator<T>[]): Iterator<T>
  • internal

    Type parameters

    • T

    Parameters

    • Rest ...its: Iterator<T>[]

    Returns Iterator<T>

Private createFilteredIterator

  • createFilteredIterator<T>(it: Iterator<Maybe<T>>): Iterator<T>
  • createFilteredIterator<T>(it: Iterator<T>, filter: Predicate<T>): Iterator<T>
  • internal

    Type parameters

    • T

    Parameters

    • it: Iterator<Maybe<T>>

    Returns Iterator<T>

  • Type parameters

    • T

    Parameters

    • it: Iterator<T>
    • filter: Predicate<T>

    Returns Iterator<T>

Private createFlatMappedIterator

  • createFlatMappedIterator<T, S>(iterator: Iterator<T>, mapper: TypedFunction<T, Iterator<S>>): Iterator<S>
  • Maps each item with the provided mapper function to an iterator, and iterates over all items of these iterators.

    internal

    Type parameters

    • T

    • S

    Parameters

    • iterator: Iterator<T>

      Base iterator.

    • mapper: TypedFunction<T, Iterator<S>>

      Mapper that maps an item to an iterator.

    Returns Iterator<S>

Private createMappedArrayIterator

  • createMappedArrayIterator<T, V>(arr: (undefined | T)[], mapFn: TypedFunction<T, V>): Iterator<V>
  • internal

    Type parameters

    • T

    • V

    Parameters

    • arr: (undefined | T)[]
    • mapFn: TypedFunction<T, V>

    Returns Iterator<V>

Private createMappedIterator

  • createMappedIterator<T, V>(it: Iterator<T>, mapper: TypedFunction<T, V>): Iterator<V>
  • internal

    Type parameters

    • T

    • V

    Parameters

    • it: Iterator<T>
    • mapper: TypedFunction<T, V>

    Returns Iterator<V>

forEach

  • forEach<T>(callback: Consumer<T>, ...iterators: Iterator<T>[]): void
  • Type parameters

    • T

    Parameters

    • callback: Consumer<T>
    • Rest ...iterators: Iterator<T>[]

    Returns void

Private takeFirst

  • takeFirst<T>(first: T, second: T): T
  • internal

    Type parameters

    • T

    Parameters

    • first: T
    • second: T

    Returns T

Private toArray

  • toArray<T>(it: Iterator<T>): T[]
  • internal

    Type parameters

    • T

    Parameters

    • it: Iterator<T>

    Returns T[]

Object literals

Private Const DoneIteratorResult

DoneIteratorResult: object
internal

done

done: true = true

value

value: undefined = undefined

Private Const EmptyIterator

EmptyIterator: object
internal

next

  • next(): IteratorResult<any>
  • Returns IteratorResult<any>

Legend

  • Module
  • Object literal
  • Variable
  • Function
  • Function with type parameter
  • Index signature
  • Type alias
  • Enumeration
  • Enumeration member
  • Property
  • Method
  • Interface
  • Interface with type parameter
  • Constructor
  • Property
  • Method
  • Index signature
  • Class
  • Class with type parameter
  • Constructor
  • Property
  • Method
  • Accessor
  • Index signature
  • Inherited constructor
  • Inherited property
  • Inherited method
  • Inherited accessor
  • Protected property
  • Protected method
  • Protected accessor
  • Private property
  • Private method
  • Private accessor
  • Static property
  • Static method

Generated using TypeDoc