Auto Added by WPeMatico

Advent of Code 2022 in Kotlin: Announcing the Winners

Advent of Code in Kotlin

We are incredibly grateful to the hundreds of people who participated in Advent of Code in Kotlin this year! It was an absolute joy to solve the fun, holiday-themed puzzles every day from December 1 to December 25. JetBrains and the Kotlin team have participated in the event for several years, and this was the second year in a row that JetBrains sponsored Advent of Code. We had a great time learning new things and solving puzzles together with all of you!

This year, we invited the community to join us in solving the puzzles using Kotlin, and we were amazed by the turnout and productivity on display!

  • 5,800 repositories were created from the Advent of Code in Kotlin GitHub templates.
  • 1,550 repositories were labeled with the aoc-2022-in-kotlin topic.
  • 12 awesome guests appeared on our daily live streams, showing and discussing solutions over the first 12 days of the contest.

If you missed any of the live streams, you can catch up on our YouTube playlist:

Congratulations to all giveaway participants and winners!

We want to recognize the amazing community spirit we saw during Advent of Code, so we’re sending prizes to 15 participants:

  • 5 randomly selected winners from the Kotlin leaderboards.
  • 5 winners who made it to the top of the united Kotlin leaderboards.
  • 5 active participants from the community who shared solutions and helped others.

Congratulations to the Advent of Code 2022 in Kotlin winners – enjoy your exclusive prizes!

5 randomly selected winners

These participants solved a number of the daily challenges and were drawn from everyone who tagged their repository with this year’s topic. Congratulations to:

5 winners from the united Kotlin leaderboards

These participants performed highly throughout December, submitting their Kotlin solutions among the fastest in the Kotlin community. Congratulations to:

5 winners from the community

These participants stood out by spending significant time helping others, discussing problems and solutions in our community channels, and making the whole campaign a truly shared experience. Congratulations to:

We applaud all the participants, both those who have tackled Advent of Code in Kotlin for years and those who took on the challenge for the first time. A special thank you goes to Eric Wastl and everyone else who organized Advent of Code.

We can’t wait to see everyone in 2023 for the next edition of Advent of Code. Until then, continue to explore the features of Kotlin, keep learning, and make the most of it!

Check out Advent of Code resources

Continue ReadingAdvent of Code 2022 in Kotlin: Announcing the Winners

Tips and Tricks for Solving Advent of Code

Advent Of Code is currently ongoing and many people all over the world are taking part. We’d like to share some tips and tricks for solving the puzzles in Kotlin – we hope you find some of them helpful! Feel free to add your favorite tips or thoughts in the comments!.

If you’re solving the puzzles in Kotlin, don’t forget to add the aoc-2021-in-kotlin topic to your repo to take part in our giveaway! If you’re starting from scratch, consider using our prepared Github template.

How do you usually go about solving the puzzles? First of all, you read and parse the input data. Let’s start with some tips for this process, and then discuss tips for writing the actual solutions in Kotlin.

1. Jumping between sample and real input

All of the Advent of Code puzzles provide your own “personal” input and the sample input to check your solution. You often need to switch back and forth between the sample and real input. If the solution for the first part works for the sample input, you then need to run it on the real input to get your result. If the solution then works on the real input, you likely switch back to the sample input to start solving the second part of the puzzle.

One of the ways to switch between two inputs quickly is by using comments with the IntelliJ IDEA action “Comment with Line Comment”. This changes the line state and removes the comment from the commented line, rather than simply adding a comment.

You can put the sample and real inputs in different files and toggle between comments for the file names by pressing the Ctrl+/ shortcut twice:

Comment line action

These small things make a difference and make working with IntelliJ IDEA a pleasure!

2. Parsing input

The next thing to do after reading the input is to parse it. You can either use the Kotlin library functions for working with strings or regular expressions. Library functions like substringBefore() and substringAfter() cover lots of cases and are often enough. Use String.toInt() and Char.digitToInt() to convert the string content or characters into integers.

