Now that all the AOC 22 challenges are published I...
# advent-of-code
a
Now that all the AOC 22 challenges are published I’m curious: • what Kotlin (or IDE) features did you find most helpful? • and what did you think was missing from Kotlin (or the stdlib)?
z
Currently I was missing the most Tree structure (day 7) :)
s
Collection functions like
windowed
, aggregations like
minOf
/
maxOf
/
minBy
/
maxBy
, functional goodies like
fold
and
runningFold
, pairs, destructuring, data classes — all this made it a pleasure to solve the puzzles in Kotlin. As for the missing features, something like Scala's
zipAll
would be nice for collections. And for the data classes I missed the possibility to do something like
state.copy(clayRobots += 1)
instead of
state.copy(clayRobots = clayRobots + 1)
.
I think I also missed
Pair.map()
on a couple of occasions...
e
transpose
is another missing one IMO
since I've been using Kotlin multiplatform during this event, there's a ton of missing stuff, but that won't apply to people using Kotlin/JVM
o
I feel that programming under time pressure is a special case for any IDE that tries to be helpful. The microseconds needed for a proper suggestion to pop up are sometimes quite annoying and if you are too fast in accepting a choice, it might be the wrong one...
For Kotlin, one of the most annoying things for me is that when you have to go from Int to Long, many places need attention because equality checks force you to add this annoying L at the end... when comparing using less ore greater, it works fine without, but because many times you check against 0 it is quite cumbersome to go from Int to Long (or back) again...
c
To reiterate @zokipirlo's concern. I participated in Advent of Code to learn about the std library (which I did learn about thanks to members contributions here and Kotlin team's videos on YouTube) and also noted the difficulty in finding a tree data structure. Is this a purposeful decision by the Kotlin team or can we expect to see the addition of more data structures? I got into Kotlin yes because Android but got excited by the Team's efforts in supporting and even organizing competitive programming events. Also, whilst there is multik - any hopes for better support for multi-dimensional arrays :) ?
e
In Kotlin you can declare a tree structure in one line. There’s not point in having it it the standard library, because there’s no chance it will ever turn out to be useful in real-life code. Even if you are faced with tree in real-life (e.g. when writing a compiler) you’d need a specialized one that you’ll declare from scratch anyway.
c
Ahh. Okay. Out of curiosity, would you be willing to explain how you could do it in Kotlin one line? I only know how to do it using classes from my DSA classes in undergrad.
e
Copy code
class Tree<T>(var value: T, var left: Tree<T>?, var right: Tree<T>?)
I'm curious what kinds of tree structures would have been useful this year; I don't think there were any
c
Thanks @ephemient. Did a face palm there though 😂 . I Tried using a Tree at first because of how Day 7's problem sounded ... At least to me. But I couldn't finish it so I gave up on the tree idea.
How did you approach it? I promised myself no spoilers but I'll allow this one ;)
e
oh I did feel that some number theory / algebra things are missing in Kotlin relative to other languages - gcd, lcm, rational numbers, etc. ended up having to write my own
@Chisomo Chiweza I actually was on the youtube show on that day specifically 🙂
c
Really? I hadn't watched the videos for the days I couldn't complete. I think I will just give up and go see them now 🏃‍♀️
j
• I'm not sure I can pick one. It seems that Kotlin contains a lot of various constructs that come handy in AoC (typically `windowed`/`chunked` , `sumOf`/`minOf`/`maxOf` /.., lot of
fold
/`reduce`/...) • As for missing features (judging by what I've addded to my utils), probably ◦ richer math (
gcd
,
lcm
, rational numbers with arithmetic) ◦ point2d, point3d, Manhattan distance ◦
IntProgression.size
split
on
List
(ideally with the option whether to keep or drop separator) However, all that can be rather easily added so overall I was satisfied with language features and the code was readable and concise. I was struggling a bit with inability to add static members to nested/inner classes but only because of the fact that the sample template did not wrap each day in a separate package/class (otherwise I would be able to declare top-level classes without having to worry about namespace clashes). Oh... and definitely private type aliases scoped to just a file so that they don't collide across days ie as they should work as per spec (https://youtrack.jetbrains.com/issue/KT-17699) (which is also solvable by putting individual days in separate packages)
o
As I am in love with typealiases to make my code more readable, I am really missing local typealiases because in my AoC project I have all days in the same package so I don't want to pollute my namespace from one puzzle to the other... I cannot really see why they are not supported (yet) in Kotlin...
Another thing that annoyed me several times was that I have some tools for AoC in a utils package, so if I need them I have to import utils.* and use them. In one day file I added a top level operator fun especially for that day that happened to collide with one of those in my utils - the one fun in the day is private, the one in utils is public. But instead of offering to import the public one, in new days IntelliJ always suggested to make the private one public instead ...
j
One thing I've missed here (and in production code) is the ability to easily create a copy of an object with the original being in scope. Gets to be a pain if I need to modify a field a few levels deep. I usually get around it with
object.run { copy(foo = foo.run { copy(...) } ) }
e
Arrow Lens helps with that, although it there's some associated conceptual and runtime cost to go with i
j
Of course it's not gonna happen, but I'd like indices start from 1, like in life 😁
j
@Jakub Gwóźdź what sort of monster are you. I'm always confused if someone starts counting from 1 😛
j
On more serious note - I’d love to see extension functions in coroutine lib that would allow parallel processing in sequences/lists/maps. as easy to use as
parallelStream
in java. I know there are flows, channels and whatnot (I did aoc2019 with them) but they are so far from idiomatic kotlin that each time I consider using them, I ask myself a question: Do I want to spend half a day now re-learning that API (and then worry if my solution does not work because it’s bad or because I misconfigured coroutines).
e
parallel map doesn't need flows or channels, just
Copy code
withContext(Dispatchers.Default) {
    inputs.map {
        async { computation(it) }
    }.awaitAll()
}
j
Everything wrapped in runblocking, I assume?
z
In coroutineScope or supervisorScope, depending of exception handling
e
or just call it from a
suspend fun main
n
Might be helpful to have multi year participants list their local "util" functions. For me, it's gcd, lcm, chunkedBy, dfs, bfs.
j
for me this year it was mostly Stack, Queue and PriorityQueue classes, with the same interface (offer(e), peek(), poll(), isNotNull()). It’s built on standard backing arrayLists, but having my interface makes it super convenient to quickly change search algorithms. ymmv 🙂
e
isn't that already built into
java.util.*
? unless you're targeting multiplatform like I was
a
any way to prevent silent
Int
overflows would be incredible, although I know this is something inherited from the JVM so it might not be possible. I unexpectedly ran into this in the first part of the monkeys-throwing-items puzzle and it took me ages to work out that was the problem (there was only one item in my input that overflowed at one moment in the sequence, so my answer was v. close to being right and manual stepping through looked like it was doing the right stuff). I know there are "safe" ways to write code to work around it, but that still means remembering to write special code where really you just want to write simple arithmetic and have something automatic tell you if you're overflowing. And opting to always write with
Long
isn't amazing either, due to fiddliness mentioned by others (having to stick
L
on the end of constants, and converting between stdlib things that return
Int
such as list sizes/indices etc). Having a runtime flag like
--error-on-arithmetic-overflow
or something that threw a RuntimeException whenever this happened would be fantastic.
j
@ephemient it is but I'm feeling dirty every time I am to import java.* in AoC 🙂 . I did it on jvm this year but previously I indeed used multiplatform for browser visualizations.
j
I implemented an adjacency graph this year and a generic dijkstra
a
a pure-Kotlin BigDecimal/BigInteger would be nice
e
re: overflow, I created some wrapper types that enforce no overflow, https://github.com/ephemient/kotlin-numeric/blob/main/src/commonMain/kotlin/CheckedInt.kt etc. but it requires conversion similar to `Int`/`Long` now
I also wrote a Java agent that rewrites bytecode to turn on and off checked arithmetic, example https://github.com/ephemient/kotlin-numeric/blob/main/agent-test/src/test/kotlin/AgentTest.kt it's not the right place to do it, though. a compiler plugin that could actually change code generation in different blocks would be best, but I probably won't touch that until there's a stable API
note, it is necessary to allow overflow in some parts of code. for example,
UInt
depends on
Int
overflow behavior
I also wrote a BigInteger implementation, https://github.com/ephemient/kotlin-numeric/blob/main/src/nativeMain/kotlin/BigInteger.kt + https://github.com/ephemient/kotlin-numeric/blob/main/src/jvmMain/kotlin/BigInteger.kt + https://github.com/ephemient/kotlin-numeric/blob/main/src/jsMain/kotlin/BigInteger.kt + https://github.com/ephemient/kotlin-numeric/blob/main/src/nativeMain/kotlin/BigInteger.kt - it delegates to Java BigInteger, JS BigInt, only native needs a pure Kotlin implementation. it is unquestionable worse performing than GMP or OpenSSL; the problem is that you can't do proper native resource management in the same interface. but if that could somehow be fixed (finalizers in K/N?) then a binding would be possible
big integers are never necessary for AOC though
c
Missing from stdlib/Kotlin: 1. IntRange.contains(other: IntRange) 2. Some kind of Kotlin library focused on Graphs (similar to pet-graph in Rust) and their algorithms? Probably asking too much to be a part of stdlib, but a helper library might be a nice project idea. 3. Type aliases inside classes 4. Better Handling of functions like .map and .filter that turns Set<*> -> Set<*> instead of Lists<*> all the time, so I don't have to use .filterTo to retain the set properties. 5. Faster Sequences plz 6. Matrices, Tensors, etc (and associated methods) as a core language/stdlib feature (might help for ML as well) What I liked about Kotlin for AoC 1. Doesn't railroad you into a specific programming style, you can switch between functional + imperative depending on problem 2. !!!!! data class !!!!!! 3. method chaining by default on stdlib, and the scope functions when that's not possible 4. just an overall clean style, a good balance between ease of python and speed/rigor of other JVM langs.
a
^ yes to 4. - I’d expect filtering a set should return a set
val shouldBeASet: Set<Integer> = setOf(1).filter { it == 1 }
another couple of extras that would have been handy 1. a simple logging framework - trying to debug specific behaviour was a pain sometimes, and my printlns got out of hand 2. some way to easily cache/memoize functions (something like how there’s a built in
by lazy {}
delegate) Both of these (as well others like BigDecimal, a tree structure, sorted list, priority queue, etc) can be implemented manually, but AOC is on the line between “implementing X myself would be easy, but more difficult than this actual challenge - and I don’t want to introduce a bug!” Plus, especially with AOC, it’s nice to have code that’s contained within a single file without external libraries, and also if the stdlib provides it then I can better read & understand code from others, who might have some custom implementation.
j
.map {…}
should not change number of elements in a collections. and since
setOf(2, 4, 6, 8, 10).map {it/3}
returns
[0, 1, 2, 2, 3]
, it needs to be a
List
, to be able to contain both
2
s
r
windowed
,
intersection
and
chunking
K
c
.take/.filter/.drop however all return lists, because they're based of the general iterable methods, instead of preserving the set property. It might also be worth introducing a .mapDistinct that produces a set instead of a list. I just want to work with sets when appropriate instead of using lists and then converting back to sets all the time.
e
"missing" 1, contains is ambiguous. contains*All* or contains*Any* "missing" 4, already doable with
.mapTo(mutableSetOf()) { ... }
or
buildSet { .mapTo(this) { ... } }
"missing" 5, how? both "missing" 2 and 6 should be external libs. there's already several, https://kotlinlang.org/docs/data-science-overview.html#kotlin-libraries
I wonder if there is any space left for Katelyn to explore structured logging. the Java world has already covered that comprehensively and you should be able to just pick one.
a simple helper like
Copy code
abstract class Memo<T, R> : (T) -> R {
    private val cache = mutableMapOf<T, R>()
    override fun invoke(key: T): R =
        cache.getOrPut(key) { compute(key) }
    abstract fun compute(key: T): R
}
    
fun <T, R> Memo(compute: (T) -> R): Memo<T, R> = object : Memo<T, R>() {
    override fun compute(key: T): R = compute(key)
}
is doable but figuring out an API to handle different cache eviction policies and and thread safety modes... not sure obvious
a
big integers are never necessary for AOC though
tell that to 2019 day 22 😈
e
https://github.com/ephemient/aoc2019/blob/kt/src/main/kotlin/io/github/ephemient/aoc2019/Day22.kt they weren't necessary then, and like day 11 this year, you're better off avoiding them
a
oh, I was in Scala that year and my solution broke with
Long
, had to move to big integers to get the right answer. But many years ago now, not about to re-open that can of worms 🪱 🙈
e
you did have to split some multiplication to avoid overflow but that's ok https://github.com/ephemient/aoc2019/blob/kt/src/main/kotlin/io/github/ephemient/aoc2019/Common.kt (ignore all the
java.*
references, that's all possible in pure Kotlin now)
e
@ephemient Btw, you don't need
properMod
. There is
mod
in the standard library: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/mod.html
a
That’s the code for AOC 2019, was
mod
in stdlib in 2019?
e
Good catch. It's available since Kotlin 1.5 (circa 2021), so, indeed, it was not available in 2019.