Introduction
The Day 6 problem shows how important proper representation of problem is. The natual way of solving this problem doesn’t work in the second part as it would produce exponential size of data. Let’s see then how quickly this problem can be solved and how we can deal with immutable data in Kotlin.
Solution
We define the afterDay
function for LanternFish
which returns fish after single day as new objects.
This approach is commonly used in functional programming, when dealing with immutable data - we don’t
modify the internal state of objects, but instead we return some new objects with modified internal state.
In this way our code becomes more readable because we can assume immutability of the objects. The same
assumption is applied to FishShoal
for its afterDays
function. However, in this approach we have to use
fold
function to write the simulation of shoal state (and not to use some additional local variable).
In our solution we keep counts of every “type” of fish in shoal using some internal map counts
.
Instead of just keeping all fish on some list, we can notice that there is a limited number of types of
fish, because fish can have timer with only limited values. That’s enough to solve this problem
efficiently and get the result in less than one second.
Day6.kt
|
|
Extra notes
Let’s notice that it was only one day, and we used the DefaultMap<K, V>
again in our code. That made my day -
it shows how useful was the definition of this helper and that it can be useful also in some future problems 😎.
What’s worth noting here is the Kotlin way to express counting after grouping objects by som property (that was used
in toFishShoal
definition). We can try to generalize that function as
|
|
which can later be used for example as
|
|
in order to count the number of occurrences of object on list.
We should pay extra attention here to the crossinline
modifier and understanding what it does in Kotlin code.
Well, it’s stated in documentation that when the lambda parameter of the inline function is defined as crossinline
,
then this parameter cannot use non-local returns. What that means in practice is we cannot use some return
in the
crossinline
lambda body that would cause jump out of some outer scope.
Using some good example, we can look at the definition of function from standard library, e.g.
|
|
For this function, the action
parameter is not defined as crossinline
so there are two types of returns from action
allowed.
The first one is the local return that causes jumping out of the execution of the action
lambda, so when we use it in the
following way
|
|
we can see printed out to the console
|
|
because we jumped out from printing action for it == 2
.
In the next situation when “standard” return
instruction is used
|
|
we get
|
|
as program result in the stdout as we jumped out from the main
for it == 2
.
We can see with these examples that the second return
caused jumping out of the main
function, while return@forEach
finishes only execution of single action
.
If we used the crossinline
modifier for action
parameter, then the second construct would be forbidden. Yes, that’s
so simple and allows us to express our intention what the action
should be capable of doing, when designing some functions.
I hope these examples show more clearly how this modifier works and when could be used in our code - it’s somehow tricky because it’s hard to find a good example on the Internet but by playing with the language we can learn how it really works and why it was introduced to the language ✌.