For more complicated scenarios, there are regular expressions. Use the destructured function to assign the output to different variables right away:

val inputLineRegex = """(d+),(d+) -> (d+),(d+)""".toRegex()

val (startX, startY, endX, endY) = inputLineRegex
    .matchEntire(s)
    ?.destructured
    ?: throw IllegalArgumentException("Incorrect input line $s")

The action “Check RegExp” in IntelliJ IDEA allows you to quickly check whether the sample input satisfies your regular expression.

Comment line action

3. Storing input

It’s often helpful to introduce domain types, even for smaller tasks like these puzzles. Solving the task for Cells and Boards, or for Segments and SevenSegmentDigits can be much easier than working directly with Ints, List of Lists of Ints, or Chars and Sets of Chars. Types help to direct your thinking to the heart of the problem.

Kotlin makes this really easy – define a one-line data class and that’s it. You don’t need a separate file, like in Java, since it’s in your main file with the rest of your solution code:

data class Step(val direction: Direction, val units: Int)

In Kotlin, you can use an extension to convert an input line directly to a required type:

fun String.toStep(): Step
fun String.toTicket(): Ticket
fun String.toSevenSegmentDigitList(): List<SevenSegmentDigit>

Then your typical starter code will look like this:

val steps = readInput(fileName).map(String::toStep)

You can either use the function reference String::toStep or the lambda expression map { it.toStep() }.

4. Enumerations instead of strings

It might be tempting to manipulate the string literals directly, but making them enum constants makes it easier to write the code and reason about how it works. You never know what comes in the second part of the puzzle!

enum class Direction { UP, DOWN, FORWARD }
data class Step(val direction: Direction, val units: Int)

It is easy to convert an input string to an enum and use the EnumClassName.valueOf("") function to get the constant by name:

// "forward 8"
fun String.toStep() = Step(
   Direction.valueOf(substringBefore(" ").uppercase()),
   substringAfter(" ").toInt()
)

With when expressions you can check all of the options and you don’t need to include the else branch:

Comment line action

IntelliJ IDEA can generate all the branches automatically.

for ((direction, units) in steps) {
   when (direction) {
       UP -> depth -= units
       DOWN -> depth += units
       FORWARD -> horizontalPosition += units
   }
}

Note that in the destructuring declaration syntax in the for loop, you automatically assign two properties, the contents of each Step, into two loop variables.

5. From typealias to a class

If working with primitives is easier at first, and defining a separate class looks too cumbersome, consider defining a typealias. You can convert it into a class later, if needed. When you replace a typealias with a class, most of the code continues to compile, and you immediately see which functions are missing.

typealias Segment = Char
data class SevenSegmentDigit(val segments: Set<Segment>) {
   constructor(s: String): this(s.toSet())
   override fun toString() = segments.toList().sorted().joinToString("")
}

For example, the characters from 'a' to 'g' used to encode the seven-segment display can be referred to as Segments in the code and be regular Chars underneath. You can define the SevenSegmentDigit class first as a typealias for Set and later convert it to a class, for example, if you want to replace a default toString with a custom one.

6. Building lists and maps

In addition to the standard listOf(), mutableListOf(), and similar functions, you can use other methods to build collections.

You can call the List function that looks like a constructor (but is not!) to provide a way to calculate each element:

List(4) { it * it } // [0, 1, 4, 9]

Use buildList and buildMap functions to build data structures imperatively:

val monsters: List<Position> = buildList {
   for (y in 0..tile.size - Monster.height) {
       for (x in 0..tile.size - Monster.width) {
           if (monsterAt(tileRotation, x, y)) {
               add(Position(x, y))
           }
       }
   }
}

In this example, we call add on a MutableList inside the lambda, and the resulting type is the read-only List.

The similar sequence {..} function builds a Sequence lazily yielding values one by one.

7. Associate and group

