Skip to content

Commit 093ff79

Browse files
pert: impl iterative depth-first search
1 parent 75f97f5 commit 093ff79

1 file changed

Lines changed: 73 additions & 43 deletions

File tree

src/components/Tree/PhyloGraph/graph.ts

Lines changed: 73 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* eslint-disable no-loops/no-loops,unused-imports/no-unused-vars */
2-
import { max, min, cloneDeep, sumBy, meanBy } from 'lodash'
2+
import { max, min, cloneDeep, sumBy, meanBy, isEmpty, last } from 'lodash'
3+
import { ErrorInternal } from 'src/helpers/ErrorInternal'
34

45
import { PHYLO_GRAPH_NODE_RADIUS } from './constants'
56

@@ -194,11 +195,6 @@ export function traverseBreadthFirst<T>(
194195
return results
195196
}
196197

197-
export enum DepthFirstTraversalOrder {
198-
Pre,
199-
Post,
200-
}
201-
202198
// Explore graph in breadth-first fashion, backwards
203199
export function traverseBreadthFirstBackwards<T>(
204200
graph: Graph,
@@ -210,53 +206,87 @@ export function traverseBreadthFirstBackwards<T>(
210206

211207
// Explore graph in depth-first pre-order fashion
212208
export function traverseDepthFirstPreOrder<T>(graph: Graph, explorer: (params: ExplorerParams) => T) {
213-
const results: T[] = []
209+
const roots = getRoots(graph)
210+
if (isEmpty(roots)) {
211+
return []
212+
}
213+
214+
const stack: GraphNode[] = roots
214215
const explored = new Set<string>()
215-
return getRoots(graph).forEach((node) => {
216-
traverseDepthFirstRecursively(graph, node, explorer, results, explored, DepthFirstTraversalOrder.Pre)
217-
})
218-
return results
216+
const results = []
217+
218+
// eslint-disable-next-line no-constant-condition
219+
while (true) {
220+
const node = stack.pop()
221+
if (!node) {
222+
return results
223+
}
224+
225+
const children: [GraphNode, GraphEdge][] = getChildren(graph, node.id)
226+
{
227+
const parents: [GraphNode, GraphEdge][] = getParents(graph, node.id)
228+
const siblings: GraphNode[] = getSiblings(graph, node.id)
229+
const isLeaf = children.length === 0 // Leaf is the node that has no children
230+
const isRoot = parents.length === 0 // Root is the node that has no parents
231+
results.push(explorer({ node, children, parents, siblings, isLeaf, isRoot }))
232+
explored.add(node.id)
233+
}
234+
235+
children.reverse().forEach(([child]) => {
236+
if (!explored.has(child.id)) {
237+
stack.push(child)
238+
explored.add(child.id)
239+
}
240+
})
241+
}
219242
}
220243

221244
// Explore graph in depth-first post-order fashion
222245
export function traverseDepthFirstPostOrder<T>(graph: Graph, explorer: (params: ExplorerParams) => T) {
223-
const results: T[] = []
224-
const explored = new Set<string>()
225-
return getRoots(graph).forEach((node) => {
226-
traverseDepthFirstRecursively(graph, node, explorer, results, explored, DepthFirstTraversalOrder.Post)
227-
})
228-
return results
229-
}
246+
const roots = getRoots(graph)
230247

231-
// Implements graph traversal in depth-first pre-order or post-order fashion. Recursive implementation.
232-
function traverseDepthFirstRecursively<T>(
233-
graph: Graph,
234-
node: GraphNode,
235-
explorer: (params: ExplorerParams) => T,
236-
results: T[],
237-
explored: Set<string>,
238-
order: DepthFirstTraversalOrder,
239-
) {
240-
const parents: [GraphNode, GraphEdge][] = getParents(graph, node.id)
241-
const children: [GraphNode, GraphEdge][] = getChildren(graph, node.id)
242-
const siblings: GraphNode[] = getSiblings(graph, node.id)
243-
const isLeaf = children.length === 0 // Leaf is the node that has no children
244-
const isRoot = parents.length === 0 // Root is the node that has no parents
245-
246-
// Explore current node if pre-order
247-
if (order === DepthFirstTraversalOrder.Pre) {
248-
explored.add(node.id)
249-
results.push(explorer({ node, children, parents, siblings, isLeaf, isRoot }))
248+
if (isEmpty(roots)) {
249+
return []
250250
}
251251

252-
// Explore child nodes recursively
253-
children.forEach(([child]) => traverseDepthFirstRecursively(graph, child, explorer, results, explored, order))
252+
// TODO: Implement post-order traversal with multiple roots
253+
if (roots.length > 1) {
254+
throw new ErrorInternal('Not implemented: multiple tree roots are not yet implmented')
255+
}
256+
const root = roots[0]
254257

255-
// Explore current node if post-order
256-
if (order === DepthFirstTraversalOrder.Post) {
257-
explored.add(node.id)
258-
results.push(explorer({ node, children, parents, siblings, isLeaf, isRoot }))
258+
const stack: GraphNode[] = []
259+
const explored = new Set<string>()
260+
const results = []
261+
stack.push(root)
262+
263+
let node
264+
while ((node = last(stack))) {
265+
const children: [GraphNode, GraphEdge][] = getChildren(graph, node.id)
266+
267+
let isTail = true
268+
for (const [child, _] of children) {
269+
if (!explored.has(child.id)) {
270+
isTail = false
271+
stack.push(child)
272+
explored.add(child.id)
273+
break
274+
}
275+
}
276+
277+
if (isTail) {
278+
stack.pop()
279+
280+
const parents: [GraphNode, GraphEdge][] = getParents(graph, node.id)
281+
const siblings: GraphNode[] = getSiblings(graph, node.id)
282+
const isLeaf = children.length === 0 // Leaf is the node that has no children
283+
const isRoot = parents.length === 0 // Root is the node that has no parents
284+
285+
results.push(explorer({ node, children, parents, siblings, isLeaf, isRoot }))
286+
explored.add(node.id)
287+
}
259288
}
289+
return results
260290
}
261291

262292
export function getNodesForEdge(graph: Graph, edge: GraphEdge) {

0 commit comments

Comments
 (0)