As an aside: This season I have been using this co...
# advent-of-code
m
As an aside: This season I have been using this construct a lot for accessing a layered list/array:
grid.getOrNull(y)?.getOrNull(x)
Wouldn't it be cool if Kotlin had a "safe array index" operator, for example like so:
grid?[y]?[x]
grid[y]?[x]?
❤️ 2
p
It even is more confusing that lists have notnull [] but maps have nullable []. This could be improved by having explicit language construct to differentiate between nullable [] and notnullable [] .
👍 3
m
Maybe even a compiler switch to make it use getOrNull instead of get 🙂
^ (not being serious)
m
grid?[y]?[x]
is grid nullable or getting y nullable? 😉
e
you can define your own extension function
Copy code
operator fun <T> List<List<T>>.get(y: Int, x: Int): T? = getOrNull(y)?.getOrNull(x)
and then write
grid[y, x]
🤩 1
m
@Michael de Kaste Yes, my suggestion was not well thought through 😉 How about
grid[y]?[x]?
? That would be more consistent 🙂
@ephemient Cool! I didn't know that the get operator function could be defined with more than one parameter 😎
1
m
@Petr Sýkora as with anything kotlin related, this has everything to do with the consistency with Java.
Maps have nullable gets because thats what java has, Lists have gets that return the element (which can be null) or throw an exception if out of bounds, which is also java. I think the worst offender is indexOf() which still returns '-1' instead of null of not found.
👍 2
p
Yeah, in jvm it “makes” sense and I understand the legacy. But it does not make much sense from Kotlin multiplatform point of view. Any language/ecosystem has it´s inconsistencies, so no big deal.
e
if it weren't this way, it wouldn't be possible to use (safely) Kotlin functions on Java collections
and while it's not always possible, most of Kotlin stdlib code works the same across platforms. garbage collection handles cycles, integer arithmetic wraps on overflow, longs go up to 64 bits, strings can potentially contain unpaired surrogates… not because those are the best choices on each platform, but because it keeps consistency
p
Also it would probably be too big of a breaking change to justify even if it was a big deal. EDIT: (my suggestion, not OP´s)
m
^ Of course what I am proposing would not be a breaking change at all, it would be new Kotlin syntax. Basically a new operator which maps to the getOrNull function instead if the get function. 🙂
👍 1
e
we already have the extension function
.getNotNull
and if you think that's too long to type you can always alias it
Copy code
import kotlin.collections.getOrNull as ʔ
listOf(1, 2, 3).ʔ(4)
I don't think the proposed syntax works either, because
Copy code
grid[y]?[x]?.toString()
would be ambiguous between
Copy code
grid[y]?[x] ?.toString()
and
Copy code
grid[y]?[x]? .toString()
n
Also, just seems very coding challenge-specific. And accommodating of one specific (albeit common in AoC) pattern. I’m personally irrationally allergic to multidimensional arrays and almost always store everything in a 1D Grid class extending List, with various methods to handle the conversions.
t
I go a slightly different route on this. I define the
contains
operator and check that points are on the grid (
point in grid
) before I call the
get(point)
operator which doesn’t do any error checking (
this[point.y][point.x]
). It works for me to do the bounds checking rather than returning a nullable type from get. If this weren’t for Advent of Code, I would probably consider writing these with more safety in mind.
n
@todd.ginsberg this is super ergonomic but I decided not to go that route because my grid extends list and list already has an established (and useful) contains function that looks at elements rather than index. This can still work with a little ambiguity since the special contains case is limited to the point class. But then again I do plenty of lists of points so it’s best to keep it separate.
👍🏻 1
m
@Neil Banman Been there, done that - this season I'm really lazy and for grids I mostly work on the "List of Strings" structure directly unless it isn't feasible from a performance standpoint. In the past I also went the opposite way and parsed everything into a "Map<Point, Content>" like structure.
n
My Grid class is for when I’m lazy. input.toGrid(), or input.toGrid(Char::digitToInt) or whatnot, and I’m off and running. These days when I’m not being lazy I just work with the string directly, like on today’s puzzle.
m
^ I'm always coding AoC from scratch, without using any pre-written helpers, the solution is always a simple main function that reads from stdin and prints two lines to stdout. Within that paradigm it's just really easy to write
generateSequence(::readLn).toList()
🙂
👍 3