uniqueck

12494167?s=400&u=8b5a96545fd5df74ef263d06135616aa40f5c075&v=4

uniqueck
Constantin Krüger
Github: uniqueck, Twitter: CKrger

About me

First time, i hope i can provide every day a solution. I hope to start a little with ruby for the Asciidoctor universe or NodeJs is also fine to provide more extensions also for Antora.

Day 01: groovy

Day1 of Advent of Code

Done with TDD in Groovy.

Part 1

First we create a test with an assumption

    @Test
    void countIfNextMeasurementIncreased() {
        assertEquals(7, SonarSweep.countIfNextMeasurementIncreased([199, 200,208,210,200,207,240,269,260,263]))
    }

Then we create the production code and start here with the method countIfNextMeasurementIncreased(def list) We iterate over two pairs from the list and compare it with each other. If the second is greater than the first, count it.

    static int countIfNextMeasurementIncreased(List<Integer> input) {
        int counter = 0
        for (int i = 0; i < input.size() - 1; i++) {
            counter += count(input[i], input[i+1])
        }
        counter
    }
    static int count(int a, b) {
        b > a ? 1 : 0
    }

After we check that the test succeed, we read the input and pass it to the method.

        def collect = new File('input.txt').text.split('\n').collect { Integer::valueOf(it) }
        println "Part1 solution: ${countIfNextMeasurementIncreased(collect)}"
Part 2

We start with a test and the assumption from the puzzle description.

    @Test
    void countThreeMeasurementSlidingWindow() {
        assertEquals(5, SonarSweep.countThreeMeasurementSlidingWindow([199, 200,208,210,200,207,240,269,260,263]))
    }

Then we start implement the production code. We iterate over the list and create for each iteration two sub lists with each 3 elements. We sum these sub lists each separate and call the count method to compare these sums.

    static int countThreeMeasurementSlidingWindow(List<Integer> input) {
        int counter = 0
        for (int i = 0; i < input.size() - 3; i++) {
            int sum1 = input.subList(i, i +3).sum(0) as int
            int sum2 = input.subList(i+1, i +4).sum(0) as int
            counter += count(sum1, sum2)
        }
        counter
    }
    static int count(int a, b) {
        b > a ? 1 : 0
    }

Finally we pass the input to the method and print the solution.

        def collect = new File('input.txt').text.split('\n').collect { Integer::valueOf(it) }
        println "Part2 solution: ${countThreeMeasurementSlidingWindow(collect)}"

Day 02: groovy

Day 2: Dive!

Navigate never without a test case. Doit with TDD and groovy.

Part 1

just create a test that fails for the provided example course

    @Test
    void part1() {
        assertEquals(150, Navigation.calculateFinalPositionValue(["forward 5", "down 5", "forward 8", "up 3", "down 8", "forward 2"]))
    }

iterate over the planned steps and extract speed and direction. Update coordinates and return multiplied result of depth and horizontalPosition.

    enum Course {
        DOWN, FORWARD, UP
    }
    static int calculateFinalPositionValue(def plannedCourse = String[]) {
        int horizontalPosition = 0
        int depth = 0

        plannedCourse.each { String plannedStep ->
            def (direction, sValue) = plannedStep.tokenize(" ")
            int speed = Integer.valueOf(sValue)
            switch (Course.valueOf(direction.toUpperCase())) {
                case Course.FORWARD: {
                    horizontalPosition += speed
                    break
                }
                case Course.UP: {
                    depth -= speed
                    break
                }
                case Course.DOWN: {
                    depth += speed
                    break
                }
            }
        }
        return horizontalPosition * depth
    }

Finally we read the specific input and let navigator do the rest.

        def lines = new File("input.txt").readLines()
        println "Day2 part 1 solution: ${calculateFinalPositionValue(lines)}"
Part2

just create a test that fails for the provided example course

    @Test
    void part2() {
        assertEquals(900, Navigation.calculateFinalPositionValueWithAIM(["forward 5", "down 5", "forward 8", "up 3", "down 8", "forward 2"]))
    }