The task of building a map from a list occurs quite often, and associate and groupBy functions make this operation straightforward. You can group elements by the provided property with groupBy, use elements as map keys to provide a way to build values (associateWith), use elements as values (associateBy), or provide a way to build a key-value pair from each element (associate).

The groupBy function groups the elements with the specified value used as a map key:

val occurrences: Map<Int, List<Segment>> =
   segments.groupBy { countSegmentInAllDigits(it) }
// {8=[a, b], 9=[c], 7=[d, g], 4=[e], 6=[f]}

The result of calling countSegmentInAllDigits in this example becomes the key in the map.

If you don’t need the groups directly, but you need to find the size of each group, use a lazy counterpart to groupBy: the groupingBy function. It doesn’t return a map straightaway, but it allows you to analyze the groups in a lazy manner:

listOf("abc", "c", "ad", "bc", "ab", "ca")
   .groupingBy(String::first)
   .eachCount() // {a=3, e=2, d=1}

If the property you’re using to group the elements is unique, use associateBy. For instance, if you need to access elements by their indexes, associateBy will build a map from indexes to elements for you:

val rulesMap: Map<Int, Rule> = rules.associateBy { it.index }

Let’s imagine you need to build a map representing an initial state by associating each of the input numbers with the corresponding Node. Use associateWith:

val initialState: Map<Int, Node> = numbers.associateWith { Node(it) }

If you need more complicated keys and values, use associate:

val bagRuleMap: Map<Bag, List<Content>> = bagRules.associate { it.bag to it.contentList }

If you don’t remember which associate function you need, choose the general associate one, and IntelliJ IDEA will suggest a better one automatically:

Comment line action

8. Sliding by windows

Sliding a list to get chunks of a given size is often useful, like in this year’s first puzzle.

numbers.windowed(2).count { (first, second) -> first < second }
"abcde".windowed(2) // [ab, bc, cd, de]

An alternative to building a list of chunks of size 2 is to use zipWithNext().

9. Sum of, min of

You don’t need to map the elements first to later find the resulting sum – the sumOf function combines these two operations. IntelliJ IDEA even suggests these replacements automatically:

Comment line action

There are similar functions maxOf and minOf for finding the maximum and minimum among the transformed values.

10. Adding index

Need to perform computations with an element index? In addition to the withIndex() extension that returns a list of pairs to iterate through, you can use many “indexed” counterparts for standard library functions, such as forEachIndexed, filterIndexed, mapIndexed, foldIndexed, and so on.

In the following example, the final calculateScore function uses the indexed version of fold to include an index into a computation of the result:

fun RoundConfiguration.calculateScore(): Long {
   val winner = listOf(playerA, playerB).maxByOrNull { it.size }!!
   return winner
       .cards()
       .foldIndexed(0L) { index, acc, element ->
           acc + (winner.size - index) * element
       }
}

Note how we marked 0 as a Long constant here (0L) to perform the computation on Long values.

11. Also logging

If the puzzle answer isn’t correct and you want to track the intermediate results step by step, you can print or log the intermediate values. The also function allows you to include println or log directives in the middle of the call chain or display the function result if you use an expression-body syntax.

In this example, we return the result of the function and also do some logging:

private fun checkRow(row: List<Int>, visited: Set<Int>) =
   row.all { elem -> elem in visited }
       .also { result -> log("Checking row $row $visited: $result") }

Here, we insert also calls in the middle of the call chain to observe the intermediate results of the computation:

val differences = input
   .windowed(2)
   .also(::log)
   .map { (first, second) -> second - first }
   .also(::log)
   .sorted()

If you need to print each list element on a separate line, you can include .onEach(::println) to the middle of the call chain. onEach performs an operation on each element and returns the unmodified list.

To avoid commenting on the lines with println, make a habit of using your own small log function instead. This way, you only need to change it in one place to stop printing all of the intermediate values for your solution.

12. Queue and stack together

