All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.utils.graphs.DirectedGraph.kt Maven / Gradle / Ivy

The newest version!
package org.openrndr.kartifex.utils.graphs

import org.openrndr.collections.PriorityQueue
import kotlin.jvm.JvmRecord
import kotlin.math.max
import kotlin.math.min

interface IEdge {
    fun from(): V
    fun to(): V
    fun value(): E
}

@JvmRecord
data class Edge(val _value: E, val _from: V, val _to: V) : IEdge {
    override fun from(): V {
        return _from
    }

    override fun to(): V {
        return _to
    }

    override fun value(): E {
        return _value
    }
}

fun  Set.indexOf(e: E): Int {
    val i = iterator()
    var index = 0
    while (i.hasNext()) {
        if (i.next() == e) {
            return index
        }
        index++
    }
    return -1
}

class DirectedGraph(
    val out: MutableMap> = mutableMapOf(),
    val `in`: MutableMap> = mutableMapOf()
) {
    fun indexOf(vertex: V): Int {
        return out.keys.indexOf(vertex)
    }


    fun vertices(): Set {
        return out.keys
    }

    fun edges(): Iterable> {
        return Iterable {
            out.entries
                .flatMap { outer ->
                    outer.value
                        .entries
                        .map { inner -> Edge(inner.value, inner.key, outer.key) }
                }
                .iterator()
        }
    }

    fun edge(from: V, to: V): E {
        val m = out[from] ?: error("no such edge")
        val e = m[to] ?: error("no such edge")
        return e
    }

    fun `in`(vertex: V): Set {
        val s: Set? = `in`[vertex]
        return if (s == null) {
            if (out.contains(vertex)) {
                emptySet()
            } else {
                error("no such vertex")
            }
        } else {
            s
        }
    }

    fun out(vertex: V): Set = out[vertex]?.keys ?: error("no such vertex $vertex")

    fun link(from: V, to:V, edge: E) = link(from, to, edge) { _, b -> b }

    fun link(from: V, to: V, edge: E, merge: (E, E) -> E) {
        add(from)
        add(to)
        val e: E? = (out[from] ?: error("no from vertex"))[to]
        if (e == null) {
            out[from]!![to] = edge
        } else {
            out[from]!![to] = merge(e, edge)
        }
        out.getOrPut(to) { mutableMapOf() }
        `in`.getOrPut(to) { mutableSetOf() }.add(from)
    }

    fun unlink(from: V, to: V) {
        (out[from] ?: error("no from vertex")).remove(to)
        (`in`[to]!!.remove(from))
    }

    fun add(vertex: V): DirectedGraph {
        return if (out.contains(vertex)) {
            this
        } else {
            out[vertex] = mutableMapOf()
            `in`[vertex] = mutableSetOf()
            this
        }
    }


//    fun remove(vertex: V): DirectedGraph {
//
//
//        if (out.contains(vertex)) {
//            for (v in out[vertex]!!.keys) {
//                out[v]?.remove(vertex)
//            }
//            out.remove(vertex)
//
//        }
//        `in`.remove(vertex)
//        return this
//    }

    fun select(selection: Set): DirectedGraph {
        val newOut = mutableMapOf>()
        val newIn = mutableMapOf>()
        for (entry in out.entries) {
            if (entry.key in selection) {
                newOut[entry.key] = entry.value.filterKeys { key -> key in selection }.toMutableMap()
            }
        }
        for (entry in `in`.entries) {
            if (entry.key in selection) {
                newIn[entry.key] = entry.value.filter { it in selection }.toMutableSet()
            }
        }
        return DirectedGraph(newOut, newIn)
    }

//    fun transpose(): DirectedGraph {
//        return DirectedGraph(
//
//            out.mapValues({ u: V, x: io.lacuna.bifurcan.Map? ->
//                `in`.get(u, EMPTY_SET as io.lacuna.bifurcan.Set).map.mapValues(
//                    java.util.function.BiFunction { v: V, y: Void? -> edge(v, u) })
//            }),
//            out.mapValues(java.util.function.BiFunction, io.lacuna.bifurcan.Set> { x: V, m: io.lacuna.bifurcan.Map -> m.keys() })
//        )
//    }


    override fun hashCode(): Int {
        return out.hashCode()
    }

    override fun equals(other: Any?): Boolean {
        return if (other is DirectedGraph<*, *>) {
            other.out == out
        } else {
            false
        }
    }

}

