datavis-tech / graph-data-structure

A graph data structure with topological sort and shortest path algorithms.
MIT License
250 stars 45 forks source link

Adding Object of Classes in edges #38

Closed jassingh94 closed 1 month ago

jassingh94 commented 4 years ago

When adding a objects of a classes in the edges, topologicalSort returns "[Object Object]"

curran commented 4 years ago

Can you share code that reproduces this?

Nodes must be identified by strings, as the documentation says.

jassingh94 commented 4 years ago

Yup i read it in the documentation later on , also went through the code itself of the module. Don't think it is possible with the current structure. Sharing code snippet just in case :

class helloWorld1 {
  constructor(){
    name : "Hello World1"
  }
}

class helloWorld2 {
  constructor(){
    name : "Hello World2"
  }
}

let Graph = require("graph-data-structure");
let graph = Graph();
graph.addEdge(new helloWorld1(), new helloWorld2())
graph.topologicalSort()
//returns Array(1) ["[object Object]"]
curran commented 4 years ago

Right. You can't use objects as identifiers for nodes. They need to be strings.

Do you have any suggestions for ways to improve this?

Maybe the code should throw an error if the node IDs are not strings?

I'm not convinced there is a strong use case for using objects as node IDs.

chansdad commented 3 years ago

Sure this library makes working with graphs a bit easier , You can save the id property of the class to create nodes and edges , and then performa topological sort . But the way i look at it is - In Data flow programming , you can have branches based on conditions. How does the topological sort handle the branching?

Lke in your example what if the black pants mandate shoes and no belts? Resulting topological sort does not give or allow for that disconnect . how can one handle such logic with this this topologic search?

curran commented 3 years ago

@chansdad You're right, it is a limitation of sorts. It's very similar to the need in functional programming for sentinel values that indicate "nothing", called Option Types. In practice, this ends up as some function in the chain returning null.

Alternatively, you could build up separate graphs for different types of pants, meaning the generation of the graph data structure itself would be a function of the type of pants, because as you say the dependency structure has to change as function of that.

chansdad commented 3 years ago

@Curran. I am trying to get my head around how to traverse the graph keeping track of the operations within each node . I got to build the graph, I know my root node . and iterating through adjacency lists , recursively and moving forward based on the nodes operations .. Do you have any suggestions on how I can go about implementing some thing like this for example .

A -> B -> (C ->D, B-> D) , D ->E -> F, G . (In D check for condition =true branch to F ) and F -> G -> H ->I, J ( if J , branch back to G)

In the above , D has two inputs , and then E has TWO outputs. E will branch to D if the properties in E = True.
H will branch to J for some condition , and then J will connect to G

Thanks in advance.

curran commented 3 years ago

I'm curious to know the context of the use case. What is this for?

Also, I'm not sure how to interpret this syntax: "A -> B -> (C ->D, B-> D) , D ->E -> F, G ". What does parentheses mean here? How does B go to B?

I have not encountered such a use case, so I'm not really sure how to advise. Maybe for each condition, it may be possible to construct different graphs? Or, keep all the dependency relationships and just output null sometimes?

I feel like I need more detail to properly help here. Thanks!

chansdad commented 3 years ago

May be this will explain ..

https://github.com/chansdad/Experiments/blob/main/sampleflow.png

I am looking to traverse the graph and for each node based on the type of node , take action more on the lines of data flow programming concept . But rather than going down to the granular level of data flow programming would like to have functional blocks of code in each node that would act on the incoming data from the previous node. each node to be of a certain type. Planned types are : Decision box , Branch , Task , Start, End, Config. So in the image , you can see are seeing two flows comprised of tasks . Task nodes can be something like - commit to database , pick up a message from a queue , publish a message to a queue , send a email , send a sms message , etc , so in the first flow for example, D's inputs ( 3 z's) could be 1. configuration, 2. pick up a message from a queue , 3. Execute another flow , and pass it to D for further processing..
So far managed to traverse this directed graph , but at the same time realized in order to apply or execute the 3 z's i need a weighted graph , and in the process of adding the weights to the edges .. not using any libraries , just pure javascript ..

curran commented 3 years ago

We could use objects as keys if we implemented #27 and used InternMap as the Map implementation: https://www.npmjs.com/package/internmap

leblancmeneses commented 1 year ago

Perhaps Symbols: https://www.typescriptlang.org/docs/handbook/symbols.html might help here; Currently looking at implementing azure devops stage yaml definition

stages:
- stage: string  # name of the stage, A-Z, a-z, 0-9, and underscore
  displayName: string  # friendly name to display in the UI
  dependsOn: string | [ string ]
  condition: string
  pool: string | pool
  variables: { string: string } | [ variable | variableReference ] 
  jobs: [ job | templateReference]

example 01:

stages:
- stage: A

# stage B runs if A fails
- stage: B
  condition: failed()

# stage C runs if B succeeds
- stage: C
  dependsOn:
  - A
  - B
  condition: succeeded('B')

example 02:

stages:
- stage: Test

- stage: DeployUS1
  dependsOn: Test    # this stage runs after Test

- stage: DeployUS2
  dependsOn: Test    # this stage runs in parallel with DeployUS1, after Test

- stage: DeployEurope
  dependsOn:         # this stage runs after DeployUS1 and DeployUS2
  - DeployUS1
  - DeployUS2

Given the current example on the README.md

// idea 1: the data contains the getGraphKey
export type NodeId<T> = T & {getGraphKey: () => Symbol}

// idea 2: the data doesn't know anything about a graph
export interface Node<T> {
 name: string;
 data: T;
}

// idea 3: found after posting: (https://segfaultx64.github.io/typescript-graph/) the data doesn't know anything about a graph but you supply the lambda in constructor to identify the key from the data.
const graph = new Graph<NodeType>((n: NodeType) => n.name)

const graph = Graph<T>();
const nodeA = { name: 'a', data: stageADefinition };
const nodeB = { name: 'b', data: stageBDefinition };
graph.addEdge(nodeA, nodeB);

build systems, workflows (argo workflows), all use dags so if anyone can recommend a base library I am all ears. My practical application is a dependency graph. If a file section is changed it might trigger manual changes in 1 or 2 other files. I would like to query this graph to know if the build should error if the change is not applied in the same pull request. I would like to be able to query from any direction to find the linked dependencies.