August 27, 2024

P87 - Depth-first order graph traversal.

a) Write a method that generates a depth-first order graph traversal sequence. The starting point should be specified, and the output should be a list of nodes that are reachable from this starting point (in depth-first order).

> "[a-b, b-c, c-d, d-e]".toGraph().nodesByDepthFrom("c")
[c, b, a, d, e]

b) Write similar method for breadth-first graph traversal.

> "[a-b, b-c, c-d, d-e]".toGraph().nodesByBreadthFrom("c")
[c, b, d, a, e]

kotlin

package org.kotlin99.graphs

import com.natpryce.hamkrest.assertion.assertThat
import com.natpryce.hamkrest.equalTo
import org.junit.Test
import org.kotlin99.common.tail


fun <V> Graph<V, *>.nodesByDepthFrom(nodeValue: V): List<V> {
    fun nodesByDepth(nodeValues: List<V>, visited: List<V>): List<V> =
        if (nodeValues.isEmpty()) emptyList()
        else {
            val head = nodeValues.first()
            val tail = nodeValues.tail()
            if (visited.contains(head)) nodesByDepth(tail, visited)
            else listOf(head) + nodesByDepth(neighbourValues(head) + tail, visited + head)
        }
    return nodesByDepth(listOf(nodeValue), emptyList())
}

fun <V> Graph<V, *>.nodesByBreadthFrom(nodeValue: V): List<V> {
    fun nodesByBreadth(nodeValues: List<V>, visited: List<V>): List<V> =
        if (nodeValues.isEmpty()) emptyList()
        else {
            val head = nodeValues.first()
            val tail = nodeValues.tail()
            if (visited.contains(head)) nodesByBreadth(tail, visited)
            else listOf(head) + nodesByBreadth(tail + neighbourValues(head), visited + head)
        }
    return nodesByBreadth(listOf(nodeValue), emptyList())
}

private fun <V> Graph<V, *>.neighbourValues(head: V) = nodes[head]!!.neighbors().map { it.value }


class P87Test {
    @Test fun `basic examples`() {
        assertThat("[a]".toGraph().nodesByDepthFrom("a"), equalTo(listOf("a")))
        assertThat("[a-b]".toGraph().nodesByDepthFrom("a"), equalTo(listOf("a", "b")))

        assertThat("[a]".toGraph().nodesByBreadthFrom("a"), equalTo(listOf("a")))
        assertThat("[a-b]".toGraph().nodesByBreadthFrom("a"), equalTo(listOf("a", "b")))
    }

    @Test fun `nodes connected in one line`() {
        "[a-b, b-c]".toGraph().let {
            assertThat(it.nodesByDepthFrom("a"), equalTo(nodeList("abc")))
            assertThat(it.nodesByDepthFrom("b"), equalTo(nodeList("bac")))
            assertThat(it.nodesByDepthFrom("c"), equalTo(nodeList("cba")))
        }
        assertThat("[a-b, b-c, c-d, d-e]".toGraph().nodesByDepthFrom("c"), equalTo(nodeList("cbade")))

        "[a-b, b-c]".toGraph().let {
            assertThat(it.nodesByBreadthFrom("a"), equalTo(nodeList("abc")))
            assertThat(it.nodesByBreadthFrom("b"), equalTo(nodeList("bac")))
            assertThat(it.nodesByBreadthFrom("c"), equalTo(nodeList("cba")))
        }
        assertThat("[a-b, b-c, c-d, d-e]".toGraph().nodesByBreadthFrom("c"), equalTo(nodeList("cbdae")))
    }

    @Test fun `graph with loop and disconnected node`() {
        "[a-b, b-c, b-e, a-c, a-d, f]".toGraph().let {
            assertThat(it.nodesByDepthFrom("c"), equalTo(nodeList("cbade")))
            assertThat(it.nodesByDepthFrom("d"), equalTo(nodeList("dabce")))
        }

        "[a-b, b-c, b-e, a-c, a-d, f]".toGraph().let {
            assertThat(it.nodesByBreadthFrom("c"), equalTo(nodeList("cbaed")))
            assertThat(it.nodesByBreadthFrom("d"), equalTo(nodeList("dabce")))
        }
    }

    @Test fun `directed graph traversal`() {
        "[a>b, b>c]".toGraph().let {
            assertThat(it.nodesByBreadthFrom("a"), equalTo(nodeList("abc")))
            assertThat(it.nodesByBreadthFrom("b"), equalTo(nodeList("bc")))
            assertThat(it.nodesByBreadthFrom("c"), equalTo(nodeList("c")))
        }
    }

    private fun nodeList(s: String) = s.toList().map(Char::toString)
}
Be first to comment
Leave a reply