Need a queue or a stack to implement an algorithm when solving the puzzle? Use ArrayDeque, a double-ended queue that provides quick access to both ends. It can be used either as a queue or a stack when needed.

For instance, in a classic implementation of a depth-first search, create a queue as an ArrayDeque and call its add and removeFirst methods inside:

fun dfs(board: Board, initial: Cell): Set<Cell> {
   val visited = mutableSetOf<Cell>()
   val queue = ArrayDeque<Cell>()
   queue += initial
   while (queue.isNotEmpty()) {
       val cell = queue.removeFirst()
       visited += cell
       queue += board.getUpperNeighbors(cell)
   }
   return visited
}

In this example, we use the short syntax += to call the plusAssign(element: T) and plusAssign(elements: Iterable) operators, which simply redirect to the corresponding add functions.

Of course, the ArrayDeque structure is useful any time you need quick access to both the start and end of the list of elements.

13. Operators

Operator overloading, which looks like mostly library or DSL-magic functionality, might also be useful when solving such small puzzles.

Consider overloading get and set operators to simplify the code for working with your class. For instance, by providing the get operator that takes Cell as an argument for the following class you can access its content more easily:

dataclass Board(val content: List<List<Int>>) {
   operator fun get(cell: Cell) =
       content.getOrNull(cell.i)?.getOrNull(cell.j)
}

Instead of writing board.content[cell.i][cell.j] for all invocations, you write

board[cell]. You can provide the set operator for mutable content accordingly.

Using the contains operator might make the code cleaner:

data class Line(val start: Point, val end: Point) {
   operator fun contains(point: Point): Boolean { … }
}

Then you can call it via the in keyword:

inputLines.count { line -> point in line }

If your elements are comparable, you can make them implement the Comparable interface, and then compare the elements using the standard <, <=, >, and >= operations.

———

Last but not least, consider creating a collection of your utilities specifically for solving Advent of Code puzzles. For example, you’ll definitely find a task that requires a point with two integer coordinates and uses its neighboring points!

That’s all for now! We hope that you enjoy solving the AdventOfCode puzzles as much as we do, and find these small tips useful!

Continue ReadingTips and Tricks for Solving Advent of Code

Advent of Code 2021 in Kotlin, Day 3: Binary Diagnostic

The nautical advent-ure continues with the third day of AOC 2021, which we will, of course, once more solve in Kotlin! This time, it will involve wrangling binary numbers in all sorts of ways. Check out the solution and a detailed explanation on YouTube, or follow along as we explain our solution in this blog post!

Solving part 1

We’re on maintenance duty today: That means we’re working with a report of binary numbers. Our goal is to calculate two values in order to get our gold star. They are referred to as the gamma and epsilon rate in the original problem description, which you can find on adventofcode.com.

We calculate the gamma rate by determining the most common bit for each position of the binary numbers in our input. When there’s more “1” bits than “0” bits in a column, the resulting number will be a “1”. Or when it is the other way around, and we see more “0” bits than “1” bits, our result will have a “0” in that position. We repeat that process for each column in our inputs until we get the final gamma rate in binary representation.

The epsilon rate is calculated analogously to the gamma rate, only this time, it always looks at the least common bit in each position.

Once both numbers are calculated, we arrive at the answer – the power consumption of our submarine – by converting the two numbers to decimal, and multiplying them.

Let’s turn our intuitive understanding into a Kotlin program! These examples use the scaffolding provided by the Advent of Code Kotlin Template, but a standalone solution would look very similar.

fun main() {
    fun part1(input: List<String>): Int {
        TODO()
    }

    val testInput = readInput("Day03_test")
    val input = readInput("Day03")
    println("P1 Test: " + part1(testInput))
    check(part1(testInput) == 198)
    println("P1 Result: " + part1(input))
}

Gamma rate: Most popular bits

