aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/at/hannibal2/skyhanni/utils/GraphUtils.kt
blob: 0d15b99ed641e05570fa3f9aedfac07d6acd638b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package at.hannibal2.skyhanni.utils

import at.hannibal2.skyhanni.data.model.DijkstraTree
import at.hannibal2.skyhanni.data.model.Graph
import at.hannibal2.skyhanni.data.model.GraphNode
import at.hannibal2.skyhanni.data.model.findPathToDestination
import java.util.PriorityQueue
import java.util.Stack

object GraphUtils {
    /**
     * Find the fastest path from [closestNode] to *any* node that matches [condition].
     */
    fun findFastestPath(
        closestNode: GraphNode,
        condition: (GraphNode) -> Boolean,
    ): Pair<Graph, Double>? {
        val distances = findDijkstraDistances(closestNode, condition)
        val entry = distances.lastVisitedNode.takeIf(condition)
        return entry?.let {
            distances.findPathToDestination(it)
        }
    }

    /**
     * Find the fastest path from [closestNode] to *all* nodes that matches [condition].
     */
    fun findFastestPaths(
        graph: Graph,
        closestNode: GraphNode,
        condition: (GraphNode) -> Boolean = { true },
    ): Pair<MutableMap<GraphNode, Graph>, MutableMap<GraphNode, Double>> {
        val paths = mutableMapOf<GraphNode, Graph>()

        val map = mutableMapOf<GraphNode, Double>()
        val distances = findAllShortestDistances(closestNode)
        for (graphNode in graph.nodes) {
            if (!condition(graphNode)) continue
            val (path, distance) = distances.findPathToDestination(graphNode)
            paths[graphNode] = path
            map[graphNode] = distance
        }
        return Pair(paths, map)
    }

    /**
     * Find all maximal sub graphs of the given graph which are not connected
     */
    fun findDisjointClusters(graph: Graph): List<Set<GraphNode>> {
        val universe = graph.toMutableSet()
        val allClusters = mutableListOf<Set<GraphNode>>()
        while (universe.isNotEmpty()) {
            val cluster = mutableSetOf<GraphNode>()
            allClusters.add(cluster)
            val queue = Stack<GraphNode>()
            queue.add(universe.first())
            while (queue.isNotEmpty()) {
                val next = queue.pop()
                universe.remove(next)
                cluster.add(next)
                queue.addAll(next.neighbours.keys)
                queue.retainAll(universe)
            }
        }
        return allClusters
    }

    fun findShortestPathAsGraph(start: GraphNode, end: GraphNode): Graph = findShortestPathAsGraphWithDistance(start, end).first

    /**
     * Find a tree of distances to the [start] node using dijkstra's algorithm.
     */
    fun findDijkstraDistances(
        start: GraphNode,
        /**
         * Bail out early before collecting all the distances to all nodes in the graph. This will not collect valid distance data for *all*
         * nodes for which bailout matches, but only the closest one.
         */
        bailout: (GraphNode) -> Boolean,
    ): DijkstraTree {
        val distances = mutableMapOf<GraphNode, Double>()
        val previous = mutableMapOf<GraphNode, GraphNode>()
        val visited = mutableSetOf<GraphNode>()
        val queue = PriorityQueue<GraphNode>(compareBy { distances.getOrDefault(it, Double.MAX_VALUE) })
        var lastVisitedNode: GraphNode = start

        distances[start] = 0.0
        queue.add(start)

        while (queue.isNotEmpty()) {
            val current = queue.poll()
            lastVisitedNode = current
            if (bailout(current)) break

            visited.add(current)

            current.neighbours.forEach { (neighbour, weight) ->
                if (neighbour !in visited) {
                    val newDistance = distances.getValue(current) + weight
                    if (newDistance < distances.getOrDefault(neighbour, Double.MAX_VALUE)) {
                        distances[neighbour] = newDistance
                        previous[neighbour] = current
                        queue.add(neighbour)
                    }
                }
            }
        }

        return DijkstraTree(
            start,
            distances,
            previous,
            lastVisitedNode,
        )
    }

    fun findAllShortestDistances(start: GraphNode): DijkstraTree {
        return findDijkstraDistances(start) { false }
    }

    fun findShortestPathAsGraphWithDistance(start: GraphNode, end: GraphNode): Pair<Graph, Double> {
        val distances = findDijkstraDistances(start) { it == end }
        return distances.findPathToDestination(end)
    }

    fun findShortestPath(start: GraphNode, end: GraphNode): List<LorenzVec> = findShortestPathAsGraph(start, end).toPositionsList()

    fun findShortestDistance(start: GraphNode, end: GraphNode): Double = findShortestPathAsGraphWithDistance(start, end).second
}