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.
Done with TDD in Groovy.
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)}"
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)}"
Navigate never without a test case. Doit with TDD and groovy.
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)}"
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)}"
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())}"
}
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"]))
}
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"]
}
Run the calculation with our puzzle input.
static void main(String[] args) {
println "Day3 life support rating: ${calculateLifeSupportRating(new File("input.txt").readLines())}"
}
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
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
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
}
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
}
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
It is better to complete all test of the helper classes and simplify the code afterwards.
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()
}
}
How does it work:
The only difference between the first star and this star, is that we process also diagonal lines.
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
For performance fallback to simple data types is a good idea ;)
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.
this documentation is autogenerated. Add a README.adoc
to your solution to take over the control of this :-)
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)
}
}
16,1,2,0,4,2,7,1,2,14
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
}
}
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
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
Start with the solution on a sheet of paper ;)
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
}
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
}