├── .gitignore ├── settings.gradle.kts ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src ├── Utils.kt ├── Day01.kt ├── Day02.kt ├── Day06.kt ├── Day07.kt ├── Day03.kt ├── Day10.kt ├── Day09.kt ├── Day14.kt ├── Day13.kt ├── Day12.kt ├── Day15.kt ├── Day11.kt ├── Day04.kt ├── Day05.kt └── Day08.kt ├── README.md ├── gradlew.bat └── gradlew /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | build 4 | src/**/*.txt 5 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "advent-of-code-21" 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kriticalflare/advent-of-code-21/main/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /src/Utils.kt: -------------------------------------------------------------------------------- 1 | import java.io.File 2 | import java.math.BigInteger 3 | import java.security.MessageDigest 4 | 5 | /** 6 | * Reads lines from the given input txt file. 7 | */ 8 | fun readInput(name: String) = File("src/input", "$name.txt").readLines() 9 | 10 | /** 11 | * Read Integers 12 | */ 13 | fun readInt(name: String) = File("src/input", "$name.txt").readLines().map { it.toInt() } 14 | 15 | /** 16 | * Converts string to md5 hash. 17 | */ 18 | fun String.md5(): String = BigInteger(1, MessageDigest.getInstance("MD5").digest(toByteArray())).toString(16) 19 | -------------------------------------------------------------------------------- /src/Day01.kt: -------------------------------------------------------------------------------- 1 | fun main() { 2 | 3 | fun part1(input: List): Int { 4 | var increase = 0 5 | for (curr in input.indices) { 6 | if (curr != 0) { 7 | val prev = curr - 1 8 | if (input[curr] > input[prev]) { 9 | increase++ 10 | } 11 | } 12 | } 13 | return increase 14 | } 15 | 16 | fun part2(input: List): Int { 17 | var window = 0 18 | var increases = 0 19 | for (i in 0..2) { 20 | window += input[i] 21 | } 22 | for (i in 3 until input.size) { 23 | val prev = window 24 | val curr = window - input[i-3] + input[i] 25 | if(curr > prev){ 26 | increases++ 27 | } 28 | } 29 | return increases 30 | } 31 | 32 | val input = readInt("Day01") 33 | println(part1(input)) 34 | println(part2(input)) 35 | } 36 | -------------------------------------------------------------------------------- /src/Day02.kt: -------------------------------------------------------------------------------- 1 | fun main() { 2 | 3 | fun part1(input: List): Int { 4 | var position = 0 5 | var depth = 0 6 | for (curr in input) { 7 | val (command, magnitude) = curr.split(" ") 8 | when(command){ 9 | "forward" -> position += magnitude.toInt() 10 | "up" -> depth -= magnitude.toInt() 11 | "down" -> depth += magnitude.toInt() 12 | } 13 | } 14 | return position * depth 15 | } 16 | 17 | fun part2(input: List): Int { 18 | var position = 0 19 | var depth = 0 20 | var aim = 0 21 | for (curr in input) { 22 | val (command, magnitude) = curr.split(" ") 23 | when(command){ 24 | "forward" -> { 25 | position += magnitude.toInt() 26 | depth += (aim * magnitude.toInt()) 27 | } 28 | "up" -> aim -= magnitude.toInt() 29 | "down" -> aim += magnitude.toInt() 30 | } 31 | } 32 | return position * depth 33 | } 34 | 35 | val input = readInput("Day02") 36 | println(part1(input)) 37 | println(part2(input)) 38 | } 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # advent-of-code-21 2 | 3 | Welcome to the Advent of Code[^aoc] Kotlin project created by [kriticalflare][github] using the [Advent of Code Kotlin Template][template] delivered by JetBrains. 4 | 5 | In this repository, kriticalflare is about to provide solutions for the puzzles using [Kotlin][kotlin] language. 6 | 7 | If you're stuck with Kotlin-specific questions or anything related to this template, check out the following resources: 8 | 9 | - [Kotlin docs][docs] 10 | - [Kotlin Slack][slack] 11 | - Template [issue tracker][issues] 12 | 13 | 14 | [^aoc]: 15 | [Advent of Code][aoc] – An annual event of Christmas-oriented programming challenges started December 2015. 16 | Every year since then, beginning on the first day of December, a programming puzzle is published every day for twenty-four days. 17 | You can solve the puzzle and provide an answer using the language of your choice. 18 | 19 | [aoc]: https://adventofcode.com 20 | [docs]: https://kotlinlang.org/docs/home.html 21 | [github]: https://github.com/kriticalflare 22 | [issues]: https://github.com/kotlin-hands-on/advent-of-code-kotlin-template/issues 23 | [kotlin]: https://kotlinlang.org 24 | [slack]: https://surveys.jetbrains.com/s3/kotlin-slack-sign-up 25 | [template]: https://github.com/kotlin-hands-on/advent-of-code-kotlin-template 26 | -------------------------------------------------------------------------------- /src/Day06.kt: -------------------------------------------------------------------------------- 1 | fun main() { 2 | 3 | 4 | fun cleanInput(input: List): List { 5 | return input.flatMap { it.split(",") }.map { it.toInt() } 6 | } 7 | 8 | fun part1(input: List, days: Int): Int { 9 | val fishes = cleanInput(input).toMutableList() 10 | for (currDay in 0 until days) { 11 | var newAdd = 0 12 | for ((idx, fish) in fishes.withIndex()) { 13 | if (fish != 0) { 14 | fishes[idx]-- 15 | } else { 16 | fishes[idx] = 6 17 | newAdd++ 18 | } 19 | } 20 | for (i in 0 until newAdd) { 21 | fishes.add(8) 22 | } 23 | } 24 | return fishes.size 25 | } 26 | 27 | fun part2(input: List, days: Int): Long { 28 | val fishes = cleanInput(input).toMutableList() 29 | var size = 0L 30 | val noOfFishAtTimerStage = LongArray(9) 31 | fishes.forEach { 32 | noOfFishAtTimerStage[it]++ 33 | } 34 | for (currDay in 0 until days) { 35 | // rotate array left 36 | val day0 = noOfFishAtTimerStage[0] 37 | for (i in 1 until 9) { 38 | noOfFishAtTimerStage[i - 1] = noOfFishAtTimerStage[i] 39 | } 40 | // add newborns from day 0 to day 8 and their parents to day 6 41 | noOfFishAtTimerStage[6] += day0 42 | noOfFishAtTimerStage[8] = day0 43 | } 44 | for (i in 0..8) { 45 | size += noOfFishAtTimerStage[i] 46 | } 47 | return size 48 | } 49 | 50 | val input = readInput( 51 | "Day06" 52 | ) 53 | val testInput = readInput("Day06test") 54 | check(part1(testInput, 18) == 26) 55 | check(part1(testInput, 80) == 5934) 56 | check(part2(testInput, 18) == 26L) 57 | check(part2(testInput, 80) == 5934L) 58 | check(part2(testInput, 256) == 26984457539L) 59 | println(part1(input, 80)) 60 | println(part2(input, 256)) 61 | 62 | } -------------------------------------------------------------------------------- /src/Day07.kt: -------------------------------------------------------------------------------- 1 | import kotlin.math.abs 2 | import kotlin.math.ceil 3 | import kotlin.math.floor 4 | 5 | fun main() { 6 | 7 | fun cleanInput(input: List): List { 8 | return input.toMutableList() 9 | .flatMap { it.split(",") } 10 | .map { it.toInt() } 11 | .sorted() 12 | } 13 | 14 | 15 | fun cleanInputDouble(input: List): List { 16 | return input.toMutableList() 17 | .flatMap { it.split(",") } 18 | .map { it.toDouble() } 19 | } 20 | 21 | fun part1(input: List): Int { 22 | val positions = cleanInput(input) 23 | var median: Int? 24 | if (positions.size % 2 == 0) { 25 | val p = (positions.size / 2) - 1 26 | median = (positions[p] + positions[p + 1]) / 2 27 | 28 | } else { 29 | val p = ((positions.size + 1) / 2) - 1 30 | median = positions[p] 31 | } 32 | var fuelUsed = 0 33 | positions.forEach { 34 | fuelUsed += (abs(median - it)) 35 | } 36 | return fuelUsed 37 | } 38 | 39 | fun part2(input: List): Long { 40 | val positions = cleanInputDouble(input) 41 | var sum = 0.0 42 | positions.forEach { 43 | sum += it 44 | } 45 | var mean = sum / positions.size 46 | val possibleValues = intArrayOf(floor(mean).toInt(), ceil(mean).toInt()) 47 | var minDiff = Long.MAX_VALUE 48 | for (target in possibleValues) { 49 | var diff = 0L 50 | for (pos in positions) { 51 | val dist = abs(target - pos.toInt()) 52 | val fuelUsed = (dist * (dist + 1)) / 2 53 | diff += fuelUsed 54 | } 55 | if (minDiff > diff) { 56 | minDiff = diff 57 | } 58 | } 59 | return minDiff 60 | } 61 | 62 | val input = readInput("Day07") 63 | val testInput = readInput("Day07test") 64 | check(part1(testInput) == 37) 65 | check(part2(testInput) == 168L) 66 | println(part1(input)) 67 | println(part2(input)) 68 | 69 | } -------------------------------------------------------------------------------- /src/Day03.kt: -------------------------------------------------------------------------------- 1 | import kotlin.text.StringBuilder 2 | 3 | fun main() { 4 | 5 | fun part1(input: List): Long { 6 | var gamma = 0L 7 | var epsilon = 0L 8 | val limit = input.first().length 9 | val sb = StringBuilder() 10 | for (i in limit - 1 downTo 0) { 11 | var zeros = 0 12 | var ones = 0 13 | var mask = 1L.shl(i) 14 | for (curr in input) { 15 | if (curr.toLong(2).and(mask) == 0L) { 16 | zeros++ 17 | } else { 18 | ones++ 19 | } 20 | } 21 | if (zeros > ones) { 22 | sb.append("0") 23 | } else { 24 | sb.append("1") 25 | } 26 | } 27 | gamma = sb.toString().toLong(2) 28 | var mask = 1L.shl(limit) - 1 29 | epsilon = gamma.inv().and(mask) 30 | return gamma * epsilon 31 | } 32 | 33 | 34 | fun part2(input: List): Long { 35 | val o2 = o2Gen(input.toMutableList()) 36 | val co2 = co2Scrub(input.toMutableList()) 37 | return o2 * co2 38 | } 39 | 40 | val testInput = readInput("Day03test") 41 | check(part1(testInput) == 198L) 42 | check(part2(testInput) == 230L) 43 | 44 | val input = readInput("Day03") 45 | println(part1(input)) 46 | println(part2(input)) 47 | } 48 | 49 | private fun o2Gen(input: MutableList): Long { 50 | val limit = input.first().length 51 | for (i in limit - 1 downTo 0) { 52 | var zeros = 0 53 | var ones = 0 54 | var mask = 1L.shl(i) 55 | for (curr in input) { 56 | if (curr.toLong(2).and(mask) == 0L) { 57 | zeros++ 58 | } else { 59 | ones++ 60 | } 61 | } 62 | if (zeros > ones) { 63 | input.removeIf { it.toLong(2).and(mask) != 0L } 64 | } else { 65 | input.removeIf { it.toLong(2).and(mask) == 0L } 66 | } 67 | if (input.size == 1) { 68 | break 69 | } 70 | } 71 | 72 | return input.first().toLong(2); 73 | } 74 | 75 | private fun co2Scrub(input: MutableList): Long { 76 | val limit = input.first().length 77 | for (i in limit - 1 downTo 0) { 78 | var zeros = 0 79 | var ones = 0 80 | var mask = 1L.shl(i) 81 | for (curr in input) { 82 | if (curr.toLong(2).and(mask) == 0L) { 83 | zeros++ 84 | } else { 85 | ones++ 86 | } 87 | } 88 | if (zeros > ones) { 89 | input.removeIf { it.toLong(2).and(mask) == 0L } 90 | } else { 91 | input.removeIf { it.toLong(2).and(mask) != 0L } 92 | } 93 | if (input.size == 1 || (input.size == 2 && input.first() == input[1])) { 94 | break 95 | } 96 | } 97 | return input.first().toLong(2); 98 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /src/Day10.kt: -------------------------------------------------------------------------------- 1 | import java.util.* 2 | 3 | fun main() { 4 | 5 | fun legalPair(lhs: Char, rhs: Char): Boolean { 6 | return when (lhs) { 7 | '[', ']' -> rhs == ']' || rhs == '[' 8 | '<', '>' -> rhs == '>' || rhs == '<' 9 | '{', '}' -> rhs == '}' || rhs == '{' 10 | '(', ')' -> rhs == ')' || rhs == '(' 11 | else -> { 12 | throw Exception("Invalid LHS") 13 | } 14 | } 15 | } 16 | 17 | fun syntaxScore(token: Char): Int { 18 | return when (token) { 19 | '(', ')' -> 3 20 | '[', ']' -> 57 21 | '{', '}' -> 1197 22 | '<', '>' -> 25137 23 | else -> { 24 | throw Exception("Invalid Token") 25 | } 26 | } 27 | } 28 | 29 | fun part1(input: List): Int { 30 | var sum = 0 31 | for (line in input) { 32 | val stack = Stack() 33 | for (char in line) { 34 | when (char) { 35 | '[', '{', '(', '<' -> { 36 | stack.push(char) 37 | } 38 | ']', '}', ')', '>' -> { 39 | val popped = stack.pop() 40 | if (!legalPair(lhs = char, rhs = popped)) { 41 | // corrupted line 42 | sum += syntaxScore(char) 43 | } 44 | } 45 | } 46 | } 47 | } 48 | return sum 49 | } 50 | 51 | fun autoCompleteScore(token: Char): Int { 52 | return when (token) { 53 | '(' -> 1 54 | '[' -> 2 55 | '{' -> 3 56 | '<' -> 4 57 | else -> throw Exception("Invalid Token") 58 | } 59 | } 60 | 61 | fun part2(input: List): Long { 62 | var sumList = mutableListOf() 63 | for (line in input) { 64 | var isCorrupt = false 65 | val stack = Stack() 66 | for (char in line) { 67 | when (char) { 68 | '[', '{', '(', '<' -> { 69 | stack.push(char) 70 | } 71 | ']', '}', ')', '>' -> { 72 | val popped = stack.pop() 73 | if (!legalPair(lhs = char, rhs = popped)) { 74 | // corrupted line 75 | isCorrupt = true 76 | stack.clear() 77 | break 78 | } 79 | } 80 | } 81 | } 82 | if (!isCorrupt) { 83 | val sb = StringBuilder() 84 | var sum = 0L 85 | while (stack.size > 0) { 86 | sb.append(stack.pop()) 87 | } 88 | for (char in sb.toString()) { 89 | sum *= 5 90 | sum += autoCompleteScore(char) 91 | } 92 | sumList.add(sum) 93 | } 94 | } 95 | val midPoint = sumList.size / 2 96 | sumList.sort() 97 | return sumList[midPoint] 98 | } 99 | 100 | val input = readInput( 101 | "Day10" 102 | ) 103 | val testInput = readInput("Day10test") 104 | check(part1(testInput) == 26397) 105 | println(part1(input)) 106 | check(part2(testInput) == 288957L) 107 | println(part2(input)) 108 | } -------------------------------------------------------------------------------- /src/Day09.kt: -------------------------------------------------------------------------------- 1 | import java.util.* 2 | 3 | fun main() { 4 | data class Point(val x: Int, val y: Int, val value: Int) 5 | 6 | fun constructGrid(input: List): Array { 7 | val grid = Array(input.size) { IntArray(input[0].length) } 8 | for ((i, r) in input.withIndex()) { 9 | for (j in r.indices) { 10 | grid[i][j] = r[j].digitToInt() 11 | } 12 | } 13 | for ((i, r) in input.withIndex()) { 14 | for (j in r.indices) { 15 | grid[i][j] = r[j].digitToInt() 16 | } 17 | } 18 | return grid 19 | } 20 | 21 | fun getLowPoints(grid: Array): List { 22 | // changes in (X,Y) in clockwise direction ( ie to go up, right, down, left) 23 | val dirX = intArrayOf(-1, 0, 1, 0) 24 | val dirY = intArrayOf(0, 1, 0, -1) 25 | 26 | val rows = grid.size 27 | val columns = grid[0].size 28 | 29 | val lowPoints = mutableListOf() 30 | for (i in 0 until rows) { 31 | for (j in 0 until columns) { 32 | val point = grid[i][j] 33 | var isLowPoint = true 34 | for (d in 0..3) { 35 | val nX = i + dirX[d] 36 | val nY = j + dirY[d] 37 | val legal = nX in 0 until rows && nY in 0 until columns 38 | if (legal && point >= grid[nX][nY]) { 39 | isLowPoint = false 40 | break 41 | } 42 | } 43 | if (isLowPoint) { 44 | lowPoints.add(Point(x = i, y = j, value = point)) 45 | } 46 | } 47 | } 48 | return lowPoints 49 | } 50 | 51 | fun part1(input: List): Int { 52 | val grid = constructGrid(input) 53 | val lowPoints = getLowPoints(grid) 54 | return lowPoints.sumOf { it.value } + lowPoints.size 55 | } 56 | 57 | fun part2(input: List): Int { 58 | val grid = constructGrid(input) 59 | val rows = grid.size 60 | val columns = grid[0].size 61 | val visitedGrid = Array(rows) { BooleanArray(columns) { false } } 62 | 63 | val lowPoints = getLowPoints(grid) 64 | val basinSizes = mutableListOf() 65 | for (lowPoint in lowPoints) { 66 | val queue: Queue = LinkedList() 67 | queue.add(lowPoint) 68 | visitedGrid[lowPoint.x][lowPoint.y] = true 69 | var basinSize = 0 70 | while (queue.size != 0) { 71 | val p = queue.poll() 72 | basinSize++ 73 | val dirX = intArrayOf(-1, 0, 1, 0) 74 | val dirY = intArrayOf(0, 1, 0, -1) 75 | for (d in 0..3) { 76 | val nX = p.x + dirX[d] 77 | val nY = p.y + dirY[d] 78 | val legal = nX in 0 until rows && nY in 0 until columns 79 | if (legal && !visitedGrid[nX][nY] && grid[nX][nY] < 9) { 80 | queue.add(Point(x = nX, y = nY, value = grid[nX][nY])) 81 | visitedGrid[nX][nY] = true 82 | } 83 | } 84 | } 85 | basinSizes.add(basinSize) 86 | } 87 | basinSizes.sortDescending() 88 | return basinSizes[0] * basinSizes[1] * basinSizes[2] 89 | } 90 | 91 | val input = readInput( 92 | "Day09" 93 | ) 94 | 95 | 96 | val testInput = readInput("Day09test") 97 | check(part1(testInput) == 15) 98 | println(part1(input)) 99 | check(part2(testInput) == 1134) 100 | println(part2(input)) 101 | } 102 | 103 | -------------------------------------------------------------------------------- /src/Day14.kt: -------------------------------------------------------------------------------- 1 | import java.util.* 2 | import kotlin.collections.HashMap 3 | 4 | fun main() { 5 | 6 | fun part1(input: List, steps: Int): Long { 7 | var template = input[0] 8 | var rules = input.filter { it.contains("->") } 9 | val sb = StringBuilder(template) 10 | val ruleMap = HashMap() 11 | for (rule in rules) { 12 | var (pattern, out) = rule.split(" -> ") 13 | ruleMap[pattern] = out 14 | } 15 | for (step in 0 until steps) { 16 | val prev = sb.toString() 17 | var prevCounter = 0 18 | var sbCounter = 0 19 | while (prevCounter < prev.length - 1) { 20 | val slice = prev.substring(prevCounter, prevCounter + 2) 21 | if (ruleMap.containsKey(slice)) { 22 | sb.insert(++sbCounter, ruleMap[slice]) 23 | } 24 | sbCounter++ 25 | prevCounter++ 26 | } 27 | } 28 | val out = sb.toString() 29 | val alphabets = HashMap() 30 | for (char in out) { 31 | alphabets[char] = alphabets.getOrDefault(char, 0) + 1 32 | } 33 | var max = Long.MIN_VALUE 34 | var min = Long.MAX_VALUE 35 | for ((_, value) in alphabets) { 36 | if (value > max) { 37 | max = value 38 | } 39 | if (value < min) { 40 | min = value 41 | } 42 | } 43 | return max - min 44 | } 45 | 46 | fun part2(input: List, steps: Int): Long { 47 | var template = input[0] 48 | val start = template.first() 49 | val end = template.last() 50 | var rules = input.filter { it.contains("->") } 51 | val ruleMap = HashMap>() 52 | for (rule in rules) { 53 | var (pattern, out) = rule.split(" -> ") 54 | ruleMap[pattern] = Pair(pattern[0] + out, out + pattern[1]) 55 | } 56 | val charCount = HashMap() 57 | val duoMap = HashMap() 58 | for (i in 0 until template.length - 1) { 59 | val duo = template.substring(i, i + 2) 60 | duoMap[duo] = duoMap.getOrDefault(duo, 0) + 1 61 | } 62 | 63 | for (step in 0 until steps) { 64 | val queue: Queue> = LinkedList() 65 | for (duo in duoMap) { 66 | if (duo.value > 0) { 67 | queue.add(Pair(duo.key, duo.value)) 68 | } 69 | } 70 | while (queue.isNotEmpty()) { 71 | val duo = queue.poll() 72 | duoMap[duo.first] = duoMap.getOrDefault(duo.first, 1) - duo.second 73 | val (out1, out2) = ruleMap[duo.first]!! 74 | duoMap[out1] = duoMap.getOrDefault(out1, 0) + duo.second 75 | duoMap[out2] = duoMap.getOrDefault(out2, 0) + duo.second 76 | } 77 | } 78 | for (duo in duoMap) { 79 | val firstChar = duo.key[0] 80 | val secondChar = duo.key[1] 81 | charCount[firstChar] = charCount.getOrDefault(firstChar, 0) + duo.value 82 | charCount[secondChar] = charCount.getOrDefault(secondChar, 0) + duo.value 83 | } 84 | charCount.forEach { 85 | if (it.key == start || it.key == end) { 86 | charCount[it.key] = ((it.value - 1) / 2) + 1 87 | } else { 88 | charCount[it.key] = it.value / 2 89 | } 90 | } 91 | var maxChar = Long.MIN_VALUE 92 | var minChar = Long.MAX_VALUE 93 | for ((_, value) in charCount) { 94 | if (value > maxChar) { 95 | maxChar = value 96 | } 97 | if (value < minChar) { 98 | minChar = value 99 | } 100 | } 101 | return maxChar - minChar 102 | } 103 | 104 | val input = readInput( 105 | "Day14" 106 | ) 107 | 108 | 109 | val testInput = readInput("Day14test") 110 | check(part1(testInput, 10) == 1588L) 111 | println(part1(input, 10)) 112 | check(part2(testInput, 10) == 1588L) 113 | check(part2(testInput, 40) == 2188189693529) 114 | println(part2(input, 40)) 115 | } 116 | 117 | -------------------------------------------------------------------------------- /src/Day13.kt: -------------------------------------------------------------------------------- 1 | fun main() { 2 | 3 | fun constructGrid(input: List): Array { 4 | val coords = input.filter { it.contains(",") } 5 | val maxR = coords.map { it.split(",")[1].toInt() }.maxOf { it } 6 | val maxC = coords.map { it.split(",")[0].toInt() }.maxOf { it } 7 | val grid = Array(maxR + 1) { 8 | BooleanArray(maxC + 1) { 9 | false 10 | } 11 | } 12 | coords.forEach { 13 | val (i, j) = it.split(",") 14 | grid[j.toInt()][i.toInt()] = true 15 | } 16 | return grid 17 | } 18 | 19 | fun part1(input: List): Int { 20 | var (axis, value) = input.first { it.contains("fold") }.split("=") 21 | val grid = constructGrid(input) 22 | var rows = grid.size - 1 23 | var columns = grid[0].size - 1 24 | axis = axis.split(" ")[2] 25 | if (axis == "y") { 26 | // horizontal divider 27 | for (i in rows downTo value.toInt()) { 28 | for (j in 0..columns) { 29 | if (grid[i][j]) { 30 | grid[i][j] = false 31 | val mirX = value.toInt() + (value.toInt() - i) 32 | val mirY = j 33 | grid[mirX][mirY] = true 34 | } 35 | } 36 | } 37 | } else { 38 | // vertical divider 39 | for (i in 0..rows) { 40 | for (j in columns downTo value.toInt()) { 41 | if (grid[i][j]) { 42 | grid[i][j] = false 43 | val mirX = i 44 | val mirY = value.toInt() + (value.toInt() - j) 45 | grid[mirX][mirY] = true 46 | } 47 | } 48 | } 49 | } 50 | 51 | var visible = 0 52 | for (i in 0..rows) { 53 | for (j in 0..columns) { 54 | if (grid[i][j]) { 55 | visible++ 56 | } 57 | } 58 | } 59 | return visible 60 | } 61 | 62 | fun part2(input: List): Int { 63 | var visible = 0 64 | val folds = input.filter { it.contains("fold") } 65 | val grid = constructGrid(input) 66 | var rows = grid.size - 1 67 | var columns = grid[0].size - 1 68 | 69 | for (fold in folds) { 70 | var (axis, value) = fold.split("=") 71 | axis = axis.split(" ")[2] 72 | if (axis == "y") { 73 | // horizontal divider 74 | for (i in rows downTo value.toInt()) { 75 | for (j in 0..columns) { 76 | if (grid[i][j]) { 77 | grid[i][j] = false 78 | val mirX = value.toInt() + (value.toInt() - i) 79 | val mirY = j 80 | grid[mirX][mirY] = true 81 | } 82 | } 83 | } 84 | rows = value.toInt() - 1 85 | } else { 86 | // vertical divider 87 | for (i in 0..rows) { 88 | for (j in columns downTo value.toInt()) { 89 | if (grid[i][j]) { 90 | grid[i][j] = false 91 | val mirX = i 92 | val mirY = value.toInt() + (value.toInt() - j) 93 | grid[mirX][mirY] = true 94 | } 95 | } 96 | } 97 | columns = value.toInt() - 1 98 | } 99 | } 100 | for (i in 0..rows) { 101 | for (j in 0..columns) { 102 | if (grid[i][j]) { 103 | print("#") 104 | visible++ 105 | } else { 106 | print(" ") 107 | } 108 | } 109 | println() 110 | } 111 | return visible 112 | } 113 | 114 | val input = readInput( 115 | "Day13" 116 | ) 117 | 118 | 119 | val testInput = readInput("Day13test") 120 | check(part1(testInput) == 17) 121 | println(part1(input)) 122 | check(part2(testInput) == 16) 123 | println(part2(input)) 124 | } 125 | 126 | -------------------------------------------------------------------------------- /src/Day12.kt: -------------------------------------------------------------------------------- 1 | import kotlin.collections.ArrayList 2 | import kotlin.collections.HashMap 3 | 4 | fun main() { 5 | 6 | 7 | data class Node(val position: Int, val label: String) 8 | 9 | fun dfsPart1( 10 | nodes: HashMap, visitedCount: IntArray, graph: ArrayList>, root: String 11 | ): Int { 12 | var paths = 0 13 | if (root == "end") { 14 | return 1 15 | } 16 | visitedCount[nodes[root]!!]++ 17 | for (adj in graph[nodes[root]!!]) { 18 | if (adj.label[0].isLowerCase() && visitedCount[nodes[adj.label]!!] < 1) { 19 | paths += dfsPart1(nodes, visitedCount.clone(), graph, adj.label) 20 | } else if (adj.label[0].isUpperCase()) { 21 | paths += dfsPart1(nodes, visitedCount.clone(), graph, adj.label) 22 | } 23 | } 24 | return paths 25 | } 26 | 27 | fun constructNodes(input: List): HashMap { 28 | var nodeCount = 0 29 | val nodes = HashMap() 30 | input.flatMap { 31 | it.split("-") 32 | }.forEach { 33 | if (!nodes.containsKey(it)) { 34 | nodes[it] = nodeCount 35 | nodeCount++ 36 | } 37 | } 38 | return nodes 39 | } 40 | 41 | fun constructGraph(input: List, nodes: HashMap): ArrayList> { 42 | val graph = ArrayList>() 43 | val nodeCount = nodes.size 44 | for (i in 0..nodeCount) { 45 | graph.add(ArrayList()) 46 | } 47 | input.forEach { edge -> 48 | val (src, dst) = edge.split("-") 49 | graph[nodes[src]!!].add(Node(position = nodes[dst]!!, label = dst)) 50 | graph[nodes[dst]!!].add(Node(position = nodes[src]!!, label = src)) 51 | } 52 | return graph 53 | } 54 | 55 | fun part1(input: List): Int { 56 | 57 | val nodes = constructNodes(input) 58 | val nodeCount = nodes.size 59 | val graph = constructGraph(input, nodes) 60 | val visitedCount = IntArray(nodeCount + 1) { 0 } 61 | 62 | return dfsPart1(nodes, visitedCount, graph, "start") 63 | } 64 | 65 | 66 | fun dfsPart2( 67 | nodes: HashMap, visitedCount: IntArray, graph: ArrayList>, root: String 68 | ): Int { 69 | var paths = 0 70 | if (root == "end") { 71 | return 1 72 | } 73 | visitedCount[nodes[root]!!]++ 74 | for (adj in graph[nodes[root]!!]) { 75 | if (adj.label == "start") { 76 | continue 77 | } 78 | if (adj.label[0].isLowerCase()) { 79 | // cases 80 | 81 | val lowerCaseVisitedTwice = nodes.any { 82 | it.key[0].isLowerCase() && visitedCount[it.value] > 1 83 | } 84 | // case1 no valid lowercase has visitedCount more than 1 85 | // case2 1 lowercase has visitedCount 2 86 | if (lowerCaseVisitedTwice) { 87 | if (visitedCount[adj.position] < 1) { 88 | paths += dfsPart2(nodes, visitedCount.clone(), graph, adj.label) 89 | } 90 | } else { 91 | if (visitedCount[adj.position] < 2) { 92 | paths += dfsPart2(nodes, visitedCount.clone(), graph, adj.label) 93 | } 94 | } 95 | } 96 | if (adj.label[0].isUpperCase()) { 97 | paths += dfsPart2(nodes, visitedCount.clone(), graph, adj.label) 98 | } 99 | } 100 | return paths 101 | } 102 | 103 | fun part2(input: List): Int { 104 | val nodes = constructNodes(input) 105 | val nodeCount = nodes.size 106 | val graph = constructGraph(input, nodes) 107 | val visitedCount = IntArray(nodeCount) { 0 } 108 | 109 | return dfsPart2(nodes, visitedCount, graph, "start") 110 | } 111 | 112 | val input = readInput( 113 | "Day12" 114 | ) 115 | 116 | 117 | val testInput1 = readInput("Day12test1") 118 | val testInput2 = readInput("Day12test2") 119 | val testInput3 = readInput("Day12test3") 120 | check(part1(testInput1) == 10) 121 | check(part1(testInput2) == 19) 122 | check(part1(testInput3) == 226) 123 | println(part1(input)) 124 | check(part2(testInput1) == 36) 125 | check(part2(testInput2) == 103) 126 | check(part2(testInput3) == 3509) 127 | println(part2(input)) 128 | } 129 | 130 | -------------------------------------------------------------------------------- /src/Day15.kt: -------------------------------------------------------------------------------- 1 | import java.util.* 2 | import kotlin.collections.HashSet 3 | import kotlin.math.abs 4 | import kotlin.math.min 5 | 6 | fun main() { 7 | 8 | data class Coord(val x: Int, val y: Int) 9 | 10 | val dirX = intArrayOf(-1, 0, 1, 0) 11 | val dirY = intArrayOf(0, 1, 0, -1) 12 | 13 | fun constructGrid(input: List): Array { 14 | val grid = Array(input.size) { IntArray(input[0].length) } 15 | for (i in input.indices) { 16 | for (j in input[0].indices) { 17 | grid[i][j] = input[i][j].digitToInt() 18 | } 19 | } 20 | return grid 21 | } 22 | 23 | fun explode(og: Array): Array { 24 | val times = 5 25 | val grid = Array(og.size * times) { IntArray(og[0].size * times) } 26 | val offsetY = og.size 27 | val offsetX = og[0].size 28 | 29 | for (i in grid.indices) { 30 | for (j in grid[0].indices) { 31 | if (i in og.indices && j in og[0].indices) { 32 | grid[i][j] = og[i][j] 33 | continue 34 | } 35 | if (j in og[0].indices) { 36 | grid[i][j] = grid[i - offsetY][j] + 1 37 | if (grid[i][j] > 9) { 38 | grid[i][j] = 1 39 | } 40 | continue 41 | } 42 | grid[i][j] = grid[i][j - offsetX] + 1 43 | if (grid[i][j] > 9) { 44 | grid[i][j] = 1 45 | } 46 | } 47 | } 48 | 49 | return grid 50 | } 51 | 52 | 53 | fun part1(input: List): Int { 54 | val grid = constructGrid(input) 55 | 56 | val dist = Array(grid.size) { IntArray(grid[0].size) { Int.MAX_VALUE } } 57 | dist[0][0] = 0 58 | 59 | val rows = grid.size 60 | val columns = grid[0].size 61 | 62 | val compareCoord: Comparator = compareBy { dist[it.x][it.y] } 63 | val pq = PriorityQueue(compareCoord) 64 | pq.add(Coord(0, 0)) 65 | 66 | val sptSet = HashSet() 67 | 68 | while (sptSet.size != rows * columns) { 69 | val u = pq.poll() 70 | sptSet.add(u) 71 | 72 | for (i in dirX.indices) { 73 | val nX = u.x + dirX[i] 74 | val nY = u.y + dirY[i] 75 | val legal = nX in 0 until rows && nY in 0 until columns 76 | if (legal && !sptSet.contains( 77 | Coord( 78 | nX, nY 79 | ) 80 | ) && dist[u.x][u.y] != Int.MAX_VALUE && dist[u.x][u.y] + grid[nX][nY] < dist[nX][nY] 81 | ) { 82 | dist[nX][nY] = dist[u.x][u.y] + grid[nX][nY] 83 | pq.add(Coord(nX, nY)) 84 | } 85 | } 86 | } 87 | return dist[rows - 1][columns - 1] 88 | } 89 | 90 | 91 | fun part2(input: List): Int { 92 | val grid = explode(constructGrid(input)) 93 | 94 | val dist = Array(grid.size) { IntArray(grid[0].size) { Int.MAX_VALUE } } 95 | dist[0][0] = 0 96 | 97 | val rows = grid.size 98 | val columns = grid[0].size 99 | 100 | val compareCoord: Comparator = compareBy { dist[it.x][it.y] } 101 | val pq = PriorityQueue(compareCoord) 102 | pq.add(Coord(0, 0)) 103 | 104 | val sptSet = HashSet() 105 | 106 | while (sptSet.size != rows * columns) { 107 | val u = pq.poll() 108 | sptSet.add(u) 109 | 110 | for (i in dirX.indices) { 111 | val nX = u.x + dirX[i] 112 | val nY = u.y + dirY[i] 113 | val legal = nX in 0 until rows && nY in 0 until columns 114 | if (legal && !sptSet.contains( 115 | Coord( 116 | nX, nY 117 | ) 118 | ) && dist[u.x][u.y] != Int.MAX_VALUE && dist[u.x][u.y] + grid[nX][nY] < dist[nX][nY] 119 | ) { 120 | dist[nX][nY] = dist[u.x][u.y] + grid[nX][nY] 121 | pq.add(Coord(nX, nY)) 122 | } 123 | } 124 | } 125 | return dist[rows - 1][columns - 1] 126 | } 127 | 128 | val input = readInput( 129 | "Day15" 130 | ) 131 | 132 | 133 | val testInput = readInput("Day15test") 134 | val testInput2 = readInput("Day15test2") 135 | check(part1(testInput) == 40) 136 | println(part1(input)) 137 | println(part1(testInput2)) 138 | check(part2(testInput) == 315) 139 | println(part2(input)) 140 | } 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /src/Day11.kt: -------------------------------------------------------------------------------- 1 | import java.util.* 2 | 3 | fun main() { 4 | // offsets for above, right, down, left, topRight, bottomRight, bottomLeft, topLeft of a cell 5 | val dirX = intArrayOf(-1, 0, 1, 0, -1, 1, 1, -1) 6 | val dirY = intArrayOf(0, 1, 0, -1, 1, 1, -1, -1) 7 | 8 | data class Cell(val x: Int, val y: Int, var value: Int) 9 | 10 | fun constructGrid(input: List): Array { 11 | val grid = Array(input.size) { IntArray(input[0].length) } 12 | for ((i, r) in input.withIndex()) { 13 | for (j in r.indices) { 14 | grid[i][j] = r[j].digitToInt() 15 | } 16 | } 17 | for ((i, r) in input.withIndex()) { 18 | for (j in r.indices) { 19 | grid[i][j] = r[j].digitToInt() 20 | } 21 | } 22 | return grid 23 | } 24 | 25 | fun part1(input: List, steps: Int): Int { 26 | val grid = constructGrid(input) 27 | val rows = grid.size 28 | val columns = grid[0].size 29 | var totalFlash = 0 30 | 31 | for (step in 0 until steps) { 32 | var flash = 0 33 | var flashed = Array(rows) { BooleanArray(columns) { false } } 34 | val flashQueue: Queue = LinkedList() 35 | 36 | // increment all energy levels by 1 37 | for (i in 0 until rows) { 38 | for (j in 0 until columns) { 39 | grid[i][j]++ 40 | if (grid[i][j] > 9) { 41 | flashed[i][j] = true 42 | flashQueue.add(Cell(x = i, y = j, value = grid[i][j])) 43 | } 44 | } 45 | } 46 | 47 | // pop flash 48 | while (flashQueue.size > 0) { 49 | val cell = flashQueue.poll() 50 | for (i in dirX.indices) { 51 | val nX = cell.x + dirX[i] 52 | val nY = cell.y + dirY[i] 53 | val legal = nX in 0 until rows && nY in 0 until columns 54 | if (legal) { 55 | grid[nX][nY]++ 56 | if (grid[nX][nY] > 9 && !flashed[nX][nY]) { 57 | flashQueue.add(Cell(nX, nY, grid[nX][nY])) 58 | flashed[nX][nY] = true 59 | } 60 | } 61 | } 62 | } 63 | 64 | // reset popped flashes to 0 65 | for (i in 0 until rows) { 66 | for (j in 0 until columns) { 67 | if (flashed[i][j]) { 68 | grid[i][j] = 0 69 | flash++ 70 | } 71 | } 72 | } 73 | 74 | totalFlash += flash 75 | } 76 | 77 | return totalFlash 78 | } 79 | 80 | fun part2(input: List): Int { 81 | val grid = constructGrid(input) 82 | val rows = grid.size 83 | val columns = grid[0].size 84 | var step = 1 85 | 86 | while (true) { 87 | var flash = 0 88 | var flashed = Array(rows) { BooleanArray(columns) { false } } 89 | val flashQueue: Queue = LinkedList() 90 | 91 | // increment all energy levels by 1 92 | for (i in 0 until rows) { 93 | for (j in 0 until columns) { 94 | grid[i][j]++ 95 | if (grid[i][j] > 9) { 96 | flashed[i][j] = true 97 | flashQueue.add(Cell(x = i, y = j, value = grid[i][j])) 98 | } 99 | } 100 | } 101 | 102 | // pop flash 103 | while (flashQueue.size > 0) { 104 | val cell = flashQueue.poll() 105 | for (i in dirX.indices) { 106 | val nX = cell.x + dirX[i] 107 | val nY = cell.y + dirY[i] 108 | val legal = nX in 0 until rows && nY in 0 until columns 109 | if (legal) { 110 | grid[nX][nY]++ 111 | if (grid[nX][nY] > 9 && !flashed[nX][nY]) { 112 | flashQueue.add(Cell(nX, nY, grid[nX][nY])) 113 | flashed[nX][nY] = true 114 | } 115 | } 116 | } 117 | } 118 | 119 | // reset popped flashes to 0 120 | for (i in 0 until rows) { 121 | for (j in 0 until columns) { 122 | if (flashed[i][j]) { 123 | grid[i][j] = 0 124 | flash++ 125 | } 126 | } 127 | } 128 | if (flash == (rows * columns)) { 129 | break 130 | } 131 | step++ 132 | } 133 | 134 | return step 135 | } 136 | 137 | val input = readInput( 138 | "Day11" 139 | ) 140 | val testInput = readInput("Day11test") 141 | check(part1(testInput, 100) == 1656) 142 | println(part1(input, 100)) 143 | check(part2(testInput) == 195) 144 | println(part2(input)) 145 | } -------------------------------------------------------------------------------- /src/Day04.kt: -------------------------------------------------------------------------------- 1 | fun main() { 2 | 3 | data class Cell(var value: Int, var marked: Boolean) 4 | 5 | fun cleanInput(input: List): List { 6 | val cInp = input.toMutableList() 7 | cInp.removeAt(0) 8 | cInp.removeIf { it.isEmpty() } 9 | return cInp 10 | } 11 | 12 | fun unmarkedSum(x: Int, bingoSet: Array>): Int { 13 | var sum = 0 14 | var startRow = (x / 5) * 5 15 | for (i in startRow until startRow + 5) { 16 | for (j in 0 until 5) { 17 | if (!bingoSet[i][j].marked) { 18 | sum += bingoSet[i][j].value 19 | } 20 | } 21 | } 22 | return sum 23 | } 24 | 25 | 26 | fun createBingoSet(cInp: List): Array> { 27 | val bingoSet = Array(cInp.size) { Array(5) { Cell(-1, false) } } 28 | for (i in cInp.indices) { 29 | val row = cInp[i].split(" ").toMutableList() 30 | row.removeIf { it.isEmpty() } 31 | for ((idx, ele) in row.withIndex()) { 32 | bingoSet[i][idx] = Cell(value = ele.toInt(), marked = false) 33 | } 34 | } 35 | return bingoSet 36 | } 37 | 38 | fun part1(input: List): Int { 39 | val bingoInput = input[0].split(",").map { it.toInt() } 40 | val cInp = cleanInput(input) 41 | val bingoSet = createBingoSet(cInp) 42 | var boardFound = false 43 | var x = -1 44 | var y = -1 45 | var winNumber = -1 46 | for (ele in bingoInput) { 47 | if (boardFound) { 48 | break 49 | } 50 | for (i in bingoSet.indices) { 51 | for (j in 0 until 5) { 52 | if (bingoSet[i][j].value == ele) { 53 | bingoSet[i][j].marked = true 54 | 55 | // check row and column of the cell for bingo 56 | // check row 57 | var rowB = true 58 | for (c in 0 until 5) { 59 | if (!bingoSet[i][c].marked) { 60 | rowB = false 61 | } 62 | } 63 | // check column 64 | var colB = true 65 | var startIdx = (i / 5) * 5 66 | for (r in startIdx until startIdx + 5) { 67 | if (!bingoSet[r][j].marked) { 68 | colB = false 69 | } 70 | } 71 | if (rowB || colB) { 72 | boardFound = true 73 | x = i 74 | y = j 75 | winNumber = ele 76 | } 77 | } 78 | } 79 | } 80 | } 81 | 82 | var sum = unmarkedSum(x, bingoSet) 83 | 84 | return sum * winNumber 85 | } 86 | 87 | fun part2(input: List): Int { 88 | val bingoInput = input[0].split(",").map { it.toInt() } 89 | val cInp = cleanInput(input) 90 | val noOfBoards = cInp.size / 5 91 | val bingoSet = createBingoSet(cInp) 92 | var boards = BooleanArray(noOfBoards) { false } 93 | var x = -1 94 | var y = -1 95 | var winNumber = -1 96 | for (ele in bingoInput) { 97 | if (boards.all { it }) { 98 | break 99 | } 100 | for (i in bingoSet.indices) { 101 | for (j in 0 until 5) { 102 | if (bingoSet[i][j].value == ele) { 103 | bingoSet[i][j].marked = true 104 | 105 | // check row and column of the cell for bingo 106 | // check row 107 | var rowB = true 108 | for (c in 0 until 5) { 109 | if (!bingoSet[i][c].marked) { 110 | rowB = false 111 | } 112 | } 113 | // check column 114 | var colB = true 115 | var startIdx = (i / 5) * 5 116 | for (r in startIdx until startIdx + 5) { 117 | if (!bingoSet[r][j].marked) { 118 | colB = false 119 | } 120 | } 121 | if (rowB || colB) { 122 | val boardNo = i / 5 123 | if (!boards[boardNo]) { 124 | boards[i / 5] = true 125 | x = i 126 | y = j 127 | winNumber = ele 128 | } 129 | } 130 | } 131 | } 132 | } 133 | } 134 | 135 | var sum = unmarkedSum(x, bingoSet) 136 | return sum * winNumber; 137 | } 138 | 139 | val input = readInput("Day04") 140 | val testInput = readInput("Day04test") 141 | check(part2(testInput) == 1924) 142 | println(part1(input)) 143 | println(part2(input)) 144 | 145 | } 146 | -------------------------------------------------------------------------------- /src/Day05.kt: -------------------------------------------------------------------------------- 1 | import kotlin.math.*; 2 | 3 | fun main() { 4 | 5 | data class Coord(var x: Int, var y: Int) 6 | 7 | fun parseInput(rawInput: List): List> { 8 | val pInp = rawInput.toMutableList() 9 | 10 | val left = pInp.map { 11 | it.split("->")[0] 12 | }.map { 13 | val ordinals = it.trim().split(",") 14 | val x = ordinals[0].toInt() 15 | val y = ordinals[1].toInt() 16 | return@map Coord(x, y) 17 | } 18 | val right = pInp.map { 19 | it.split("->")[1] 20 | }.map { 21 | val ordinals = it.trim().split(",") 22 | val x = ordinals[0].toInt() 23 | val y = ordinals[1].toInt() 24 | return@map Coord(x, y) 25 | } 26 | 27 | return left.zip(right) 28 | } 29 | 30 | fun maxSize(input: List): Int { 31 | var max = Int.MIN_VALUE 32 | input.toMutableList().flatMap { 33 | it.split("->") 34 | }.flatMap { 35 | it.split(",") 36 | }.map { 37 | it.trim().toInt() 38 | }.forEach { 39 | if (it > max) max = it 40 | } 41 | return max 42 | } 43 | 44 | fun part1(input: List): Int { 45 | val directions = parseInput(input) 46 | val maxSize = maxSize(input) + 1 47 | val vents = Array(maxSize) { IntArray(maxSize) } 48 | directions.forEach { direction -> 49 | if (direction.first.x == direction.second.x) { 50 | // vertical line 51 | if (direction.first.y < direction.second.y) { 52 | for (i in direction.first.y..direction.second.y) { 53 | // ALL following matrix access is mat[col][row] coz thats how the PS has it 54 | vents[i][direction.first.x]++ 55 | } 56 | } else { 57 | for (i in direction.second.y..direction.first.y) { 58 | vents[i][direction.first.x]++ 59 | } 60 | } 61 | } else if (direction.first.y == direction.second.y) { 62 | // horizontal line 63 | if (direction.first.x < direction.second.x) { 64 | for (i in direction.first.x..direction.second.x) { 65 | vents[direction.first.y][i]++ 66 | } 67 | } else { 68 | for (i in direction.second.x..direction.first.x) { 69 | vents[direction.first.y][i]++ 70 | } 71 | } 72 | } 73 | } 74 | var twoPoint = 0 75 | for (i in vents) { 76 | for (j in i) { 77 | if (j >= 2) twoPoint++ 78 | } 79 | } 80 | return twoPoint 81 | } 82 | 83 | 84 | fun part2(input: List): Int { 85 | val directions = parseInput(input) 86 | val maxSize = maxSize(input) + 1 87 | val vents = Array(maxSize) { IntArray(maxSize) } 88 | directions.forEach { direction -> 89 | if (direction.first.x == direction.second.x) { 90 | // vertical line 91 | if (direction.first.y < direction.second.y) { 92 | for (i in direction.first.y..direction.second.y) { 93 | // ALL following matrix access is mat[col][row] coz thats how the PS has it 94 | vents[i][direction.first.x]++ 95 | } 96 | } else { 97 | for (i in direction.second.y..direction.first.y) { 98 | vents[i][direction.first.x]++ 99 | } 100 | } 101 | } else if (direction.first.y == direction.second.y) { 102 | // horizontal line 103 | if (direction.first.x < direction.second.x) { 104 | for (i in direction.first.x..direction.second.x) { 105 | vents[direction.first.y][i]++ 106 | } 107 | } else { 108 | for (i in direction.second.x..direction.first.x) { 109 | vents[direction.first.y][i]++ 110 | } 111 | } 112 | } else { 113 | val diff = abs(direction.second.y - direction.first.y) 114 | if (direction.first.y < direction.second.y && direction.first.x > direction.second.x) { 115 | // upward slope 116 | for (i in 0..diff) { 117 | vents[direction.first.y + i][direction.first.x - i]++ 118 | } 119 | } else if (direction.first.y > direction.second.y && direction.first.x > direction.second.x) { 120 | // upward slope 121 | for (i in 0..diff) { 122 | vents[direction.first.y - i][direction.first.x - i]++ 123 | } 124 | } else if (direction.first.x < direction.second.x && direction.first.y < direction.second.y) { 125 | // downward slope 126 | for (i in 0..diff) { 127 | vents[direction.first.y + i][direction.first.x + i]++ 128 | } 129 | } else if (direction.first.x < direction.second.x && direction.first.y > direction.second.y) { 130 | // downward slope 131 | for (i in 0..diff) { 132 | vents[direction.first.y - i][direction.first.x + i]++ 133 | } 134 | } 135 | 136 | } 137 | } 138 | var twoPoint = 0 139 | for (i in vents) { 140 | for (j in i) { 141 | if (j >= 2) twoPoint++ 142 | } 143 | } 144 | return twoPoint 145 | } 146 | 147 | 148 | val input = readInput("Day05") 149 | val testInput = readInput("Day05test") 150 | check(part1(testInput) == 5) 151 | check(part2(testInput) == 12) 152 | println(part1(input)) 153 | println(part2(input)) 154 | } 155 | -------------------------------------------------------------------------------- /src/Day08.kt: -------------------------------------------------------------------------------- 1 | import kotlin.math.pow 2 | 3 | fun main() { 4 | 5 | fun part1(input: List): Int { 6 | val outputs = input.map { 7 | it.split("|")[1] 8 | }.flatMap { 9 | it.split(" ") 10 | } 11 | var uniqueSegments = 0 12 | outputs.forEach { 13 | when (it.trim().length) { 14 | 2, 3, 4, 7 -> { 15 | uniqueSegments++ 16 | } 17 | } 18 | } 19 | return uniqueSegments 20 | } 21 | 22 | 23 | fun parseInput(input: List): List> { 24 | return input.map { 25 | val (l, r) = it.split("|") 26 | Pair(l, r) 27 | } 28 | } 29 | 30 | fun part2(input: List): Int { 31 | val parsedInput = parseInput(input) 32 | var sum = 0 33 | for (inp in parsedInput) { 34 | 35 | val encounteredChar = BooleanArray(7) { false } 36 | 37 | val scrambled = inp.first.split(" ").toMutableList() 38 | scrambled.removeIf { it.isEmpty() } 39 | val scrambledOutputs = inp.second.split(" ").toMutableList() 40 | scrambledOutputs.removeIf { it.isEmpty() } 41 | 42 | // step 1 find probables for representing '1' 43 | val probable1 = HashSet() 44 | scrambled.forEach { 45 | if (it.length == 2) { 46 | it.toCharArray().forEach { c -> 47 | probable1.add(c) 48 | // to mark these chars as visited atleast once 49 | encounteredChar[c - 'a'] = true 50 | } 51 | } 52 | } 53 | 54 | // step 2 find confirmed for slot i 55 | // find scrambled with len 3 56 | // and take the Complement of intersection of set from step 1 and aforementioned set 57 | var i = ' ' 58 | val probable7 = HashSet() 59 | scrambled.forEach { 60 | if (it.length == 3) { 61 | it.toCharArray().forEach { c -> 62 | if (!probable1.contains(c)) { 63 | i = c 64 | encounteredChar[c - 'a'] = true 65 | } 66 | probable7.add(c) 67 | } 68 | } 69 | } 70 | 71 | // step 3 find scrambled with len 4 and find probables for slot vi and vii 72 | val probable4 = HashSet() 73 | scrambled.forEach { 74 | if (it.length == 4) { 75 | it.toCharArray().forEach { c -> 76 | probable4.add(c) 77 | encounteredChar[c - 'a'] = true 78 | } 79 | } 80 | } 81 | 82 | // step 4 find encoded string for "9" 83 | // need to find a string with length 6 which has all 84 | // 5 of the encountered chars until now and 1 un-encountered char 85 | // which will be slot iv confirm 86 | 87 | var iv = ' ' 88 | scrambled.forEach { 89 | if (it.length == 6) { 90 | if (it.toHashSet().intersect(probable7.union(probable4)).size == 5) { 91 | it.forEach { c -> 92 | if (!probable7.union(probable4).contains(c)) { 93 | iv = c 94 | encounteredChar[c - 'a'] = true 95 | } 96 | } 97 | } 98 | } 99 | } 100 | 101 | // step 5 the one char we havent encountered yet is the slot v confirmed 102 | var v = ' ' 103 | for ((idx, b) in encounteredChar.withIndex()) { 104 | if (!b) { 105 | v = 'a' + idx 106 | } 107 | } 108 | 109 | // step 6 find a len 5 word with 110 | // exactly 1 char each from probable1 and probable4 111 | // this would give slots ii and vii 112 | // which would inturn confirm slots iii and vi 113 | 114 | var ii = ' ' 115 | var vii = ' ' 116 | var iii = ' ' 117 | var vi = ' ' 118 | 119 | scrambled.forEach { 120 | if (it.length == 5) { 121 | val set = it.toHashSet() 122 | if (set.intersect(probable1).size == 1 && 123 | set.intersect(probable4.subtract(probable1)).size == 1 124 | ) { 125 | set.intersect(probable1).forEach { c -> 126 | ii = c 127 | } 128 | set.intersect(probable4.subtract(probable1)).forEach { c -> 129 | vii = c 130 | } 131 | probable1.forEach { c -> 132 | if (c != ii) { 133 | iii = c 134 | } 135 | } 136 | probable4.subtract(probable1).forEach { c -> 137 | if (c != vii) { 138 | vi = c 139 | } 140 | } 141 | } 142 | } 143 | } 144 | 145 | // step 7 create sets corresponding to 0 .. 9 146 | 147 | val zero = hashSetOf(i, ii, iii, iv, v, vi) 148 | val one = hashSetOf(ii, iii) 149 | val two = hashSetOf(i, ii, vii, v, iv) 150 | val three = hashSetOf(i, ii, vii, iii, iv) 151 | val four = hashSetOf(vi, vii, ii, iii) 152 | val five = hashSetOf(i, vi, vii, iii, iv) 153 | val six = hashSetOf(i, vi, vii, iii, iv, v) 154 | val seven = hashSetOf(i, ii, iii) 155 | val eight = hashSetOf(i, ii, iii, iv, v, vi, vii) 156 | val nine = hashSetOf(vii, vi, i, ii, iii, iv) 157 | 158 | val digits = listOf(zero, one, two, three, four, five, six, seven, eight, nine) 159 | 160 | // step 8 161 | var numericalValue = 0 162 | scrambledOutputs.forEachIndexed { index, out -> 163 | val outHash = out.toHashSet() 164 | var digit = 0 165 | digits.forEachIndexed { idx, d -> 166 | if (outHash == d) { 167 | digit = idx 168 | } 169 | } 170 | numericalValue += (digit * (10.0.pow(3 - index)).toInt()) 171 | } 172 | 173 | // step 9 add every numerical value to sum 174 | sum += numericalValue 175 | } 176 | return sum 177 | } 178 | 179 | 180 | val input = readInput("Day08") 181 | val testInput = readInput("Day08test") 182 | val testInput2 = readInput("Day08test2") 183 | check(part1(testInput) == 26) 184 | check(part2(testInput2) == 5353) 185 | check(part2(testInput) == 61229) 186 | println(part1(input)) 187 | println(part2(input)) 188 | } -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | --------------------------------------------------------------------------------