iterate over the planned steps and extract speed and direction. Update coordinates and return multiplied result of depth and horizontalPosition.

    enum Course {
        DOWN, FORWARD, UP
    }
    static int calculateFinalPositionValueWithAIM(def plannedCourse = String[]) {
        int horizontalPosition = 0
        int depth = 0
        int aim = 0

        plannedCourse.each { String plannedStep ->
            def (direction, sValue) = plannedStep.tokenize(" ")
            int speed = Integer.valueOf(sValue)
            switch (Course.valueOf(direction.toUpperCase())) {
                case Course.FORWARD: {
                    horizontalPosition += speed
                    depth += (aim * speed)
                    break
                }
                case Course.UP: {
                    aim -= speed
                    break
                }
                case Course.DOWN: {
                    aim += speed
                }
            }
        }

        println "${horizontalPosition}, ${depth}, ${aim}"


        return horizontalPosition * depth

    }

Finally we read the specific input and let navigator do the rest.

        def lines = new File("input.txt").readLines()
        println "Day2 part 2 solution: ${calculateFinalPositionValueWithAIM(lines)}"

Day 03: groovy

Day 03: Binary Diagnostic

Part1 calculate power consumption rating

Let start creating a test.

    @Test
    void calculatePowerConsumption() {
        assertEquals(198, BinaryDiagnostic.calculatePowerConsumption(["00100", "11110", "10110", "10111", "10101", "01111", "00111", "11100", "10000", "11001", "00010", "01010"]))
    }

We iterate over the input and count for each position the bit equals 1. If the sum is greater than the half over all input lines we set the bit 1 for the gamma rate and 0 fir the epsilon rate. After all positions are processed we can multiply the decimal values for gamma rating and epsilon rating to get the power consumption rating.

    static int calculatePowerConsumption(List<String> diagnosticReport) {

        def gammaBinary = ""
        def epsilonBinary = ""
        def tempValue = 0
        def length = diagnosticReport[0].length()
        int half = diagnosticReport.size() / 2

        for (int position = 0; position < length; position++) {
            tempValue = 0
            diagnosticReport.each { String it ->
                tempValue += Integer.parseInt(it[position])
            }
            gammaBinary += tempValue > half ? "1" : "0"
            epsilonBinary += tempValue > half ? "0" : "1"
        }
        println gammaBinary
        println epsilonBinary

        return Integer.parseInt(gammaBinary, 2) * Integer.parseInt(epsilonBinary, 2)
    }

Run the calculation with our puzzle input.

    static void main(String[] args) {
        println "Day3 power consumption rating: ${calculatePowerConsumption(new File("input.txt").readLines())}"
    }
Part2 calculate life support rating
  1. The same we start with a test for the example input.

        @Test
        void calculateLifeSupportRating() {
            assertEquals(230, BinaryDiagnostic.calculateLifeSupportRating(["00100", "11110", "10110", "10111", "10101", "01111", "00111", "11100", "10000", "11001", "00010", "01010"]))
        }
  2. So for this we have to count the bits on each position multiple times, so we create some help method countBitsOnPosition. We define another help method filterByCriteria to filter our input list for the criteria (oxygen and co2). The last method calculateLifeSupportRating is our main method to start the calculation based on out puzzle input. We call filterByCriteria two times with different closures for the filter criteria (oxygen and CO2)

        static int calculateLifeSupportRating(List<String> diagnosticReport) {
            // calculate oxygen generator rating
            String oxygenGeneratorRating = filterByCriteria(diagnosticReport, { int[] tempBitsCounted -> tempBitsCounted[0] > tempBitsCounted[1] ? "0" : "1" })
            String co2ScrubberRating = filterByCriteria(diagnosticReport, { int[] tempBitsCounted -> tempBitsCounted[0] <= tempBitsCounted[1] ? "0" : "1" })
            return Integer.parseInt(oxygenGeneratorRating, 2) * Integer.parseInt(co2ScrubberRating, 2)
        }
    
        static String filterByCriteria(List<String> diagnosticReport, Closure<String> filterBitClosure) {
            int position = 0
            List<String> tempList = diagnosticReport
            do {
                String filterBit = filterBitClosure.call(countBitsOnPosition(position, tempList))
                tempList = tempList.findAll { it[position] == filterBit }
                position++
            } while (tempList.size() > 1)
            return tempList[0]
        }
    
        static int[] countBitsOnPosition(int position, List<String> diagnosticReport) {
            def by = diagnosticReport.countBy { it[position] }
            return [by."0", by."1"]
        }
  3. Run the calculation with our puzzle input.

        static void main(String[] args) {
            println "Day3 life support rating: ${calculateLifeSupportRating(new File("input.txt").readLines())}"
        }

