What did you guys do with regards to parsing multi...
# advent-of-code
e
What did you guys do with regards to parsing multiple lines as one passport, what with the blank line?
I wanted to find (or write) something like
split { it.isBlank() }
to group items in a
List<String>
to
List<List<String>>
. But in the end I used
fold
. https://github.com/edgars-supe/advent-of-code/blob/master/src/main/kotlin/lv/esupe/aoc/year2020/Day4.kt
a
you could just split by \n\n
n
I did that too, though I wanted to do it another way
e
Yeah, but my input is
List<String>
.
n
that's a choice though of course
a
Yeah, it’s definitely just a choice. For me personally it was easier to avoid splitting by newline at first.
n
in an ideal world kotlin's standard library would have something like chunkedBy
👆 2
similar to
split
a
You could have a running index to keep track of where the current passport started, looping through the lines, when there’s a blank line update the running index & add that sublist to a passport list
e
Do you guys have set up a framework for your solutions or do you just kinda ad-hoc it? It's the former for me, so the raw input I get is always a line-by-line list.
@adamratzman That's pretty much what I did at first, but I didn't like the fact that I had to add the last passport to the list after the loop (which caused me a bit of confusion, because I didn't get the result I needed).
a
I just have this function to read in the input file, and I’ll just split by newline if needed
Copy code
fun readInput(filename: String): String {
    return File(ClassLoader.getSystemResource(filename).file).readText()
}
Yeah it’s definitely not ideal
Might add a chunkedby function honestly
n
I have a file for utilities
but I think a framework like that is probably more effort than its worth
I wouldn't want to commit to list<String> and that's a one liner to write, so I don't really see the benefit
I'm trying to write chunkedBy Adam but actually getting stuck on the multiple layers of
this
Copy code
fun<T, R> Sequence<T>.chunkedBy(key: (T) -> R) = sequence {
    val seq = this@Sequence

}
e
I dunno, it's my 3rd year and all the utilities and models (especially Point) have come in handy very well. Plus, at least now I have a reasonably presentable repo on my GH. 😄
n
How is point different from Pair?
👆 1
a
Yeah @Nir I agree. I’ll take a stab at chunkedBy too haha
n
do you know why kotlin is complaining about the
this@Sequence
?
e
n
coudl just make those extensions on Pair<Int, Int>
👆 1
a
yes, use this@chunkedBy instead @Nir
n
But from an AOC point of view I would have used almost none of that this year
a
might want to do chunkedBy on a generic collection instead tho?
actually nvm that’s a bad idea
n
Well, that's the curse of Kotlin, you have to write everything twice
once for sequences, one for iterable
unfortunately
a
ha, yep
n
the entirety of my utils file right now, btw edgars:
Copy code
val aocDataDir = File("C:\\Users\\quick\\Downloads\\advent_of_code")

operator fun File.div(s: String) = resolve(s)

fun<K, V> MutableMap<K, V>.update(k: K, transform: (V?) -> V) { this[k] = transform(this[k]) }

fun String.popSuffix(suffix: String) = if (endsWith(suffix)) removeSuffix(suffix) else null
then I just do `(aocDataDir / "day1.txt").readLines() or whatever
Thanks adom for the correction on chunked
👍 1
e
Well, a
Pair<Int, Int>
is not always a point. I mean, if you're the only dev, you know what's what and what a pair of Ints is for and whatever. Feels cleaner to me like this, though.
t
File reading is the the only helper code I retain from year to year because I can't stand having to rewrite it. 🙂 I should probably buckle down and write a
Point2D
and
Point3D
, but I kind of like rethinking them for the problem at hand each year.
❤️ 1
a
@Nir
Copy code
val lines = """not blank
    |line
    |
    |^ that was blank
    | so chunk
    |
    |chunk
    |
""".trimMargin().split("\n")
println(lines.chunkedBy { it.isBlank() })
Outputs
[[not blank, line], [^ that was blank,  so chunk], [chunk]]
e
Nice!
n
what's your chunkedBy implementation?
I just finished writing, haven't tested yet
Copy code
fun<T, R> Sequence<T>.chunkedBy(key: (T) -> R) = sequence {
    val iter = this@chunkedBy.iterator()
    if (!iter.hasNext()) return@sequence
    var currentList = mutableListOf(iter.next())
    var currentKey = key(currentList.first())

    for (t in iter) {
        val nextKey = key(t)
        if (nextKey == currentKey) {
            currentList.add(t)
            continue
        }
        yield(currentKey to currentList)
        currentKey = nextKey
        currentList = mutableListOf(t)
    }
    yield(currentKey to currentList)
}
a
Copy code
fun<T> List<T>.chunkedBy(key: (T) -> Boolean): List<List<T>> {
    val chunks = mutableListOf<List<T>>()
    var startingIndex = 0
    forEachIndexed { index, t ->
        if (key(t)) {
            chunks += subList(startingIndex, index)
            startingIndex = index + 1
        }
    }
    if (startingIndex < size) chunks += subList(startingIndex, size)
    return chunks
}
I decided I’d take the performance hit on lists
n
yeah my version works a bit differently
it's a little more flexible but not nearly as immediate to this use case
Copy code
sequenceOf("hello", "there", "", "bye", "there").chunkedBy {it.isEmpty()}.toList().also { println(it) }
prints:
Copy code
[(false, [hello, there]), (true, []), (false, [bye, there])]
So in this particular application, you'd just follow by filtering out all the true keys since those represent empty lines
but you can use this to chunk up anything that you want chunked up according to some contiguity property
for example if you have a list of numbers, you could use this to have chunks of positive runs and then negative runs (useful in some algos)
a
Definitely looks really nice. I was thinking more immediate applicability but I really do love that function lol
n
Yeah, it's modelled on pythons' groupby
I can't take credit 🙂
a
So this would be the corresponding list version then? @Nir
Copy code
fun <T, R> List<T>.chunkedBy(key: (T) -> R): List<Pair<R, List<T>>> {
    val chunks = mutableListOf<Pair<R, List<T>>>()
    var startingIndex = 0
    forEachIndexed { index, t ->
        val value = key(t)
        if (value != key(this[startingIndex])) {
            chunks += key(this[startingIndex]) to subList(startingIndex, index)
            startingIndex = index
        }
    }
    if (startingIndex < size) chunks += key(this[startingIndex]) to subList(startingIndex, size)
    return chunks
}