From the problem description, we know that we are going to need to iterate our list of inputs (a list of binary strings) column by column. We can assume that all numbers in the input have the same length, so we can use the zero’th element, and save its indices into a variable via the indices function.

We can then loop over all the column indices, and count the number of ones and zeroes in that column to help us decide what we should do next. Ideally, we would like to use a function such as the following:

val columns = input[0].indices
for(column in columns) {
    input.countBitsInColumn(column)
}

Since this is a pretty problem-specific function, the standard library does not come with an implementation for it. But with the power of extension functions, we can build this exact function ourselves! A simple implementation looks like this:

private fun List<String>.countBitsInColumn(column: Int): Pair<Int, Int> {
    var zeroes = 0
    var ones = 0
    for(line in this) {
        if(line[column] == '0') zeroes++ else ones++
    }
    return zeroes to ones
}

The function keeps track of the ones and zeroes, and returns them as a Pair (constructed via the infix function to).

We can still make this code a bit more expressive, by replacing the Pair return type with our own custom data class:

private data class BitCount(val zeroes: Int, val ones: Int)

By having the function return an instance of BitCount, the zeroes and ones can be accessed explicitly, and our code has become a little bit more expressive:

val bitCount = input.countBitsInColumn(column)
val zeroes = bitCount.zeroes
val ones = bitCount.ones

Since BitCount is a data class, we can use a destructuring declaration to elegantly access the zeroes and ones directly instead of having to write the three lines above. This single line has the same effect:

val (zeroes, ones) = input.countBitsInColumn(column)

Creating the string of binary numbers that makes up the gamma rate is now a matter of figuring out which bit is more popular, and concatenating all of these bits into a single string. To do so, we can use Kotlin’s buildString function, which gives us access to a StringBuilder. We can combine that with a short if-expression, and append the most common bit to the gammaRate string:

val gammaRate = buildString {
    for (column in columns) {
        val (zeroes, ones) = input.countBitsInColumn(column)
        val commonBit = if (zeroes > ones) "0" else "1"
        append(commonBit)
    }
}

This makes up the first half of the solution for part 1. To see the calculated value, we can turn this string of binary digits back into a real integer using the toInt function, and telling it we’re looking at a base-2 number:

return gammaRate.toInt(2)

This outputs “22”! We can confirm that this is indeed the correct gamma rate for the given sample input by going back to adventofcode.com and carefully re-reading the instructions. Great!

Epsilon rate: The binary inverse

Moving on to the epsilon rate, we make use of the property we observed in our initial discussion: the epsilon rate is just the binary inverse of the gamma rate. Where you see a “1” in the gamma rate, there’s a “0” in the epsilon rate, and vice versa. Once again, using an extension function, the call site for the epsilon rate looks like this:

val epsilonRate = gammaRate.invertBinaryString()

We can use a few primitives from the standard library to assemble the implementation for invertBinaryString: we use the asIterable function to get access to the individual characters, and use the joinToString function to turn each “0” into a “1” and each “1” into a “0”:

private fun String.invertBinaryString() = this
    .asIterable()
    .joinToString("") { if (it == '0') "1" else "0" }

To get the final result, we multiply these two numbers together, and obtain the power consumption of the submarine, which we can submit and get our first gold star for the day!

return gammaRate.toInt(2) * epsilonRate.toInt(2)

Intermission: An alternative approach for the gamma rate

Before moving on to part 2 of the challenge, let’s reuse our knowledge that joinToString takes a transform function, and attempt to write a more functional version of the algorithm that calculates the gamma rate in this problem. We can combine the map function, a destructuring declaration, and joinToString to get the same result as the solution that uses a for-loop, buildString, and append:

val gammaRate2 = columns
    .map { input.countBitsInColumn(it) }
    .joinToString("") { (zeroes, ones) ->
        if (zeroes > ones) "0" else "1"
    }

check(gammaRate == gammaRate2)

Having seen both solutions, which one do you prefer? Feel free to let us know in the comments below, or on our YouTube video!

