Skip to content

Commit

Permalink
Solution for 2023, Day 4
Browse files Browse the repository at this point in the history
  • Loading branch information
zodac committed Dec 4, 2023
1 parent 78346d8 commit 7926d77
Show file tree
Hide file tree
Showing 9 changed files with 278 additions and 5 deletions.
82 changes: 82 additions & 0 deletions 2023/src/main/java/me/zodac/advent/Day04.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* BSD Zero Clause License
*
* Copyright (c) 2021-2023 zodac.me
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
* IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

package me.zodac.advent;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import me.zodac.advent.pojo.ScratchCard;

/**
* Solution for 2023, Day 4.
*
* @see <a href="https://adventofcode.com/2023/day/4">[2023: 04] Scratchcards</a>
*/
public final class Day04 {

private static final int DEFAULT_NUMBER_OF_COPIES = 0;

private Day04() {

}

/**
* Given a {@link Collection} of {@link ScratchCard}s, we calculate the points for each {@link ScratchCard}, then sum them.
*
* @param scratchCards the input {@link ScratchCard}s
* @return the sum of points for all {@link ScratchCard}s
*/
public static long totalPointsForScratchCards(final Collection<ScratchCard> scratchCards) {
return scratchCards
.stream()
.mapToLong(ScratchCard::calculatePoints)
.sum();
}

/**
* Given a {@link Collection} of {@link ScratchCard}s, each winning digit in a {@link ScratchCard} provides a copy of the next <i>n</i>
* {@link ScratchCard}s. The number of copies (and original {@link ScratchCard}s) are added up and returned.
*
* @param scratchCards the input {@link ScratchCard}s
* @return the total number of {@link ScratchCard}s, including copies
*/
public static long countTotalNumberOfScratchCards(final Collection<ScratchCard> scratchCards) {
final Map<Integer, Integer> copiesById = new HashMap<>();
long numberOfCopies = 0L;

for (final ScratchCard scratchCard : scratchCards) {
final int id = scratchCard.id();
final int numberOfTimesToExecute = copiesById.getOrDefault(id, DEFAULT_NUMBER_OF_COPIES) + 1;

final int currentCopiesForId = copiesById.getOrDefault(id, DEFAULT_NUMBER_OF_COPIES);
copiesById.put(id, currentCopiesForId + 1);

if (scratchCard.winners().isEmpty()) {
continue;
}

for (int i = 1; i <= scratchCard.winners().size(); i++) {
numberOfCopies += numberOfTimesToExecute;
final int currentCopiesForNextId = copiesById.getOrDefault(id + i, DEFAULT_NUMBER_OF_COPIES);
copiesById.put(id + i, currentCopiesForNextId + numberOfTimesToExecute);
}
}

return scratchCards.size() + numberOfCopies;
}
}
75 changes: 75 additions & 0 deletions 2023/src/main/java/me/zodac/advent/pojo/ScratchCard.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* BSD Zero Clause License
*
* Copyright (c) 2021-2023 zodac.me
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
* IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

package me.zodac.advent.pojo;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import me.zodac.advent.util.CollectionUtils;
import me.zodac.advent.util.StringUtils;

/**
* Record defining a scratchcard. The scratchcard will contain valid winning digits and the drawn digits, and we will look for any drawn winners.
*
* @param id the ID of the {@link ScratchCard}
* @param winners the drawn digits that were winners
*/
public record ScratchCard(int id, Set<Integer> winners) {

private static final Pattern VALID_SCRATCHCARD = Pattern.compile("Card\\s+(?<id>\\d+):\\s+(?<winners>(\\d+\\s+)*)\\|(?<drawn>(\\s+\\d+)*)");

/**
* Creates a {@link ScratchCard} from a {@link CharSequence} in the format:
* <pre>
* Card [id]: [winning digits, space separated] | [drawn digits, space separated]
* </pre>
*
* @param input the {@link CharSequence} to parse
* @return the {@link ScratchCard}
* @throws IllegalArgumentException thrown if the input does not match the expected format
*/
public static ScratchCard parse(final CharSequence input) {
final Matcher matcher = VALID_SCRATCHCARD.matcher(input);
if (!matcher.matches()) {
throw new IllegalArgumentException(String.format("Invalid input: '%s'", input));
}

final int id = Integer.parseInt(matcher.group("id"));
final List<Integer> winningDigits = StringUtils.collectIntegersInOrder(matcher.group("winners"));
final List<Integer> drawnDigits = StringUtils.collectIntegersInOrder(matcher.group("drawn"));
return new ScratchCard(id, CollectionUtils.intersection(new HashSet<>(winningDigits), drawnDigits));
}

/**
* Calculates the points for the {@link ScratchCard}. The first winning digit for the {@link ScratchCard} is worth <b>1</b> point, and the points
* double for each subsequent winning digit. This can be simplified to:
* <pre>
* 2 ^ (numberOfWinners - 1)
* </pre>
*
* <p>
* If there were no winning digits, <b>0</b> is returned.
*
* @return the points for the {@link ScratchCard}
*/
public long calculatePoints() {
return winners.isEmpty() ? 0L : (long) StrictMath.pow(2, winners().size() - 1);
}
}
74 changes: 74 additions & 0 deletions 2023/src/test/java/me/zodac/advent/Day04Test.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* BSD Zero Clause License
*
* Copyright (c) 2021-2023 zodac.me
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
* IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