Day 04: groovy

Day 4: Giant Squid

This solution is written in Groovy and of course with TDD approach.

The original puzzle can be found at https://adventofcode.com/2021/day/4

The working example can be found here uniqueck / advent-of-code

Today I learned

You can solve the puzzle without an error free algorithm. Part 1 I solved and at part 2 if run the test it fails any time. My testcases for the class BingoBoard and method isWins don’t cover columns or maybe rows the right way. So for the second part I have to reimplement the isWins method. Also I doesn’t react in the markNumber method on not existing fields for that number, so maybe it was another day too late for the kind of puzzle ;) But my new lovely method for collections is collate → easily split a list to sublist of a specific size. Groovy I love you, but for the namings we have something to discuss, why in the hell call this method not partials

First Star

How does it work:

I created a class BingoBoard representing a bingo board with a subclass Field representing one field of a bingo board. These class can check if the boards currently wins, and we can sum the remaining points on this board with, also we can mark a field with the given number. To print the board to the console I added some simple toString method.

class BingoBoard {

    static class Field {
        int value
        boolean marked

        private Field(int value) {
            this.value = value
            this.marked = false
        }

        static Field of(int value) {
            return new Field(value)
        }

        String toString() {
            return (marked ? "X" : "${value}").padLeft(2, " ")
        }
    }

    List<Field> fields


    static BingoBoard init(String[] boardLayout) {
        def fields = boardLayout.collect { it.split(" ").findAll { it.trim() }.collect { Field.of(it.trim() as int) } }.flatten()
        return new BingoBoard(fields)
    }

    private BingoBoard(def fields) {
        this.fields = fields
    }

    boolean isWins() {
        // check rows
        def match = false
        int i = 0
        do {
            // check line
            match |= fields.subList(i * 5, 5 * i + 5).every { it.marked }
            // check row
            match |= fields.collate(5).every { it[i].marked }
            i++
        } while (!match && i < 5)
        return match
    }

    int sumOfAllUnmarkedNumbers() {
        def sum = fields.findAll { !it.marked }.collect { it.value as int }.sum(0)
        return sum as int
    }

    boolean markNumber(int value) {
        // search field with this number
        def field = fields.find { it.value == value }
        if (field) {
            field.marked = true
            return wins
        } else {
            return false
        }
    }

    String toString() {
        return fields.collect { it.toString() }.collate(5).collect {it.join(" ")}.join("\n")
    }


}

To read the puzzle input, I choose to skip empty lines and remove the first lines, and process it as the random numbers. For the remaining lines I choose my new lovely method collate to split all 5 lines, so I can map these as a BingoBoard instance. The first line we only have to split after , and parse the values as int.

    static GiantSquid initGame(File puzzleInputFile) {
        def allLines = puzzleInputFile.readLines().findAll { String it -> it?.trim() }
        int[] randomNumbers = allLines.remove(0).split(",").collect{it as int}
        def boards = allLines.collate(5).collect { it.join(" " ) }.collect { BingoBoard.init(it) }
        return new GiantSquid(boards, randomNumbers)
    }

So we can start play bingo and choose the right board to win. So we iterate over the random numbers and mark these number on each board. The mark method also returns the win state of these board after marking the number. So we can fast breaks the loops and calculate the final score.

    int playBingoAndReturnFinalScoreFromWinningBoard() {
        int finalScore = -1
        int randomNumbersIndex = 0
        do {
            for (int j = 0; j < boards.size(); j++) {
                if (boards[j].markNumber(randomNumbers[randomNumbersIndex])) {
                    finalScore = randomNumbers[randomNumbersIndex] * boards[j].sumOfAllUnmarkedNumbers()
                    break
                }
            }
            randomNumbersIndex++
        } while (finalScore == -1 && randomNumbersIndex < randomNumbers.size())
        return finalScore
    }