Here’s the complete implementation of part 1 again, for reference:

fun part1(input: List<String>): Int {
    val columns = input[0].indices
    val gammaRate = buildString {
        for (column in columns) {
            val (zeroes, ones) = input.countBitsInColumn(column)
            val commonBit = if (zeroes > ones) "0" else "1"
            append(commonBit)
        }
    }
    val gammaRate2 = columns
        .map { input.countBitsInColumn(it) }
        .joinToString("") { (zeroes, ones) ->
            if (zeroes > ones) "0" else "1"
        }
    check(gammaRate == gammaRate2)
    val epsilonRate = gammaRate.invertBinaryString()
    return gammaRate.toInt(2) * epsilonRate.toInt(2)
}

private fun String.invertBinaryString() = this
   .asIterable()
   .joinToString("") { if (it == '0') "1" else "0" }

private fun List<String>.countBitsInColumn(column: Int): BitCount {
   var zeroes = 0
   var ones = 0
   for (line in this) {
       if (line[column] == '0') zeroes++ else ones++
   }
   return BitCount(zeroes, ones)
}

Solving part 2

For part 2, we once again multiply numbers together: this time, the oxygen generator rating, and the CO2 scrubber rating! You can find detailed instructions on how to calculate these numbers on adventofcode.com. We’ll also discuss the computation of the oxygen generator rating here as an example.

The algorithm once again iterates the numbers column by column again, determining the most frequent bit (or, in the case of the CO2 scrubber rating, the least frequent bit). Then, the process of elimination is applied: all numbers that don’t have the most frequent bit are removed, and only the numbers which have the most popular bit in the given position remain. Tiebreakers go to the numbers with “1” in a given position for the oxygen generator rating, and the inverse for the CO2 scrubber.

This process is repeated until only one number remains, which is our result.

Computing the oxygen generator rating

Let’s start by defining a small local function for it, and let’s fill in some logic. We can structure the function similarly to our solution for part 1. To implement the process of elimination, we need to keep track of all the candidate numbers (starting with all of the input). We then need to repeatedly filter the list to make sure only the numbers with the most common bit at any given position remain.

We can reuse our countBitsInColumn implementation to arrive at a solution that looks like this:

fun oxyRating(): String {
    val columns = input[0].indices
    var candidates = input
    for(column in columns) {
        val (zeroes, ones) = candidates.countBitsInColumn(column)
        val mostCommon = if (zeroes > ones) '0' else '1'
        candidates = candidates.filter { it[column] == mostCommon }
        if(candidates.size == 1) break
    }
    return candidates.single()
}

When coming up with this snippet, it’s important we take great care to ensure the if-condition handles tiebreakers correctly: If “0” and “1” are equally common, it keeps values with a “1” in the position being considered.

Because I made the choice to use an immutable list here, removing elements is done via the filter function, which creates a new list. This list can then be assigned to the candidates variable again. If you’d like, you can also try to use a mutable list, and use the removeIf function for this algorithm, instead.

The only candidate that should remain can be extracted from the collection via the single function, and then returned. This allows us to already check our first partial result:

return oxyRating().toInt(2)

Copy-pasting the CO2 implementation

We can get our quick-and-dirty implementation of the CO2 scrubber rating by copy-pasting the code we wrote a moment ago, and adjusting the filter condition to keep only those candidates that don’t have the most common bit:

fun co2Rating(): String {
    val columns = input[0].indices
    var candidates = input
    for (column in columns) {
        val (zeroes, ones) = candidates.countBitsInColumn(column)
        val mostCommon = if (zeroes > ones) '0' else '1'
        candidates = candidates.filter { it[column] != mostCommon }
        if(candidates.size == 1) break
    }
    return candidates.single()
}

Together with our previous partial solution, this is actually enough to get our second gold star for the day – multiply the numbers, run the program, and get your reward:

