Skip to content

Commit

Permalink
Solution for 2018, Day 02
Browse files Browse the repository at this point in the history
  • Loading branch information
zodac committed Oct 6, 2023
1 parent dd59bb6 commit 231a64b
Show file tree
Hide file tree
Showing 8 changed files with 348 additions and 4 deletions.
128 changes: 128 additions & 0 deletions 2018/src/main/java/me/zodac/advent/Day02.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* 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.HashSet;
import java.util.List;
import me.zodac.advent.util.StringUtils;

/**
* Solution for 2018, Day 2.
*
* @see <a href="https://adventofcode.com/2018/day/2">AoC 2018, Day 2</a>
*/
public final class Day02 {

private Day02() {

}

/**
* Given some box IDs in the form of {@link String}s, we calculate the checksum by counting the number of box IDs with:
* <ul>
* <li>A character occurring exactly <b>2</b> times</li>
* <li>A character occurring exactly <b>3</b> times</li>
* </ul>
*
* <p>
* These counts are then multiplied to return the checksum of the provided box IDs.
*
* @param boxIds the {@link String}s of the box IDs
* @return the checksum of the box IDs
*/
public static long checksumOfBoxIds(final Iterable<String> boxIds) {
int numberOfDoubleCharacters = 0;
int numberOfTripleCharacters = 0;

for (final String boxId : boxIds) {
if (hasAtLeastOneCharacterThatOccursExactlyTwoTimes(boxId)) {
numberOfDoubleCharacters++;
}

if (hasAtLeastOneCharacterThatOccursExactlyThreeTimes(boxId)) {
numberOfTripleCharacters++;
}
}

return (long) numberOfDoubleCharacters * numberOfTripleCharacters;
}

/**
* Given a {@link List} of box IDs in the form of {@link String}s, we know there will be one pair different by only <b>1</b> character. We find
* this pair of box IDs, remove the different character, then return the common letters for the pair.
*
* @param boxIds the {@link String}s of the box IDs
* @return the common characters of the valid box ID pair
* @throws IllegalStateException thrown if a valid pair of box IDs could not be found
*/
public static String findCommonCharactersForValidBoxIds(final List<String> boxIds) {
final int expectedLengthAfterDifferencesRemoved = boxIds.getFirst().length() - 1;

for (int i = 0; i < boxIds.size(); i++) {
final String currentBoxId = boxIds.get(i);

for (int j = i + 1; j < boxIds.size(); j++) {
final String nextBoxId = boxIds.get(j);

final String differentCharactersRemoved = StringUtils.removeDifferentCharacters(currentBoxId, nextBoxId);
if (differentCharactersRemoved.length() == expectedLengthAfterDifferencesRemoved) {
return differentCharactersRemoved;
}
}
}

throw new IllegalStateException("Could not find a valid pair of box IDs");
}

private static boolean hasAtLeastOneCharacterThatOccursExactlyTwoTimes(final CharSequence input) {
return hasAtLeastOneCharacterThatOccursExactly(input, 2);
}

private static boolean hasAtLeastOneCharacterThatOccursExactlyThreeTimes(final CharSequence input) {
return hasAtLeastOneCharacterThatOccursExactly(input, 3);
}

private static boolean hasAtLeastOneCharacterThatOccursExactly(final CharSequence input, final int times) {
final int stringLength = input.length();
final Collection<Character> checkedCharacters = new HashSet<>();

for (int i = 0; i < stringLength - 1; i++) {
final char currentChar = input.charAt(i);
int count = 1; // Start count at 1 as we count the current occurance

if (checkedCharacters.contains(currentChar)) {
continue;
}

for (int j = i + 1; j < stringLength; j++) {
if (input.charAt(j) == currentChar) {
count++;
}
}

if (count == times) {
return true;
}

checkedCharacters.add(currentChar);
}

return false;
}
}
67 changes: 67 additions & 0 deletions 2018/src/test/java/me/zodac/advent/Day02Test.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* 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 org.junit.jupiter.api.Test;