Second Star

How does it work:

The same way as for the First star, we iterate over the random numbers and mark all remaining boars. After that we look for a board that wins the game and save the instance, all winning boards are removed from the list, and we repeat this loop until the no board is left. So we can now calculate with the saved instance of the board the final score.

    int playBingoAndLetGiantSquidWins() {
        int finalScore = -1
        int randomNumbersIndex = 0
        int lastNumber = -1
        BingoBoard lastBoard = null
        do {
            lastNumber = randomNumbers[randomNumbersIndex]
            boards.each {it.markNumber(lastNumber)}
            lastBoard = boards.find {it.wins}
            boards.removeAll {it.wins }
            randomNumbersIndex++
        } while (boards.size() > 0 && randomNumbersIndex < randomNumbers.size())
        return lastBoard.sumOfAllUnmarkedNumbers() * lastNumber
    }

Day 05: groovy

Day 4: Hydrothermal Venture

This solution is written in Groovy and of course with TDD approach.

The original puzzle can be found at https://adventofcode.com/2021/day/5

The working example can be found here uniqueck / advent-of-code

Today I learned

It is better to complete all test of the helper classes and simplify the code afterwards.

First Star

How does it work:

We craete a list of Line elements from the puzzle input. We filter all lines out, which not have a start and end point on the the row or column. So only horizontal and vertical lines are processed.

Then we create the map based on all points / coords on the line and increased the bit for these coord.

After these map is created we filter out all elements with a bit higher than one.

package io.uniqueck.aoc.day5

import java.util.stream.IntStream

class Coord {

    int x, y

    static Coord of(String stringRepresentation) {
        def (tempX, tempY) = stringRepresentation.tokenize(",")
        new Coord(tempX as int, tempY as int)
    }

    Coord(int x, y) {
        this.x = x
        this.y = y
    }

    boolean isOnSameRowOrColumn(Coord target) {
        return x == target.x || y == target.y
    }


    List<Coord> createListOfCoordsToConnectTo(Coord target) {

        List<Coord> listOfCoords = []
        // diagonal
        int stepSizeX = target.x - x < 0 ? -1 : target.x - x == 0 ? 0 : 1
        int stepSizeY = target.y - y < 0 ? -1 : target.y - y == 0 ? 0 : 1

        int stepX = x
        int stepY = y

        while (!(stepX == target.x && stepY == target.y)) {
            listOfCoords << new Coord(stepX, stepY)
            stepX += stepSizeX
            stepY += stepSizeY
        }
        listOfCoords << target

        return listOfCoords
    }

    boolean equals(o) {
        if (this.is(o)) return true
        if (getClass() != o.class) return false

        Coord coord = (Coord) o

        if (x != coord.x) return false
        if (y != coord.y) return false

        return true
    }

    int hashCode() {
        int result
        result = x
        result = 31 * result + y
        return result
    }

    String toString() {
        return "(${x},${y})"
    }

}
package io.uniqueck.aoc.day5

class Line {

    Coord start, end

    // converts 1,2 -> 3,2 to a line with start coordinate 1,2 and end coordinate 3,2
    static Line of(String stringRepresentation) {
        def (tempStart, tempEnd) = stringRepresentation.tokenize("->")
        return new Line(Coord.of(tempStart), Coord.of(tempEnd))
    }

    Line(Coord start, end) {
        this.start = start
        this.end = end
    }

    boolean isStartAndEndOnSameRowOrColumn() {
        return start.isOnSameRowOrColumn(end)
    }

    List<Coord> getListOfCoordsFromStartToEnd() {
        return start.createListOfCoordsToConnectTo(end)
    }



}
package io.uniqueck.aoc.day5

class HydrothermalVenture {

    static void main(String[] args) {
        def lines = new File("src/main/resources/day5/input.txt").readLines()
        new HydrothermalVenture().process(lines)
    }

    void process(List<String> input) {
        def lines = input.collect {Line.of(it)}
        println "HydrothermalVenture only horizontal and vertical lines: ${countHowManyPointsAreOverlappingFromMoreThenOnLine(lines.findAll {it.startAndEndOnSameRowOrColumn})}"
        println "HydrothermalVenture all type of lines: ${countHowManyPointsAreOverlappingFromMoreThenOnLine(lines)}"
    }

