Refactor day 6 of AoC 2023

This commit is contained in:
Ivan R. 2024-12-21 10:52:15 +05:00
parent 3a04689db8
commit f58568de12
Signed by: lumin
GPG key ID: E0937DC7CD6D3817
18 changed files with 201 additions and 460 deletions

View file

@ -1,11 +1,11 @@
.gradle
gradle*
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### IntelliJ IDEA ###
/.idea
.idea
*.iws
*.iml
*.ipr
@ -13,8 +13,8 @@ out/
!**/src/main/**/out/
!**/src/test/**/out/
# Android studio
/local.properties
### Kotlin ###
.kotlin
### Eclipse ###
.apt_generated

View file

@ -0,0 +1,102 @@
# Advent of Code 2023 day 6 solution in Kotlin
## Wait For It
[Task page](https://adventofcode.com/2023/day/6)
The ferry quickly brings you across Island Island. After asking around, you discover that there is indeed normally
a large pile of sand somewhere near here, but you don't see anything besides lots of water and the small island
where the ferry has docked.
As you try to figure out what to do next, you notice a poster on a wall near the ferry dock.
"Boat races! Open to the public! Grand prize is an all-expenses-paid trip to Desert Island!"
That must be where the sand comes from! Best of all, the boat races are starting in just a few minutes.
You manage to sign up as a competitor in the boat races just in time. The organizer explains that it's not really
a traditional race - instead, you will get a fixed amount of time during which your boat has to travel as far
as it can, and you win if your boat goes the farthest.
As part of signing up, you get a sheet of paper (your puzzle input) that lists the time allowed for each race
and also the best distance ever recorded in that race. To guarantee you win the grand prize, you need to make sure you
go farther in each race than the current record holder.
The organizer brings you over to the area where the boat races are held. The boats are much smaller than
you expected - they're actually toy boats, each with a big button on top. Holding down the button charges the boat,
and releasing the button allows the boat to move. Boats move faster if their button was held longer,
but time spent holding the button counts against the total race time. You can only hold the button at the start
of the race, and boats don't move until the button is released.
For example:
```
Time: 7 15 30
Distance: 9 40 200
```
This document describes three races:
- The first race lasts 7 milliseconds. The record distance in this race is 9 millimeters.
- The second race lasts 15 milliseconds. The record distance in this race is 40 millimeters.
- The third race lasts 30 milliseconds. The record distance in this race is 200 millimeters.
Your toy boat has a starting speed of zero millimeters per millisecond.
For each whole millisecond you spend at the beginning of the race holding down the button,
the boat's speed increases by one millimeter per millisecond.
So, because the first race lasts 7 milliseconds, you only have a few options:
- Don't hold the button at all (that is, hold it for 0 milliseconds) at the start of the race.
The boat won't move; it will have traveled 0 millimeters by the end of the race.
- Hold the button for 1 millisecond at the start of the race. Then, the boat will travel
at a speed of 1 millimeter per millisecond for 6 milliseconds, reaching a total distance traveled of 6 millimeters.
- Hold the button for 2 milliseconds, giving the boat a speed of 2 millimeters per millisecond.
It will then get 5 milliseconds to move, reaching a total distance of 10 millimeters.
- Hold the button for 3 milliseconds. After its remaining 4 milliseconds of travel time,
the boat will have gone 12 millimeters.
- Hold the button for 4 milliseconds. After its remaining 3 milliseconds of travel time,
the boat will have gone 12 millimeters.
- Hold the button for 5 milliseconds, causing the boat to travel a total of 10 millimeters.
- Hold the button for 6 milliseconds, causing the boat to travel a total of 6 millimeters.
- Hold the button for 7 milliseconds. That's the entire duration of the race. You never let go of the button.
The boat can't move until you let go of the button. Please make sure you let go of the button so
the boat gets to move. 0 millimeters.
Since the current record for this race is 9 millimeters, there are actually 4 different ways you could win:
you could hold the button for 2, 3, 4, or 5 milliseconds at the start of the race.
In the second race, you could hold the button for at least 4 milliseconds and at most 11 milliseconds and
beat the record, a total of 8 different ways to win.
In the third race, you could hold the button for at least 11 milliseconds and no more than 19 milliseconds and
still beat the record, a total of 9 ways you could win.
To see how much margin of error you have, determine the number of ways you can beat the record in each race;
in this example, if you multiply these values together, you get 288 (4 * 8 * 9).
Determine the number of ways you could beat the record in each race.
What do you get if you multiply these numbers together?
## Part Two
As the race is about to start, you realize the piece of paper with race times and record distances you got earlier actually just has very bad kerning. There's really only one race - ignore the spaces between the numbers on each line.
So, the example from before:
```
Time: 7 15 30
Distance: 9 40 200
```
...now instead means this:
```
Time: 71530
Distance: 940200
```
Now, you have to figure out how many ways there are to win this single race.
In this example, the race lasts for 71530 milliseconds and the record distance you need to beat is 940200 millimeters.
You could hold the button anywhere from 14 to 71516 milliseconds and beat the record, a total of 71503 ways!
How many ways can you beat the record in this one much longer race?

View file

@ -1,10 +1,9 @@
plugins {
kotlin("jvm") version "1.9.0"
application
kotlin("jvm") version "2.0.21"
}
group = "space.comfycamp"
version = "1.0-SNAPSHOT"
version = "1.0"
repositories {
mavenCentral()
@ -17,11 +16,3 @@ dependencies {
tasks.test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(8)
}
application {
mainClass.set("MainKt")
}

View file

@ -0,0 +1,2 @@
rootProject.name = "day_06"

View file

@ -0,0 +1,12 @@
package space.comfycamp
fun main() {
val resource = object{}.javaClass.getResource("/input.txt")!!
val lines = resource.readText().trim().lines()
val res1 = multiplyWaysToBeat(lines)
println("Part 1: $res1")
val res2 = countWaysForOneRace(lines)
println("Part 2: $res2")
}

View file

@ -0,0 +1,61 @@
package space.comfycamp
import kotlin.math.ceil
import kotlin.math.sqrt
private fun parseRaces(lines: List<String>): MutableList<Race> {
val races = mutableListOf<Race>()
val times = lines[0]
.replace("Time:", "")
.split(" ")
.filter{it.isNotEmpty()}
val distances = lines[1]
.replace("Distance:", "")
.split(" ")
.filter{it.isNotEmpty()}
for ((time, distance) in times.zip(distances)) {
races.add(
Race(distance.toLong(), time.toLong())
)
}
return races
}
fun multiplyWaysToBeat(lines: List<String>): Int {
val races = parseRaces(lines)
var res = 1
for (race in races) {
var x1 = (race.duration - sqrt(race.duration * race.duration - 4.0 * race.distance)) / 2.0
val x2 = (race.duration + sqrt(race.duration * race.duration - 4.0 * race.distance)) / 2.0
if (ceil(x1) - x1 < 1e-4) {
x1 += 0.5
}
res *= (ceil(x2) - ceil(x1)).toInt()
}
return res
}
fun countWaysForOneRace(input: List<String>): Int {
val races = parseRaces(input)
val race = Race(
races.map { it.distance.toString() }.reduce { acc, s -> acc + s }.toLong(),
races.map { it.duration.toString() }.reduce { acc, s -> acc + s }.toLong(),
)
var x1 = (race.duration - sqrt(race.duration * race.duration - 4.0 * race.distance)) / 2.0
val x2 = (race.duration + sqrt(race.duration * race.duration - 4.0 * race.distance)) / 2.0
if (ceil(x1) - x1 < 1e-4) {
x1 += 0.5
}
return (ceil(x2) - ceil(x1)).toInt()
}
data class Race(val distance: Long, val duration: Long)

View file

@ -0,0 +1,18 @@
import space.comfycamp.countWaysForOneRace
import space.comfycamp.multiplyWaysToBeat
import kotlin.test.assertEquals
import kotlin.test.Test
class RaceTest {
val lines = object{}.javaClass.getResource("/input.txt")!!.readText().trim().lines()
@Test
fun testMultiplyWaysToBeat() {
assertEquals(288, multiplyWaysToBeat(lines))
}
@Test
fun testCountWaysForOneRace() {
assertEquals(71503, countWaysForOneRace(lines))
}
}

View file

@ -1 +0,0 @@
kotlin.code.style=official

View file

@ -1,5 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View file

@ -1,234 +0,0 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View file

@ -1,89 +0,0 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View file

@ -1,12 +0,0 @@
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0"
}
rootProject.name = "advent-of-code-2023"

View file

@ -1,5 +0,0 @@
class ResourceReader {
fun readFile(filename: String): List<String> {
return this::class.java.getResourceAsStream(filename).bufferedReader().readLines();
}
}

View file

@ -1,66 +0,0 @@
import kotlin.math.ceil
import kotlin.math.sqrt
class Solution06(lines: List<String>) {
private val races = mutableListOf<Race>()
init {
val times = lines[0]
.replace("Time:", "")
.split(" ")
.filter{it.isNotEmpty()}
val records = lines[1]
.replace("Distance:", "")
.split(" ")
.filter{it.isNotEmpty()}
for (i in times.indices) {
races.add(
Race(records[i].toLong(), times[i].toLong())
)
}
}
fun getMultipliedNumbersOfWays(): Int {
var res = 1
println("races: $races")
for (race in races) {
var x1 = (race.duration - sqrt(race.duration * race.duration - 4.0 * race.distance)) / 2.0
val x2 = (race.duration + sqrt(race.duration * race.duration - 4.0 * race.distance)) / 2.0
if (ceil(x1) - x1 < 1e-4) {
x1 += 0.5
}
println("x1 = $x1, x2 = $x2")
res *= (ceil(x2) - ceil(x1)).toInt()
}
return res
}
fun getNumberOfWaysForOneRace(): Int {
val race = Race(
races.map{it.distance.toString()}.reduce{acc, s -> acc + s}.toLong(),
races.map{it.duration.toString()}.reduce{acc, s -> acc + s}.toLong(),
)
println("distance: ${race.distance}, duration: ${race.duration}")
var x1 = (race.duration - sqrt(race.duration * race.duration - 4.0 * race.distance)) / 2.0
val x2 = (race.duration + sqrt(race.duration * race.duration - 4.0 * race.distance)) / 2.0
if (ceil(x1) - x1 < 1e-4) {
x1 += 0.5
}
println("x1 = $x1, x2 = $x2")
return (ceil(x2) - ceil(x1)).toInt()
}
}
data class Race(val distance: Long, val duration: Long)

View file

@ -1,33 +0,0 @@
import kotlin.test.Test
import kotlin.test.assertEquals
class Solution06Test{
@Test
fun testGetMultipliedNumbersOfWays() {
val text = ResourceReader().readFile("day-06/test.txt")
val res = Solution06(text).getMultipliedNumbersOfWays()
assertEquals(288, res)
}
@Test
fun solvePart1() {
val text = ResourceReader().readFile("day-06/input.txt")
val res = Solution06(text).getMultipliedNumbersOfWays()
println(res)
}
@Test
fun testGetNumberOfWaysForOneRace() {
val text = ResourceReader().readFile("day-06/test.txt")
val res = Solution06(text).getNumberOfWaysForOneRace()
assertEquals(71503, res)
}
@Test
fun solvePart2() {
val text = ResourceReader().readFile("day-06/input.txt")
val res = Solution06(text).getNumberOfWaysForOneRace()
println(res)
}
}