Introduction
The Day 9 problem requires from us searching through the defined heightmap. We can make it using commonly known algorithms for searching graphs where our graph can be seen as the positions from the map connected with each other if they are adjacent on map. Let’s see then how to implement the DFS and BFS search algorithms in Kotlin 🔍.
Solution
In order to get the answer for the first part of the problem it’s enough if for every position from our map, we filter the positions that are the low points and sum the values from map on these positions increased by 1 as stated in problem description.
For the second part, we define some useful search
method that is capable of search the map in the DFS and BFS order.
Usually we would write these algorithms with some recursive function that calls itself in order to visit siblings nodes.
For example, in case of DFS search, we could define
|
|
which would allow us to visit node, visit its first child, then the first child of first child etc. But we have to
remember that for bigger data such definition would not work (and wouldn’t be really efficient)
because calling function go
brings some extra cost in space and time.
In such cases we usually define the iterative version of these algorithms that from my perspective may be harder to understand and keep clean in code. We could try to define such method in the following way
|
|
where we see that the order of search depends on the order of removing elements from queue
. You can look at it in the
following way:
- for DFS we inserted some sibling node to
queue
and the next node that we want to visit is this node so the last in thequeue
- let’s remove then the last element fromqueue
and start searching from it (you can know it as Last-In, First-Out or LIFO order) - for BFS, at first we want to add all siblings of current node to the
queue
and then start searching from the first node that was inserted in the past - let’s remove then the first element fromqueue
and start searching from it (you can know it as First-In, First-Out or FIFO order)
In Kotlin, we don’t have to use the while
loop to express our intention of searching because it can be generated for
us by the compiler. If we want to stay with the “recursive” approach that seems to be more readable for more people and
have an efficient search algorithm, we can use the
tailrec fun
in Kotlin in the following way:
|
|
This definition will be translated to the loop as in previous example by Kotlin compiler, because all
exits from inner go
method are the tail calls of this method. It’s required that no transformation is
done on the result of recursive call of function to name it a tail recursive function and to optimize its calls.
Day9.kt
|
|
Extra notes
Let’s see how we defined the fun Pos.isValid()
. It’s a private function in the class Map
which is an extension
of Pos
class. Thanks to such approach we could call it without specifying the receiver explicitly, but rather
using this
as implicit receiver. It’s the most common approach to do this for private function because they all can be
called only inside the class implementation so the this
receiver of outer class doesn’t have to be defined explicitly.
Notice also the definition of indices
property of our Map
- it’s a good practice to create functions and properties
that are similar to the definitions from the standard library (not only in Kotlin but Kotlin provides us really
consistent standard library naming).