    @SuppressWarnings('GrMethodMayBeStatic')
    int countHowManyPointsAreOverlappingFromMoreThenOnLine(List<Line> lines) {
        int[][] result = new int[999][999]
        lines.collect { it.listOfCoordsFromStartToEnd}.flatten().each { Coord it ->
            result[it.y][it.x]++
        }
        ((List<List<Integer>>)result).flatten().findAll{it > 1}.size()
    }
}
Second Star

How does it work:

The only difference between the first star and this star, is that we process also diagonal lines.

Day 06: groovy

Day 4: Lanternfish

This solution is written in Groovy.

The original puzzle can be found at https://adventofcode.com/2021/day/6

The working example can be found here uniqueck / advent-of-code

Today I learned

For performance fallback to simple data types is a good idea ;)

First Star + Second Star

I started to create a class representing the LanternFish and his behaviour, this worked well for Start 1, but for Star 2 this is a memory problem to spawn every time a new object of LanternFish.

So i switched back to array based approach. We have a array of datatype long to eliminate integer overflow errors.

So we iterate over the array and shift each entry to the left, until we reached the first position. For the first position we added the current count of fishes to position 6 (reseted age to 6 days) and the current count of zero day fishes as new borned fishes at position 8 (8 days).

class Simulator {

    static void main(String[] args) {
        def countBy = new File("input.txt").readLines().collect { it.split(",") }.flatten().collect { it as int }.countBy { it }
        long[] initialStock = [0,0,0,0,0,0,0,0,0]
        countBy.each {initialStock[it.key] += it.value}
        println initialStock
        println "Star1: ${countLanternFishesAfterDays(initialStock, 80)}"
        println "Star2: ${countLanternFishesAfterDays(initialStock, 256)}"
    }

    static long countLanternFishesAfterDays(long[] initialStock, int afterDays, int currentDay = 1) {
        println "Day: ${currentDay} - ${initialStock}"
        long[] newFishes = [0,0,0,0,0,0,0,0,0]
        initialStock.eachWithIndex { long count, int index ->
            if (index > 0) {
                newFishes[index - 1] += count
            } else {
                newFishes[6] += count
                newFishes[8] += count
            }
        }
        if (currentDay < afterDays) {
            return countLanternFishesAfterDays(newFishes, afterDays, currentDay + 1)
        } else {
            return newFishes.sum()
        }
    }



}

For the input processing we have to count all fishes based on the age of days to create the initialStock array.

Day 07: groovy

this documentation is autogenerated. Add a README.adoc to your solution to take over the control of this :-)

groovy

TheTreacheryOfWhalesTest.groovy
import org.junit.jupiter.api.Test

class TheTreacheryOfWhalesTest {

    @Test
    void fuel() {
        assert 37 == new TheTreacheryOfWhales().fuelPart1(new File("example.txt"))
        assert 168 == new TheTreacheryOfWhales().fuelPart2(new File("example.txt"))
    }

    @Test
    void sumNumberFrom1Till() {
        assert 1 == new TheTreacheryOfWhales().sumNumbersFrom1Till(1)
        assert 3 == new TheTreacheryOfWhales().sumNumbersFrom1Till(2)
        assert 6 == new TheTreacheryOfWhales().sumNumbersFrom1Till(3)
        assert 10 == new TheTreacheryOfWhales().sumNumbersFrom1Till(4)
    }
}
example.txt
16,1,2,0,4,2,7,1,2,14
TheTreacheryOfWhales.groovy
class TheTreacheryOfWhales {

    static void main(String[] args) {
        println "The Treachery of Wahles Fuel: ${new TheTreacheryOfWhales().fuelPart1(new File("input.txt"))}"
        println "The Treachery of Wahles Fuel: ${new TheTreacheryOfWhales().fuelPart2(new File("input.txt"))}"
    }