return oxyRating().toInt(2) * co2Rating().toInt(2)

Removing duplication

Hopefully, you’ll agree at this point that our code could still be improved before we check it into our repository. Both co2Rating and oxyRating are essentially the same function, just with a different parameter. We only changed a single line of code in them – why would we want to have all that other code lying around as well? We can do better. We can define an enum class RatingType to distinguish between the types of ratings we want to calculate:

private enum class RatingType {
    OXYGEN,
    CO2
}

We can then turn one of the two rating functions into a multi-purpose implementation by using a when-statement based on the requested RatingType:

fun part2(input: List<String>): Int {
    fun rating(type: RatingType): String {
        val columns = input[0].indices
        var candidates = input
        for (column in columns) {
            val (zeroes, ones) = candidates.countBitsInColumn(column)
            val mostCommon = if (zeroes > ones) '0' else '1'
            candidates = candidates.filter {
                when (type) {
                    RatingType.OXYGEN -> it[column] == mostCommon
                    RatingType.CO2 -> it[column] != mostCommon
                }
            }
            if (candidates.size == 1) break
        }
        return candidates.single()
    }
    return rating(RatingType.OXYGEN).toInt(2) * rating(RatingType.CO2).toInt(2)
}

This code works the same, but is much more concise, and doesn’t contain duplications anymore. That’s much better!

We’re done!

That’s it! This code can go into our repository, and we can celebrate another problem solved, and hopefully some more Kotlin lessons learned.

If you’re coming up with your own solutions for Advent of Code, or if you’ve coded along to our videos, make sure to share your code on GitHub, and add the topic aoc-21-in-kotlin to your repo! This way you have a chance to win some Kotlin swag this holiday season! Check out the “Advent of Code 2021 in Kotlin” blog post for more details.

Enjoy yourself, keep warm, and take care!

Continue ReadingAdvent of Code 2021 in Kotlin, Day 3: Binary Diagnostic

Advent of Code 2021 in Kotlin, Day 1

In this post, I’m going to walk you through my solution to the Advent of Code 2021 Day 1 task. Of course, I used Kotlin to solve it! 

The input for the task is a text file, where each line is a number that represents a measurement of the depth of the seafloor. The task is to count the number of measurements where the depth increases. For example:

199 (N/A - no previous measurement)
200 (increased)
208 (increased)
210 (increased)
200 (decreased)
207 (increased)
240 (increased)
269 (increased)
260 (decreased)
263 (increased)

In the sample input above, there are 10 measurements. The depth of the seafloor increases 7 times, and that would be the correct answer for the task.

To read the file, I used the following line of code:

File("input.txt").readLines()

The result was a list of strings, but I actually needed a list of integers. Hence, I came up with the following helper function to read the input file and transform it into a list of integers:

fun readInputAsInts(name: String) = File("src", "$name.txt").readLines().map { it.toInt() }

Since the task is to compare each number to the previous one, I needed to read the numbers in pairs: the first and the second, then the second and the third, and so on. The Kotlin standard library provides a very useful function for this situation: windowed().

val input = readInputAsInts("Day01")
val list: List<List<Int>> = input.windowed(2)

The result was a list where each element was a pair of integers that I needed to compare and see whether the second integer was greater.

The count function with a predicate as a parameter is perfect for this situation. I was able to implement the whole solution as follows:

val input = readInputAsInts("Day01")
input.windowed(2).count { (a, b) -> a < b }

As is usually the case in Advent of Code puzzles, the requirement changes in the second part. I now needed to work with a three-measurement sliding window.  For the sample input above, this meant that I needed to read three numbers (199, 200, 208), then read another triple (200, 208, 210), calculate the total for both triples, and compare them. Then I would count the number of cases where the second total was greater than the first. In this example 199+200+208 < 200+208+210, meaning this case counts.