/// directed graphs
private class TarjanState(val index: Int) {
    var lowlink: Int
    var onStack: Boolean

    init {
        lowlink = index
        onStack = true
    }
}

object Graphs {
    fun  stronglyConnectedComponents(
        graph: DirectedGraph,
        includeSingletons: Boolean
    ): Set> {

        // algorithmic state
        val state = mutableMapOf()

        val stack = ArrayDeque()

        // call-stack state
        val path = mutableListOf()
        val branches = mutableListOf>()
        val result = mutableSetOf>()
        for (seed in graph.vertices()) {
            if (state.contains(seed)) {
                continue
            }
            branches.add(mutableListOf(seed).iterator())
            do {
                // traverse deeper
                if (branches.last().hasNext()) {
                    val w: V = branches.last().next()
                    var ws: TarjanState? = state[w]
                    if (ws == null) {
                        ws = TarjanState(state.size)
                        state[w] = ws
                        stack.addLast(w)
                        path.add(w)
                        branches.add(graph.out(w).iterator())
                    } else if (ws.onStack) {
                        val vs: TarjanState = state[path.last()]!!
                        vs.lowlink = min(vs.lowlink, ws.index)
                    }

                    // return
                } else {
                    branches.removeLast()
                    val w: V = path.removeLast()
                    val ws: TarjanState = state[w]!!

                    // update predecessor's lowlink, if they exist
                    if (path.size > 0) {
                        val v: V = path.last()
                        val vs: TarjanState = state[v]!!
                        vs.lowlink = min(vs.lowlink, ws.lowlink)
                    }

                    // create a new group
                    if (ws.lowlink == ws.index) {
                        if (!includeSingletons && stack.last() === w) {
                            stack.removeLast()
                            state[w]!!.onStack = false
                        } else {
                            val group = mutableSetOf()
                            while (true) {
                                val x: V = stack.removeLast()
                                group.add(x)
                                state[x]!!.onStack = false
                                if (x === w) {
                                    break
                                }
                            }
                            result.add(group)
                        }
                    }
                }
            } while (path.size > 0)
        }
        return result
    }


    fun  stronglyConnectedSubgraphs(
        graph: DirectedGraph,
        includeSingletons: Boolean
    ): List> {
        val result = mutableListOf>()
        stronglyConnectedComponents(graph, includeSingletons)
            .forEach { s ->
                result.add(
                    graph.select(s)
                )
            }
        return result
    }


    fun  cycles(graph: DirectedGraph): List> {
        // traversal
        val path = mutableListOf()
        val branches = mutableListOf>()

        //state
        val blocked = mutableSetOf()
        val blocking = mutableMapOf>()
        val result = mutableListOf>()
        for (subgraph in stronglyConnectedSubgraphs(graph, true)) {
            // simple rings are a pathological input for this algorithm, and also very common
            if (subgraph.vertices()
                    .all { v: V -> subgraph.out(v).size == 1 }
            ) {
                val seed: V = subgraph.vertices().iterator().next()
                subgraph.out(seed)
                result.add(
                    bfsVertices(seed) { vertex: V -> subgraph.out(vertex) }.asSequence().toList()+ listOf(seed)
                )
                continue
            }
            for (seed in subgraph.vertices()) {


                val threshold = subgraph.indexOf(seed)
                path.add(seed)
                branches.add(subgraph.out(seed).iterator())
                blocked.clear()
                blocking.clear()
                var depth = 1
                do {
                    // traverse deeper
                    if (branches.last().hasNext()) {
                        val v: V = branches.last().next()
                        if (subgraph.indexOf(v) < threshold) {
                            continue
                        }
                        if (seed == v) {
                            result.add(path + listOf(seed))
                            depth = 0
                        } else if (!blocked.contains(v)) {
                            path.add(v)
                            depth++
                            branches.add(subgraph.out(v).iterator())
                        }
                        blocked.add(v)
                        // return
                    } else {
                        val v: V = path.removeLast()
                        depth = max(-1, depth - 1)
                        if (depth < 0) {
                            val stack = ArrayDeque().apply { addFirst(v) }
                            while (stack.size > 0) {
                                val u: V = stack.removeLast()
                                if (blocked.contains(u)) {
                                    blocked.remove(u)
                                    blocking[u] ?: emptySet()
                                        .forEach { value: V -> stack.addLast(value) }
                                    blocking.remove(u)
                                }
                            }
                        } else {
                            graph.out(v).forEach { u: V -> blocking.getOrPut(u) { mutableSetOf() }.add(v) }
                        }
                        branches.removeLast()
                    }
                } while (path.size > 0)
            }
        }
        return result
    }