    @SuppressWarnings('GrMethodMayBeStatic')
    int fuelPart1(File puzzleInput) {
        int[] input = puzzleInput.readLines().collect {it.split(",")}.flatten().collect {it as int}
        int[] fuel = new int[input.max()]
        for (int i = input.min(); i < input.max(); i++) {
            for (int j = 0; j < input.length; j++) {
                fuel[i] += Math.abs(input[j] - i)
            }
        }
        println fuel
        fuel.min()
    }

    @SuppressWarnings('GrMethodMayBeStatic')
    int fuelPart2(File puzzleInput) {
        int[] input = puzzleInput.readLines().collect {it.split(",")}.flatten().collect {it as int}
        int[] fuel = new int[input.max()]
        for (int i = input.min(); i < input.max(); i++) {
            for (int j = 0; j < input.length; j++) {
                fuel[i] += sumNumbersFrom1Till(Math.abs(input[j] - i))
            }
        }
        println fuel
        fuel.min()
    }

    @SuppressWarnings('GrMethodMayBeStatic')
    int sumNumbersFrom1Till(int number) {
        int sum = 0
        number.times {sum += (it + 1) }
        sum
    }

}
input.txt
1101,1,29,67,1102,0,1,65,1008,65,35,66,1005,66,28,1,67,65,20,4,0,1001,65,1,65,1106,0,8,99,35,67,101,99,105,32,110,39,101,115,116,32,112,97,115,32,117,110,101,32,105,110,116,99,111,100,101,32,112,114,111,103,114,97,109,10,494,43,989,562,667,505,3,630,175,9,1115,348,1135,186,676,122,776,19,1303,9,263,199,628,352,951,31,589,535,975,1153,331,1253,528,1408,972,660,6,3,130,1057,1061,368,535,198,3,472,341,212,560,231,1384,79,265,99,1748,88,741,129,882,173,51,289,987,12,18,318,167,998,1165,255,113,29,279,608,32,395,118,623,1136,1067,220,213,324,386,392,136,316,311,84,145,7,414,772,636,536,217,221,230,719,221,35,923,75,432,12,629,33,681,830,164,514,272,1780,217,1037,123,216,297,44,4,439,1297,990,31,1084,182,708,873,83,265,224,286,910,486,228,1220,420,10,1197,771,384,564,96,332,855,682,924,983,1579,702,627,469,31,55,40,525,897,194,264,1357,40,892,161,738,503,530,1295,1180,901,683,1217,1446,353,40,31,930,225,1343,1064,167,650,200,878,446,44,185,354,841,43,545,682,196,433,148,71,1020,506,7,579,138,126,513,1232,580,808,507,97,420,217,683,1376,423,559,1372,1077,150,1268,366,93,30,228,538,1405,272,547,1044,38,31,281,287,785,327,391,480,70,206,49,492,27,389,79,37,1068,396,366,217,540,1123,998,298,455,726,33,925,175,41,731,1112,53,513,638,471,397,4,241,271,365,46,35,72,438,151,219,1071,1781,748,157,355,1186,151,926,271,222,1201,1060,34,119,260,266,1276,847,835,343,151,832,189,96,650,785,314,79,1355,129,205,569,865,375,190,126,413,73,14,291,98,43,1058,1375,7,809,6,719,60,258,412,439,619,15,82,407,222,1746,790,535,1221,181,515,615,757,904,58,921,689,205,653,282,41,1840,333,1459,1532,789,228,401,96,429,42,23,35,32,118,900,410,421,240,101,873,277,489,218,173,132,1161,426,1516,187,669,457,59,647,30,232,237,1158,39,815,1756,787,131,814,47,35,993,383,1459,117,101,637,84,1952,213,261,233,1238,76,821,866,314,236,417,951,473,370,187,484,225,199,472,140,1456,106,113,966,60,1543,49,13,6,102,519,111,670,991,325,124,269,28,126,894,781,597,1142,61,534,763,542,327,829,1558,524,15,703,44,643,98,435,54,164,624,387,1047,382,326,517,31,1575,938,1054,544,647,828,322,154,1021,82,1373,5,58,926,556,89,94,150,115,572,75,110,133,1508,273,230,561,80,1839,345,716,1003,1060,226,651,168,79,80,893,819,423,94,15,185,507,88,911,588,1320,249,863,295,447,146,756,317,38,18,179,115,727,316,1472,556,169,111,59,534,34,75,993,10,880,1364,675,1575,36,333,1268,1072,1538,862,746,1149,236,871,737,89,13,692,416,762,26,373,644,1299,256,252,512,900,1011,208,1205,94,820,444,925,1144,530,206,613,142,1543,216,77,279,600,128,148,120,8,24,1680,441,34,433,251,360,1636,205,113,610,1631,765,423,289,463,893,261,568,527,815,24,791,55,16,533,569,121,418,443,479,31,404,592,444,579,691,1560,1480,14,346,599,608,1199,193,84,1097,120,932,1517,244,430,21,1288,82,24,990,721,234,891,410,446,327,547,1410,755,1766,474,159,71,1326,358,597,768,450,392,122,44,971,384,99,406,729,453,1293,323,18,842,766,665,17,57,170,298,0,23,431,377,755,236,6,205,1441,293,163,66,963,1521,765,33,53,295,239,382,1146,100,128,1031,293,87,990,90,1469,168,319,1487,1090,39,250,57,308,277,284,214,1583,832,139,1785,544,674,288,836,482,93,631,786,663,239,791,23,38,897,13,468,81,139,648,189,363,32,962,494,52,603,284,935,305,110,2,1,109,543,354,689,265,333,973,350,618,154,789,848,5,30,223,540,74,8,544,911,197,246,96,562,168,118,384,167,1147,68,867,1041,1082,777,985,96,96,251,33,580,1066,17,135,212,433,355,617,1092,244,166,853,183,145,325,92,138,863,255,556,1420,638,57,119,1081,650,13,984,540,94,727,896,1070,1731,849,255,26,768,1134,540,363,211,657,686,831,168,136,241,398,86,572,191,542,12,1039,57,47,1317,498,390,77,605,267,42,474,313,95,758,823,265,924,540,93,1329,1214,573,263,827,8,140,121,1132,566,37,1604,67,65,8,132,663,1224,6,424,482,631,583,119,1285,91,403,387,472,888,121,236,41,277,481,103,104,1300,44,504,851,277,528,990,457,568,1093,79,34,1001,782,585,688,265,1006,166,293,870,653,41,345,957,607,649,938,381,200,46

