UNPKG

graphinius

Version:

Generic graph library in Typescript

219 lines (165 loc) 6.99 kB
import * as fs from 'fs'; import * as path from 'path'; import * as $N from '../../core/base/BaseNode'; import * as $E from '../../core/base/BaseEdge'; import * as $G from '../../core/base/BaseGraph'; import * as $R from '../../utils/RemoteUtils'; const DEFAULT_WEIGHT = 1; const CSV_EXTENSION = ".csv"; export interface ICSVInConfig { separator? : string; explicit_direction? : boolean; direction_mode? : boolean; weighted? : boolean; } export interface ICSVInput { _config : ICSVInConfig; readFromAdjacencyListFile(filepath : string) : $G.IGraph; readFromAdjacencyList(input : Array<string>, graph_name : string) : $G.IGraph; readFromAdjacencyListURL(config : $R.RequestConfig, cb : Function); readFromEdgeListFile(filepath : string) : $G.IGraph; readFromEdgeList(input : Array<string>, graph_name: string) : $G.IGraph; readFromEdgeListURL(config : $R.RequestConfig, cb : Function); } class CSVInput implements ICSVInput { _config : ICSVInConfig; constructor( config: ICSVInConfig = {} ) { this._config = { separator: config.separator != null ? config.separator : ',', explicit_direction: config.explicit_direction != null ? config.explicit_direction : true, direction_mode: config.direction_mode != null ? config.direction_mode : false, weighted: config.weighted != null ? config.weighted : false }; } readFromAdjacencyListURL(config : $R.RequestConfig, cb : Function) { this.readGraphFromURL(config, cb, this.readFromAdjacencyList); } readFromEdgeListURL(config : $R.RequestConfig, cb : Function) { this.readGraphFromURL(config, cb, this.readFromEdgeList); } private readGraphFromURL(config: $R.RequestConfig, cb: Function, localFun: Function) { let self = this, graph_name = config.file_name, graph : $G.IGraph, request; $R.checkNodeEnvironment() $R.retrieveRemoteFile(config, function(raw_graph) { let input = raw_graph.toString().split('\n'); graph = localFun.apply(self, [input, graph_name]); cb(graph, undefined); }); } readFromAdjacencyListFile(filepath : string) : $G.IGraph { return this.readFileAndReturn(filepath, this.readFromAdjacencyList); } readFromEdgeListFile(filepath : string) : $G.IGraph { return this.readFileAndReturn(filepath, this.readFromEdgeList); } private readFileAndReturn(filepath: string, func: Function) : $G.IGraph { $R.checkNodeEnvironment(); let graph_name = path.basename(filepath); let input = fs.readFileSync(filepath).toString().split('\n'); return func.apply(this, [input, graph_name]); } readFromAdjacencyList(input : Array<string>, graph_name : string) : $G.IGraph { let graph = new $G.BaseGraph(graph_name); for ( let idx in input ) { let line = input[idx], elements = this._config.separator.match(/\s+/g) ? line.match(/\S+/g) : line.replace(/\s+/g, '').split(this._config.separator), node_id = elements[0], node : $N.IBaseNode, edge_array = elements.slice(1), edge : $E.IBaseEdge, target_node_id : string, target_node : $N.IBaseNode, dir_char: string, directed: boolean, edge_id: string, edge_id_u2: string; if ( !node_id ) { // end of file or empty line, just treat like an empty line... continue; } node = graph.hasNodeID(node_id) ? graph.getNodeById(node_id) : graph.addNodeByID(node_id); for ( let e = 0; e < edge_array.length; ) { if ( this._config.explicit_direction && ( !edge_array || edge_array.length % 2 ) ) { throw new Error('Every edge entry has to contain its direction info in explicit mode.'); } target_node_id = edge_array[e++]; target_node = graph.hasNodeID(target_node_id) ? graph.getNodeById(target_node_id) : graph.addNodeByID(target_node_id); /** * The direction determines if we have to check for the existence * of an edge in 'both' directions or only from one node to the other * Within the CSV module this check is done simply via ID check, * as we are following a rigorous naming scheme anyways... */ dir_char = this._config.explicit_direction ? edge_array[e++] : this._config.direction_mode ? 'd' : 'u'; if ( dir_char !== 'd' && dir_char !== 'u' ) { throw new Error("Specification of edge direction invalid (d and u are valid)."); } directed = dir_char === 'd'; edge_id = node_id + "_" + target_node_id + "_" + dir_char; edge_id_u2 = target_node_id + "_" + node_id + "_" + dir_char; if ( graph.hasEdgeID(edge_id) || ( !directed && graph.hasEdgeID(edge_id_u2) ) ) { // The completely same edge should only be added once... continue; } else { edge = graph.addEdgeByID(edge_id, node, target_node, {directed: directed}); } } } return graph; } readFromEdgeList(input : Array<string>, graph_name : string, weighted = false) : $G.IGraph { let graph = new $G.BaseGraph(graph_name); for ( let idx in input ) { let line = input[idx], elements = this._config.separator.match(/\s+/g) ? line.match(/\S+/g) : line.replace(/\s+/g, '').split(this._config.separator); if ( ! elements ) { // end of file or empty line, just treat like an empty line... continue; } if ( elements.length < 2 || elements.length > 3 ) { throw new Error('Edge list is in wrong format - every line has to consist of two entries (the 2 nodes)'); } let node_id = elements[0], node : $N.IBaseNode, target_node : $N.IBaseNode, edge : $E.IBaseEdge, target_node_id = elements[1], dir_char = this._config.explicit_direction ? elements[2] : this._config.direction_mode ? 'd' : 'u', directed: boolean, edge_id: string, edge_id_u2: string, parse_weight: number, edge_weight: number; node = graph.hasNodeID(node_id) ? graph.getNodeById(node_id) : graph.addNodeByID(node_id); target_node = graph.hasNodeID(target_node_id) ? graph.getNodeById(target_node_id) : graph.addNodeByID(target_node_id); if ( dir_char !== 'd' && dir_char !== 'u' ) { throw new Error("Specification of edge direction invalid (d and u are valid)."); } directed = dir_char === 'd'; edge_id = node_id + "_" + target_node_id + "_" + dir_char; edge_id_u2 = target_node_id + "_" + node_id + "_" + dir_char; parse_weight = parseFloat(elements[2]); edge_weight = this._config.weighted ? (isNaN(parse_weight) ? DEFAULT_WEIGHT : parse_weight) : null; /** * @todo introduce Edge Dupe Checker and replace this logic */ if ( graph.hasEdgeID(edge_id) || ( !directed && graph.hasEdgeID(edge_id_u2) ) ) { continue; } else if (this._config.weighted) { edge = graph.addEdgeByID(edge_id, node, target_node, {directed: directed, weighted: true, weight: edge_weight}); } else { edge = graph.addEdgeByID(edge_id, node, target_node, {directed: directed}); } } return graph; } } export { CSVInput };