/**
* Tests to verify answers for {@link Day02}.
*/
public class Day02Test {

private static final String INPUT_FILENAME = "day02.txt";
private static final String INPUT_FILENAME_PART_2 = "day02_2.txt";

@Test
void example() {
final List<String> values = ExampleInput.readLines(INPUT_FILENAME);

final long checksumOfBoxIds = Day02.checksumOfBoxIds(values);
assertThat(checksumOfBoxIds)
.isEqualTo(12L);

final List<String> valuesPart2 = ExampleInput.readLines(INPUT_FILENAME_PART_2);

final String commonLetters = Day02.findCommonCharactersForValidBoxIds(valuesPart2);
assertThat(commonLetters)
.isEqualTo("fgij");
}

@Test
void part1() {
final List<String> values = PuzzleInput.readLines(INPUT_FILENAME);

final long checksumOfBoxIds = Day02.checksumOfBoxIds(values);
assertThat(checksumOfBoxIds)
.isEqualTo(5_880L);
}

@Test
void part2() {
final List<String> values = PuzzleInput.readLines(INPUT_FILENAME);

final String commonLetters = Day02.findCommonCharactersForValidBoxIds(values);
assertThat(commonLetters)
.isEqualTo("tiwcdpbseqhxryfmgkvjujvza");
}
}
7 changes: 7 additions & 0 deletions 2018/src/test/resources/day02.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
abcdef
bababc
abbcde
abcccd
aabcdd
abcdee
ababab
7 changes: 7 additions & 0 deletions 2018/src/test/resources/day02_2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
abcde
fghij
klmno
pqrst
fguij
axcye
wvxyz
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
![](https://img.shields.io/badge/2021%20⭐-19-orange)
![](https://img.shields.io/badge/2020%20⭐-0-red)
![](https://img.shields.io/badge/2019%20⭐-0-red)
![](https://img.shields.io/badge/2018%20⭐-2-orange)
![](https://img.shields.io/badge/2018%20⭐-4-orange)
![](https://img.shields.io/badge/2017%20⭐-0-red)
![](https://img.shields.io/badge/2016%20⭐-0-red)
![](https://img.shields.io/badge/2015%20⭐-50-green)
Expand Down Expand Up @@ -176,7 +176,7 @@ The source code is released under the [BSD Zero Clause License](https://opensour
| Day | Part 1 | Part 2 |
|:-----------------------------------------------|---------:|---------:|
| [Day 1](https://adventofcode.com/2018/day/1) | 27,792 ⭐ | 22,808 ⭐ |
| [Day 2](https://adventofcode.com/2018/day/2) | | |
| [Day 2](https://adventofcode.com/2018/day/2) | 66,502 ⭐ | 59,417 ⭐ |
| [Day 3](https://adventofcode.com/2018/day/3) | | |
| [Day 4](https://adventofcode.com/2018/day/4) | | |
| [Day 5](https://adventofcode.com/2018/day/5) | | |
Expand Down
2 changes: 1 addition & 1 deletion advent-of-code-inputs
56 changes: 55 additions & 1 deletion common-utils/src/main/java/me/zodac/advent/util/StringUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -352,9 +352,54 @@ public static String lookAndSay(final String input) {
return output.toString();
}

/**
* Iterates through both input {@link String}s index by index, and if the character at any index is different, that character is removed.
*
* <p>
* For example, given {@code foobar} and {@code fuubar}, the returned value will be:
* <pre>
* {@code fbar}
* </pre>
*
* @param first the first {@link String}
* @param second the second {@link String}
* @return the {@link String} with any differences removed
* @throws IllegalArgumentException thrown if the two input {@link String}s do not have the same length, or if either input is {@code null}
*/
public static String removeDifferentCharacters(final String first, final String second) {
if (first == null || second == null) {
throw new IllegalArgumentException("Inputs must not be null");
}

if (first.length() != second.length()) {
throw new IllegalArgumentException(
String.format("Expected inputs of equal length, found %s (%s) and %s (%s)", first, first.length(), second, second.length()));
}

final int stringLength = first.length();

final StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < stringLength; i++) {
final char charFromFirst = first.charAt(i);
final char charFromSecond = second.charAt(i);

if (charFromFirst == charFromSecond) {
stringBuilder.append(charFromFirst);
}
}

return stringBuilder.toString();
}

/**
* Removes the last character in the {@link String}.
*
* <p>
* For example, given {@code foobar}, the returned value will be:
* <pre>
* {@code fooba}
* </pre>
*
* @param input the input {@link String}
* @return the updated {@link String}
*/
Expand All @@ -365,6 +410,12 @@ public static String removeLastCharacter(final String input) {
/**
* Removes the last {@code numberOfCharactersToRemove} characters in the {@link String}.
*
* <p>
* For example, given {@code foobar} and {@code numberOfCharactersToRemove} set to <b>3</b>, the returned value will be:
* <pre>
* {@code foo}
* </pre>
*
* @param input the input {@link String}
* @param numberOfCharactersToRemove the number of characters to remove
* @return the updated {@link String}
Expand Down Expand Up @@ -421,7 +472,10 @@ public static String replaceAtIndex(final String input, final CharSequence subSt
* Sorts the individual characters in the given {@link String} alphabetically.
*
* <p>
* For example, {@code foobar} will be returned as {@code abfoor}
* For example, given {@code foobar}, the returned value will be:
* <pre>
* {@code abfoor}
* </pre>
*
* @param input the {@link String} to sort
* @return the sorted {@link String}
Expand Down
Loading

0 comments on commit 231a64b

Please sign in to comment.