Day 08: groovy

Day 8: Seven Segment Seach

This solution is written in Groovy.

The original puzzle can be found at https://adventofcode.com/2021/day/8

The working example can be found here uniqueck / advent-of-code

Today I learned

Start with the solution on a sheet of paper ;)

First Star

We have to count all unique output digits, this mean patterns with size (2,3,4,7)

    static int star1(File input) {
        int counter = 0
        input.readLines().each { String line ->
            println line
            def (uniqueSignalPatterns, fourDigitOutputValue) = line.tokenize("|")
            List<String> uniqueSignalPatternsList = uniqueSignalPatterns.tokenize(" ")
            println uniqueSignalPatternsList
            List<String> fourDigitOutputValueList = fourDigitOutputValue.tokenize(" ")
            println fourDigitOutputValueList

            fourDigitOutputValueList.findAll { [2, 3, 4, 7].contains(it.size()) }.each {
                counter += createCombinations(it).findAll {
                    uniqueSignalPatternsList.contains(it)
                }.size()
            }
        }
        return counter
    }
Second Star

To get the unique numbers I used the groupBy operator with size of the strings and the use a map with key is the digit. All not unique digits remain in a list of undetermined patterns.

We have to determined based on the unique digits (1,4,7,8) the other digits (0,6,9) and (2,3,5).

It’s easy to determine the 6, because we know the bits for digit 1 and one bit is not part of the 6, but both bits are part of 0 and 9.

The same solution works for 3 with a little difference, because we have to check if both bits are part of 3.

If we have determined the 3, we can determine based on the 3 the 9 and the last missing pattern is 0.

So only 2 and 5 are missing. Here we have to use the determined digits 1,6 and 9 to determine the 5. Why? We have to check which bit from digit 1 is not part of digit 6. We eliminate this bit from digit 9 and compare it with the last patterns. The match is 5, the remaining pattern is the two.