The solution that was appealing to me was to optimize the condition for this task a bit. I needed to compare the totals of the triples as follows: 199+200+208 <=> 200+208+210. I saw the same two numbers on both sides of the equation, which meant I could just eliminate these figures, leaving me with 199 < 210. 

Each pair of triplets contained a total of only four numbers, so I was able to group the input by four elements and compare the first number to the last:

input.windowed(4).count { it[0] < it[3] }

The solution to the second part of the challenge turned out to be rather simple! 
I have published the code for my solution to the Advent of Code GitHub repository if you’d like to compare it to yours. Have fun with Advent of Code this year!

Continue ReadingAdvent of Code 2021 in Kotlin, Day 1

Advent of Code 2021 in Kotlin – Win Prizes, Solve Problems, Have Fun!

It’s that magical time of the year! Supermarkets are stocking cookies, the smell of cinnamon and roasted chestnuts fills the air, and maybe you’ll even see some snowflakes fall in front of your window. It can only mean one thing: Advent of Code is coming!

It starts December 1, when the first of twenty-five coding puzzles is unlocked. From then on, a new coding challenge comes out each day. Whether you’re a beginner programmer or seasoned professional, there isn’t a better or cozier way to give your problem-solving skills a workout than the fun seasonal challenges from adventofcode.com.

At JetBrains, we’re proud to be supporting Advent of Code this year as one of its top sponsors. Many people across our different teams are already looking forward to solving and discussing the new challenges. To get you in the mood for AOC 2021, we’ve prepared a primer video that covers:

  • Basic tips on how to get up and running with solving AOC 2021 in Kotlin.
  • A ready-made GitHub template to give you some structure.
  • Some of the added incentives we have for those who try their hand at solving the challenges in Kotlin.

Check it out here:

You can also take a look at our selection of Advent of Code solutions from 2020!

Giveaway: Solve problems, win prizes

In addition to having fun and exchanging ideas with the community, our team is giving you an extra reason to share your code on GitHub this year! We’re giving away some Kotlin care packages to sweeten the holiday season for our community.

When we select the lucky winners, we’ll need to be able to find you and your code. So, to enter, make sure you meet the following conditions:

  • You have attempted to solve at least three days of AOC 2021.
  • You have shared your code publicly on your GitHub account.
  • You have added the aoc-2021-in-kotlin topic to your repository (see below).
  • You have a contact method (email, Twitter) on your GitHub profile.

To reiterate, you don’t need to be the fastest or complete every single challenge. Take your time, have fun, and use this opportunity to learn something new!

To add the aoc-2021-in-kotlin topic to your repository, edit the repository details by clicking the gear icon in the top right corner on the repository page. Then, in the topics field, add the value.

That’s all you need to do – with a bit of luck, we’ll reach out to you and arrange for your little surprise to be delivered to you!

GitHub template

If you want to start your Advent of Code journey with a bit of structure, you can use our GitHub repository template. It provides you with some basic scaffolding to structure your solutions and do automated testing using JUnit. To get started and receive a customized repository for your personal solutions, just press the “Use this template” button on the repository page. Do not fork the project.

Get the template

You can find information about the structure of the repository, its content, and where to put your solution files in the template README.

Please note that even when using this template, you’ll have to add the aoc-2021-in-kotlin tag manually to your repository in order to participate in the giveaway.

Solve problems and have fun!

We hope you’re excited for the challenges you’ll be getting each day throughout December, and that you’ll use this opportunity to play with Kotlin and discover something new that will be useful in other contexts.

As a last piece of advice: when doing Advent of Code, take your time, have fun, learn new things, and don’t feel pressured. You might have fun competing with your friends, but the most important thing is for you to enjoy yourself while spending time with the puzzles. After all, Kotlin is meant to be fun!

Enjoy the holiday season, indulge in some treats, and solve some interesting problems with Kotlin. Happy holidays!

Continue ReadingAdvent of Code 2021 in Kotlin – Win Prizes, Solve Problems, Have Fun!

End of content

No more pages to load