package me.zodac.advent;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.List;
import me.zodac.advent.input.ExampleInput;
import me.zodac.advent.input.PuzzleInput;
import me.zodac.advent.pojo.ScratchCard;
import org.junit.jupiter.api.Test;

/**
* Tests to verify answers for {@link Day04}.
*/
public class Day04Test {

private static final String INPUT_FILENAME = "day04.txt";

@Test
void example() {
final List<ScratchCard> values = ExampleInput.readLines(INPUT_FILENAME)
.stream()
.map(ScratchCard::parse)
.toList();

final long part1Result = Day04.totalPointsForScratchCards(values);
assertThat(part1Result)
.isEqualTo(13L);

final long part2Result = Day04.countTotalNumberOfScratchCards(values);
assertThat(part2Result)
.isEqualTo(30L);
}

@Test
void part1() {
final List<ScratchCard> values = PuzzleInput.readLines(INPUT_FILENAME)
.stream()
.map(ScratchCard::parse)
.toList();

final long part1Result = Day04.totalPointsForScratchCards(values);
assertThat(part1Result)
.isEqualTo(18_519L);
}

@Test
void part2() {
final List<ScratchCard> values = PuzzleInput.readLines(INPUT_FILENAME)
.stream()
.map(ScratchCard::parse)
.toList();

final long part2Result = Day04.countTotalNumberOfScratchCards(values);
assertThat(part2Result)
.isEqualTo(11_787_590L);
}
}
6 changes: 6 additions & 0 deletions 2023/src/test/resources/day04.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53
Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19
Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1
Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83
Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36
Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Advent Of Code: Java Edition

![](https://img.shields.io/badge/2023%20⭐-6-red)
![](https://img.shields.io/badge/2023%20⭐-8-orange)
![](https://img.shields.io/badge/2022%20⭐-28-yellow)
![](https://img.shields.io/badge/2021%20⭐-19-orange)
![](https://img.shields.io/badge/2020%20⭐-0-red)
Expand Down Expand Up @@ -50,14 +50,14 @@ The source code is released under the [BSD Zero Clause License](https://opensour
## Progress And Ranks

<details>
<summary>2022 Results</summary>
<summary>2023 Results</summary>

| Day | Part 1 | Part 2 |
|:---------------------------------------------|--------:|--------:|
| [Day 1](https://adventofcode.com/2023/day/1) | 4,216 ⭐ | 6,251 ⭐ |
| [Day 2](https://adventofcode.com/2023/day/2) | 697 ⭐ | 555 ⭐ |
| [Day 3](https://adventofcode.com/2023/day/3) | 7,815 ⭐ | 5,683 ⭐ |

| [Day 4](https://adventofcode.com/2023/day/4) | 1,382 ⭐ | 4,192 ⭐ |

</details>

Expand Down
2 changes: 1 addition & 1 deletion advent-of-code-inputs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;

/**
Expand Down Expand Up @@ -200,4 +202,18 @@ public static <T> List<List<T>> groupBySize(final Collection<? extends T> values

return groups;
}

/**
* Returns the intersection of a {@link Set} and a {@link Collection}s - the common elements in both.
*
* @param first the first {@link Set}
* @param second the second {@link Collection}
* @param <E> the type of the {@link Set} and {@link Collection}
* @return the common elements between both {@link Set} and {@link Collection}
*/
public static <E> Set<E> intersection(final Set<? extends E> first, final Collection<E> second) {
final Set<E> intersection = new HashSet<>(first);
intersection.retainAll(second);
return intersection;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
Expand Down Expand Up @@ -169,4 +170,23 @@ void testGroupBySize_givenInvalidInputs(final int amountPerGroup, final String e
.isInstanceOf(IllegalArgumentException.class)
.hasMessage(errorMessage);
}

@ParameterizedTest
@MethodSource("provideForIntersection")
void testIntersection(final Set<String> first, final Set<String> second, final Set<String> expected) {
final Set<String> output = CollectionUtils.intersection(first, second);
assertThat(output)
.hasSameElementsAs(expected);
}

private static Stream<Arguments> provideForIntersection() {
return Stream.of(
Arguments.of(Set.of("a", "b", "c"), Set.of("a", "b", "c"), Set.of("a", "b", "c")), // Both inputs match
Arguments.of(Set.of("a", "b", "c"), Set.of("b", "c", "a"), Set.of("a", "b", "c")), // Order insensitive
Arguments.of(Set.of("a", "b", "c"), Set.of("c", "d", "e", "f"), Set.of("c")), // Single match
Arguments.of(Set.of("a", "b", "c"), Set.of("a", "b", "c", "d", "e", "f"), Set.of("a", "b", "c")), // Multiple matches
Arguments.of(Set.of("a", "b", "c"), Set.of("a", "b"), Set.of("a", "b")), // Second is a subset of first
Arguments.of(Set.of("a", "b", "c"), Set.of("d", "e", "f"), Set.of()) // No match
);
}
}
2 changes: 1 addition & 1 deletion create.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ else
cd ./advent-of-code-inputs || exit 1
git add "${year}/day${day_long}.txt"
git commit -m "Adding input for ${year}, Day ${day}"
git push
git push --quiet
cd .. || exit 1
fi

Expand Down

0 comments on commit 7926d77

Please sign in to comment.