Advent of Code 2023 day 9
12/09/2023, 5:00 AMMichael de Kaste
12/09/2023, 5:14 AMobject Day9 : Challenge() {
val parsed = input.lines().map { it.split(" ").map(String::toInt) }
private fun next(list: List<Int>): Int = when (list.all { it == 0 }) {
true -> 0
else -> list.last() + list.zipWithNext { a, b -> b - a }.let(::next)
}
override fun part1() = parsed.sumOf(::next)
override fun part2() = parsed.map(List<Int>::reversed).sumOf(::next)
}
bj0
12/09/2023, 5:19 AMMarcin Wisniowski
12/09/2023, 5:29 AMMarcin Wisniowski
12/09/2023, 5:29 AMMarcin Wisniowski
12/09/2023, 5:29 AMbj0
12/09/2023, 5:32 AMfun findNext(list: List<Int>): Int {
val diffs = list.windowed(2) { (a, b) -> b - a }
if (diffs.all { it == 0 }) return list.last()
return list.last() + findNext(diffs)
}
part1 {
lines.map { it.getIntList() }.sumOf { findNext(it) }
}
part2 {
lines.map { it.getIntList().reversed() }.sumOf { findNext(it) }
}
Lukas Lebovitz
12/09/2023, 5:32 AMMichael de Kaste
12/09/2023, 5:37 AMprivate fun next(list: List<Int>): Int = when (list.all { it == 0 }) {
true -> 0
else -> list.last() + list.zipWithNext { a, b -> b - a }.let(::next)
}
TimedValue(value=["Day9", 1953784198, 957], duration=1.010900ms)
vs.
private fun next(list: List<Int>): Int = list.lastOrNull()
?.let { last -> last + list.zipWithNext { a, b -> b - a }.let(::next) }
?: 0
TimedValue(value=["Day9", 1953784198, 957], duration=944.6us)
Michael de Kaste
12/09/2023, 5:40 AMJonathan Kolberg
12/09/2023, 5:41 AMfun Sequence<Long>.extrapolateValue(): Long {
val rows = generateSequence(this.toList()) { lastRow ->
lastRow.windowed(2).map { (a, b) ->
b - a
}
}.takeWhile { it.any { n -> n != 0L } }
return rows.sumOf { it.last() }
}
And then summing it up, yields the same result as some of the other solutions, but aoc says that is a wrong result, I'm baffeled.
Edit: my problem was that number parsing only got positive numbers.Jakub Gwóźdź
12/09/2023, 5:45 AMJakub Gwóźdź
12/09/2023, 5:46 AMnvbahool
12/09/2023, 5:53 AMJonathan Kolberg
12/09/2023, 5:54 AMMichael de Kaste
12/09/2023, 5:56 AMfun List<Int>.next(): Int = lastOrNull()?.let { it + zipWithNext { a, b -> b - a }.next() } ?: 0
object Day9 : Challenge() {
private val parsed = input.lines().map { it.split(" ").map(String::toInt) }
override fun part1() = parsed.sumOf(List<Int>::next)
override fun part2() = parsed.map(List<Int>::reversed).sumOf(List<Int>::next)
}
ephemient
12/09/2023, 5:59 AMJakub Gwóźdź
12/09/2023, 6:07 AMVamsi
12/09/2023, 6:18 AMfold
for the first time. it's fun 🙂
https://github.com/bravura-quark/aoc-kotlin-2023/blob/main/src/Day09.ktTomasz Linkowski
12/09/2023, 6:21 AMrunningFold
for me again (I did everything as in the story - didn't realize it's just possible to sum the last values in part 1):
fun List<List<Long>>.extendFromRight() = reversed()
.runningFold(last()) { prevNumbers, curNumbers -> curNumbers.append(prevNumbers.last()) }
.reversed()
fun List<List<Long>>.extendFromLeft() = reversed()
.runningFold(last()) { prevNumbers, curNumbers -> curNumbers.prepend(prevNumbers.first()) }
.reversed()
PoisonedYouth
12/09/2023, 6:24 AMAnirudh
12/09/2023, 6:32 AM.sum()
to .reduceRight(Int::minus)
edit: and one it.last()
to it.first()
andriyo
12/09/2023, 6:37 AMmySequence.toList()
at one point in Part 2. Not sure if it's due to fundamental nature of the problem or it's possible to do it fully thru streams without terminating in a collection.Paul Woitaschek
12/09/2023, 6:39 AMAnirudh
12/09/2023, 6:44 AMThe only thing I'm not happy about is that I needed to do@andriyo yup, that caught me out too. in my case withat one point in Part 2mySequence.toList()
reduceRight
and in your case with reversed
maybe that the designers wanted us to be conscious of the fact that reversing a sequence (or doing anything from right to left) requires having the full result, and hence no longer a "sequence" but a collectionandriyo
12/09/2023, 6:48 AMandriyo
12/09/2023, 7:02 AMAnirudh
12/09/2023, 7:02 AMmaiatoday
12/09/2023, 7:05 AM-?
Paul Woitaschek
12/09/2023, 7:06 AMAnirudh
12/09/2023, 7:09 AMandriyo
12/09/2023, 7:11 AMNeil Banman
12/09/2023, 7:17 AMNeil Banman
12/09/2023, 7:18 AMPaul Woitaschek
12/09/2023, 7:18 AMNeil Banman
12/09/2023, 7:19 AMMichael de Kaste
12/09/2023, 7:20 AMPaul Woitaschek
12/09/2023, 7:20 AMAnirudh
12/09/2023, 7:22 AMPaul Woitaschek
12/09/2023, 7:22 AMpublic inline fun <T> Iterable<T>.sumOf(selector: (T) -> Long): Long {
It’s therePaul Woitaschek
12/09/2023, 7:23 AMDavio
12/09/2023, 7:24 AMNeil Banman
12/09/2023, 7:24 AMPaul Woitaschek
12/09/2023, 7:25 AMOverloadResolutionByLambdaReturnType
Davio
12/09/2023, 7:25 AMprivate fun getNextValue(history: List<Long>) : Long {
if (history.distinct().size == 1) {
return history.first()
}
return history.last() + getNextValue(history.zipWithNext { a, b -> b - a })
}
I decided on using a recursive function that would continue until all of the elements were the same (they do not have to be 0).
If the elements are not all the same, we can use zipWithNext
to create the derived history (history' if you will) and pass it to the recursive function and get its next valueDavio
12/09/2023, 7:26 AMDavio
12/09/2023, 7:27 AMDavio
12/09/2023, 7:27 AMNeil Banman
12/09/2023, 7:28 AMIt does not have?OverloadResolutionByLambdaReturnType
Paul Woitaschek
12/09/2023, 7:28 AMAndre T
12/09/2023, 7:30 AMNeil Banman
12/09/2023, 7:30 AMDavio
12/09/2023, 7:30 AMmaiatoday
12/09/2023, 7:31 AMDavio
12/09/2023, 7:31 AMDavio
12/09/2023, 7:32 AMephemient
12/09/2023, 7:35 AMandriyo
12/09/2023, 7:48 AMDan Fingal-Surma
12/09/2023, 7:54 AMDan Fingal-Surma
12/09/2023, 7:57 AMgenerateSequence
after first solving with a MutableList
ephemient
12/09/2023, 7:58 AMAndre T
12/09/2023, 7:59 AMRiccardo Lippolis
12/09/2023, 8:12 AMList<Int>.reversed()
appeared to perform better than List<Int>.asReversed()
, which I did not expect at first... am I doing so many lookups that the reversed index lookup (which is done by the implementation returned by asReversed()
) becomes too expensive to outperform building a new reversed list?ephemient
12/09/2023, 8:15 AMDan Fingal-Surma
12/09/2023, 8:15 AMephemient
12/09/2023, 8:15 AMasReversed
wrapper might be more expensive than creating a new simple objectDan Fingal-Surma
12/09/2023, 8:16 AMRiccardo Lippolis
12/09/2023, 8:20 AMReversedListReadOnly
, there is an extra bounds check, which is performed on every indexed lookup, but when iterating it's only performed when creating the iterator, so that would explain why you don't see the same result indeed. But indeed, too small to actually make a difference anyway 🙂Dan Fingal-Surma
12/09/2023, 8:21 AMzipWithNext
Jaap Beetstra
12/09/2023, 8:36 AMfun calculateNext(list: List<Int>): Int = if (list.all { it == 0 }) 0 else
list.last() + calculateNext(list.zipWithNext { a, b -> b - a })
fun part1(input: List<String>): Int = input.map { it.splitToInts() }.sumOf { calculateNext(it) }
fun part2(input: List<String>): Int =
input.map { it.splitToInts() }.map { it.reversed() }.sumOf { calculateNext(it) }
Michael de Kaste
12/09/2023, 8:45 AMGrzegorz Aniol
12/09/2023, 8:49 AMephemient
12/09/2023, 8:57 AM.sumOf { it.await() }
instead of .awaitAll().sum()
Dan Fingal-Surma
12/09/2023, 8:57 AMx.map { async { block } }.awaitAll()
is just x.map { block }
if the code isn’t really asyncephemient
12/09/2023, 8:58 AMDefault
dispatcher so it does actually spawn threadsephemient
12/09/2023, 8:58 AMphldavies
12/09/2023, 9:07 AMphldavies
12/09/2023, 9:12 AMasReversed
was slower than reversed
, and I'm only iterating that first list level, not indexing.Grzegorz Aniol
12/09/2023, 9:19 AM.awaitAll()
is seems to be better, because it's suspended function, can be cancelled and it fails immediately any of waiting job fails. I agree this is not more performant for this task, but I want to try and play with it this year 😉Norbert Kiesel
12/09/2023, 9:25 AMJakub Gwóźdź
12/09/2023, 9:27 AMphldavies
12/09/2023, 9:28 AMzipWithNext
when used with a transform
avoids allocating. When used without a transform
it will allocate a Pair<T, T>
which has property-access for first
and last
, and windowed
will allocate a list with two elements, which has index-access for the component1()
and component2()
(assuming destructuring). It may reuse the underlying list iirc when provided with a transform.phldavies
12/09/2023, 9:31 AMwindowed(2) { (a, b) -> b - a }
more performant than zipWithNext { a, b -> b - a }
phldavies
12/09/2023, 9:31 AMWarmup finished after 10.000029300s with 3183 iterations
year 2023 day 9 part 1
Window took 320.367us (1.00x): 1743490457
Default took 349.463us (1.09x): 1743490457
WindowMap took 804.369us (2.51x): 1743490457
year 2023 day 9 part 2
Window took 329.255us (1.00x): 1053
Default took 355.929us (1.08x): 1053
WindowMap took 817.143us (2.48x): 1053
phldavies
12/09/2023, 9:35 AMkotlin.collections.MovingSubList
outperforms a normal list iterator.phldavies
12/09/2023, 9:56 AM@AoKSolution
object Day09MutArray : PuzDSL({
fun IntArray.next() = apply {
for (i in lastIndex downTo 1)
for (j in 0..<i)
this[j] = this[j + 1] - this[j]
}.sum()
val parser = lineParser {
it.split(' ').mapNotNull(String::toIntOrNull).toIntArray()
}
part1(parser) { histories -> histories.sumOf { it.next() } }
part2(parser) { histories ->
histories.sumOf {
it.reverse()
it.next()
}
}
})
This seems to be as fast as (if not slightly faster) @ephemient’s maths based solution - which I find interesting 😄 - then again, my "benchmarking" isn't the most precise.Riccardo Lippolis
12/09/2023, 10:01 AMphldavies
12/09/2023, 10:02 AMandriyo
12/09/2023, 10:16 AMjonas.app
12/09/2023, 10:28 AMdifference at each stepI totally misunderstood the description and assumed the difference at a step is always an absolute value.
phldavies
12/09/2023, 10:41 AMBenchmark Mode Cnt Score Error Units
BenchDay09.arrayPart1 thrpt 3 6773.200 ± 47.510 ops/s
BenchDay09.arrayPart2 thrpt 3 6557.496 ± 960.668 ops/s
BenchDay09.mathsPart1 thrpt 3 7589.826 ± 284.387 ops/s
BenchDay09.mathsPart2 thrpt 3 7546.072 ± 765.402 ops/s
ephemient
12/09/2023, 10:50 AMLong
while yours should be still be OKNikita Merinov
12/09/2023, 10:53 AMephemient
12/09/2023, 10:56 AMephemient
12/09/2023, 11:01 AM[z] = 1z
[y, z] = 2z - 1y
[x, y, z] = 3z - 3y + 1x
[w, x, y, z] = 4z - 6y + 4x - 1w
[v, w, x, y, z] = 5z - 10y + 10x - 5w + 1v
[u, v, w, x, y, z] = 6z - 15y + 20x - 15w + 6v - 1u
once you see the pattern, it's pretty easy to proveJakub Gwóźdź
12/09/2023, 11:03 AMNikita Merinov
12/09/2023, 11:09 AMKai Yuan
12/09/2023, 11:21 AMJonathan Kolberg
12/09/2023, 12:02 PMEl Anthony
12/09/2023, 12:25 PMKash Kabeya
12/09/2023, 12:28 PMprivate const val PART_ONE_EXPECTED = 114L
private const val PART_TWO_EXPECTED = 2L
private val space = """\s+""".toRegex()
fun main() {
Logger.debug = true
fun compute(input: List<String>, partOne: Boolean): Long {
val op: (Long, Long) -> Long = if (partOne) Long::plus else Long::minus
val getter: (List<Long>) -> Long = if (partOne) ({ it[it.size - 1] }) else ({ it[0] })
fun findNextValue(list: List<Long>): Long {
val intervals = list.windowed(2).map { it[1] - it[0] }
return op(getter(list), if (intervals.distinctBy { it }.size == 1) intervals[0] else findNextValue(intervals))
}
return input.sumOf { findNextValue(it.mapLongs(space)) }
}
fun part1(input: List<String>): Long = compute(input, true)
fun part2(input: List<String>): Long = compute(input, false)
val input = readLines("Day09")
val inputTest = readLines("Day09-Test")
check(part1(inputTest) == PART_ONE_EXPECTED)
check(part2(inputTest) == PART_TWO_EXPECTED)
println(part1(input))
println(part2(input))
}
phldavies
12/09/2023, 12:29 PMCharles Flynn
12/09/2023, 12:36 PMNikita Merinov
12/09/2023, 1:45 PM[1, 2, 4]
result is 1 * 1 + (-3) * 2 + 3 * 4 = 7
For input [1, 3, 6]
result is 1 * 1 + (-3) * 3 + 3 * 6 = 10
On the other hand newton's one is more complex - it doesn't give straight coefficients to use and involves calculation of divided differences (it is almost the same what we were doing in the puzzle if following by the problem description), and computations will be different for different data points even if the number of points is the same.
E.g. for [1,2,4]
result 7 is calculated by 1 + (2-1) * (3-0) + (4-2 - (2-1))/2 * (3-0) * (3-1)
E.g. for [1,3,6]
result 10 is calculated by 1 + (3-1) * (3-0) + (6-3 - (3-1))/2 * (3-0) * (3-1)
Of course it doesn't change anything, but I had to comment on "lagrange vs newton interpolation" topic 🙂ephemient
12/09/2023, 2:08 PMN(x) = \sum_{i=0}^{k}{s \choose i}i!{h}^{i}[y_0,\ldots,y_i]
simplifying with h=1ephemient
12/09/2023, 2:10 PMx
and evaluating, which is what you'd get doing it the Lagrange way, although that's valid tookqr
12/09/2023, 3:18 PMDavio
12/09/2023, 3:24 PMNikita Merinov
12/09/2023, 4:26 PM"Newton forward divided difference formula" is recorded as
Copy codeN(x) = \sum_{i=0}^{k}{s \choose i}i!{h}^{i}[y_0,\ldots,y_i]
simplifying with h=1That was exactly the formula I've used to show calculations for inputs
[1,2,4]
and [1,3,6]
. The last part of that formula - [y0,...,yi]
- is the notation of divided differences. So it does not directly use input data (like 1,2,4 or 1,3,6), but their differences and differences of differences etc.
I'm not producing a polynomial onThere is no need to produce polynomial itself (I'm not even sure how one would do it dynamically, by building expression tree maybe), since we knowand evaluating, which is what you'd get doing it the Lagrange way, although that's valid toox
x
we are searching interpolation for. Probably my sentence "polynomial coefficients" was bit misleading. Lagrange interpolation formula looks like (screenshot), and it operates directly on input (in opposite to newton's one). E.g. for the given input [1,2,4]
you only need to calculate coefficients c1, c2, c3
, so that the final result is c1 * 1 + c2 * 2 + c3 * 4
.ephemient
12/09/2023, 5:07 PMx
. if you were to apply it to this problem, you could simply compute it once and then evaluate it at x=lastIndex+1
and x=-1
. but that requires more computation and is not what I builtephemient
12/09/2023, 5:08 PMnumpy.polynomial
as Polynomial.fit
, if you were in that environment)Andrei Kovalevsky
12/09/2023, 10:17 PMNikita Merinov
12/10/2023, 8:54 AMx
. Lagrange is not special here, and so newton is not. "Newton forward divided difference formula" is specialized for evenly spaced x
- it just means it simplifies calculation a bit - but it still gives you a polynomial, which should work on any x
correctly spaced from the last one.
But I'm afraid the discussion went in different direction.
My very first point was about the formula you have provided - [x, y, z] = 3z - 3y + 1x
(and others) and your statement that it is a special case of newton polynomial.
My comment was simply that - it's rather special case of lagrange, not newton. As I cannot see how one can easily transform newton formula to computation like 3z - 3y + 1x
. If I'm wrong, could you please show how that can be done?Tolly Kulczycki
12/10/2023, 8:28 PMTolly Kulczycki
12/10/2023, 8:29 PM