After this, we can decode the four output patterns and sum it up.

    static int star2(File input) {

        int sum = 0
        input.readLines().each { String line ->
            def (uniqueSignalPatterns, fourDigitOutputValue) = line.tokenize("|")

            println "UniqueSignalPatterns: ${uniqueSignalPatterns}"
            println "FourDigitOutputValue: ${fourDigitOutputValue}"

            def signalsForDigit = [:] as Map<Integer, String>
            def notDeterminedSignals = [] as List<String>
            uniqueSignalPatterns.trim().split(" ").collect { it.split("").sort().join() }.groupBy { it.size() }.each {

                switch (it.key) {
                    case 2: {
                        signalsForDigit[1] = it.value[0]
                        break
                    }
                    case 4: {
                        signalsForDigit[4] = it.value[0]
                        break
                    }
                    case 3: {
                        signalsForDigit[7] = it.value[0]
                        break
                    }
                    case 7: {
                        signalsForDigit[8] = it.value[0]
                        break
                    }
                    default: {
                        notDeterminedSignals += it.value
                    }
                }
            }

            println "determined signals after unique signals - start"
            println "determined signals: $signalsForDigit"
            println "not determined signals: $notDeterminedSignals"
            println "determined signals after unique signals - end"

            while (notDeterminedSignals) {
                notDeterminedSignals.each { String notDetermined ->
                    switch (notDetermined.size()) {
                        case 5: {
                            // check if already determined 1 is part of pattern, so we found the 3
                            if (notDetermined.contains(signalsForDigit[1][0]) && notDetermined.contains(signalsForDigit[1][1])) {
                                signalsForDigit[3] = notDetermined
                            } else
                            // if already determined 6 and 9, we can determined together with the 1 the last to remaining
                            if (signalsForDigit[6] && signalsForDigit[9] && signalsForDigit[1]) {

                                // check which bit from 1 is not part of 6
                                println signalsForDigit[6]
                                println signalsForDigit[1][0]
                                println signalsForDigit[1][1]
                                String signalForDigit6 = signalsForDigit[6]
                                String bit = signalForDigit6.contains(signalsForDigit[1][0]) ? signalsForDigit[1][1] : signalsForDigit[1][0]
                                println "Bit ${bit} is not part of 6"
                                // remove these bit from 9 and compare it with the current undetermined signal, if it succeed we found the 5
                                if (signalsForDigit[9].replace(bit, "") == notDetermined) {
                                    signalsForDigit[5] = notDetermined
                                } else {
                                    signalsForDigit[2] = notDetermined
                                }
                            }

                            break;
                        }
                        case 6: {
                            // can be 0,9,6
                            // check if already determined 1 is not part of pattern, so we found the 6
                            if (!(notDetermined.contains(signalsForDigit[1][0]) && notDetermined.contains(signalsForDigit[1][1]))) {
                                signalsForDigit[6] = notDetermined
                            } else
                            // if 3 is already determined, we can determine 9
                            if (signalsForDigit[3]
                                    && notDetermined.contains(signalsForDigit[3][0])
                                    && notDetermined.contains(signalsForDigit[3][1])
                                    && notDetermined.contains(signalsForDigit[3][2])
                                    && notDetermined.contains(signalsForDigit[3][3])
                                    && notDetermined.contains(signalsForDigit[3][4])
                            ) {
                                signalsForDigit[9] = notDetermined
                            } else
                            // if 9 and 6 is already determined, the last 6 signal digit is 0
                            if (signalsForDigit[6] && signalsForDigit[9]) {
                                signalsForDigit[0] = notDetermined
                            }
                        }
                    }
                }
                // remove all determined signals from unknown signals
                notDeterminedSignals.removeAll(signalsForDigit.values())
                println "determined signals: $signalsForDigit"
                println "not determined signals: $notDeterminedSignals"
            }


            String tempValue = ""
            fourDigitOutputValue.trim().split(" ").collect {it.split("").sort().join()}.each {String outputPart ->
                println "Search for $outputPart"
                tempValue += signalsForDigit.find {it.value == outputPart}.key
            }
            println "Decoded Output Signal $tempValue"
            sum += tempValue as Integer


        }

        return sum
    }