    /// traversal

    /// traversal
    fun  bfsVertices(start: V, adjacent: (V) -> Iterable): Iterator {
        return bfsVertices(listOf(start), adjacent)
    }

    fun  bfsVertices(start: Iterable, adjacent: (V) -> Iterable): Iterator {
        val queue = ArrayDeque()
        val traversed = mutableSetOf()
        start.forEach { value: V -> queue.add(value) }

        return object : Iterator {
            override fun hasNext(): Boolean {
                return queue.size > 0
            }

            override fun next(): V {
                val v: V = queue.removeFirst()
                traversed.add(v)
                adjacent(v).forEach { w: V ->
                    if (!traversed.contains(w)) {
                        queue.addLast(w)
                    }
                }
                return v
            }
        }
    }

    /// search
    private class ShortestPathState {
        val origin: V
        val node: V
        val prev: ShortestPathState?
        val distance: Double

        constructor(origin: V) {
            this.origin = origin
            prev = null
            node = origin
            distance = 0.0
        }

        constructor(node: V, prev: ShortestPathState, edge: Double) {
            origin = prev.origin
            this.node = node
            this.prev = prev
            distance = prev.distance + edge
        }

        fun path(): List {
            val result = ArrayDeque()
            var curr: ShortestPathState? = this
            while (true) {
                result.addFirst(curr!!.node)
                if (curr.node == curr.origin) {
                    break
                }
                curr = curr.prev
            }
            return result
        }
    }


    /**
     * @return the shortest path, if one exists, between a starting vertex and an accepted vertex, excluding trivial
     * solutions where a starting vertex is accepted.
     */
    fun  shortestPath(
        graph: DirectedGraph,
        start: Iterable,
        accept: (V) -> Boolean,
        cost: (IEdge) -> Double
    ): List? {
        val originStates = mutableMapOf>>()
        val queue = PriorityQueue>(compareBy { it.distance })

        for (v in start) {
            if (graph.vertices().contains(v)) {
                val init = ShortestPathState(v)
                (originStates.getOrPut(v) { mutableMapOf() })[v] = init
                queue.add(init)
            }
        }
        var curr: ShortestPathState?
        while (true) {
            curr = queue.poll()
            if (curr == null) {
                return null
            }
            val states: MutableMap> = originStates[curr.origin] ?: error("no state")
            if (states[curr.node] !== curr) {
                continue
            } else if (curr.prev != null && accept(curr.node)) {
                return curr.path()
            }
            for (v in graph.out(curr.node)) {
                val edge: Double =
                    cost(Edge(graph.edge(curr.node, v), curr.node, v))
                require(edge >= 0) { "negative edge weights are unsupported" }
                var next: ShortestPathState? = states[v]
                next = if (next == null) {
                    ShortestPathState(v, curr, edge)
                } else if (curr.distance + edge < next.distance) {
                    ShortestPathState(v, curr, edge)
                } else {
                    continue
                }
                states[v] = next
                queue.add(next)
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy