From 92b65f0fa4e5261f5c37027c93164fa5151a4aa0 Mon Sep 17 00:00:00 2001 From: Christopher Bohn <bohn@unl.edu> Date: Thu, 1 Dec 2022 08:28:03 -0600 Subject: [PATCH] Finished Year 2022 Day 1 --- 2022/README.md | 31 +++++++ .../java/edu/unl/cse/bohn/ImportData.java | 92 +------------------ 2022/src/main/java/edu/unl/cse/bohn/Main.java | 50 +--------- .../main/java/edu/unl/cse/bohn/Puzzle.java | 29 +----- .../java/edu/unl/cse/bohn/year2022/Day1.java | 87 ++++++++++++++++++ README.md | 2 +- scaffolding/ImportData.java | 2 +- 7 files changed, 123 insertions(+), 170 deletions(-) mode change 100644 => 120000 2022/src/main/java/edu/unl/cse/bohn/ImportData.java mode change 100644 => 120000 2022/src/main/java/edu/unl/cse/bohn/Main.java mode change 100644 => 120000 2022/src/main/java/edu/unl/cse/bohn/Puzzle.java create mode 100644 2022/src/main/java/edu/unl/cse/bohn/year2022/Day1.java diff --git a/2022/README.md b/2022/README.md index 03da0a2..5f1626b 100644 --- a/2022/README.md +++ b/2022/README.md @@ -2,3 +2,34 @@ ## Day 1 +- [The problem](https://adventofcode.com/2022/day/1) +- [The solution](src/main/java/edu/unl/cse/bohn/year2022/Day1.java) + +I like the first few days' problems -- they're simple enough that they make great computational thinking examples for my 100-level students. +Breaking the problem down into subproblems: +- Keep track of how many calories an elf is carrying +- Detect when we've reach the end of the list of items that elf is carrying +- Convert strings to integers +- Of the elves considered so far, keep track of the number of calories being carried by the elf who is carrying the most calories (or the three elves who are carrying the most calories) +- Update that count when we've finished with an elf + - For part 1, we must determine whether the most-recent elf is carrying more calories than the maximum among the previous elves + - For part 2, we must determine whether the most-recent elf is carrying more calories than any of the previous top-three +- Produce the result + - For part 1, the result is the maximum number of calories being carried by any one elf + - For part 2, the result is the sum of the number of calories being carried by the top three + +While I wouldn't expect my students to be able to provide solutions in quite so few lines as I did -- we don't teach the Java streams API in CS1 -- they could definitely do this problem. +The only wrinkle is that a blank line isn't the *only* way to reach the end of an elf's list. + +### Refactoring opportunity + +Parts 1 & 2 differ only in how many "maximum" elves we're tracking and, consequently, how we perform our update. +My part 2 solution used a set that can never grow to more than 3 items because we're tracking the top three elves. +Part 1 can be thought of as a degenerate case of tracking the top "one" elf; +this suggests that it can be solved with a set that can never grow to more than 1 item. +Which means we can use the same solution for both parts, parameterized by the top "number" of elves, which becomes the upper limit of the size of the set. + +## Day 2 + +(coming soon) + diff --git a/2022/src/main/java/edu/unl/cse/bohn/ImportData.java b/2022/src/main/java/edu/unl/cse/bohn/ImportData.java deleted file mode 100644 index e472acd..0000000 --- a/2022/src/main/java/edu/unl/cse/bohn/ImportData.java +++ /dev/null @@ -1,91 +0,0 @@ -package edu.unl.cse.bohn; - -import org.json.simple.JSONObject; -import org.json.simple.parser.JSONParser; -import org.json.simple.parser.ParseException; - -import java.io.BufferedReader; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URLConnection; -import java.util.LinkedList; -import java.util.List; -import java.util.Objects; - -public class ImportData { - - public static final String FILENAME = "apikeys.json"; - - protected final String protocol; - protected final String host; - protected final String path; - protected final String apiKey; - - @SuppressWarnings("unused") - public static List<String> readFile(String filename) throws IOException { - List<String> data = new LinkedList<>(); - BufferedReader bufferedReader; - bufferedReader = new BufferedReader(new FileReader(filename)); - while (bufferedReader.ready()) { - data.add(bufferedReader.readLine()); - } - return data; - } - - public ImportData(String apiKeyName, int year, int day) { - String apiKey; - protocol = "https"; - host = "adventofcode.com"; - path = "/" + year + "/day/" + day + "/input"; - try { - apiKey = getApiKey(apiKeyName); - } catch (IOException ioException) { - System.err.println("Could not retrieve API key: " + ioException.getMessage()); - apiKey = null; - } - this.apiKey = apiKey; - } - - protected String getApiKey(String apiKeyName) throws IOException { - JSONObject apiKeyJson; - try (InputStreamReader inputStreamReader = new InputStreamReader( - Objects.requireNonNull(ImportData.class.getClassLoader().getResourceAsStream(FILENAME)))) { - apiKeyJson = (JSONObject)new JSONParser().parse(inputStreamReader); - } catch (NullPointerException nullPointerException) { - FileNotFoundException newException = new FileNotFoundException("File " + FILENAME + " not found."); - newException.initCause(nullPointerException); - throw newException; - } catch (ParseException parseException) { - throw new IOException("Error while parsing file " + FILENAME + ".", parseException); - } - if (!apiKeyJson.containsKey(apiKeyName)) { - System.err.println("WARNING! Could not locate API key named " + apiKeyName + " in file " + FILENAME + "."); - } - String apiKey = apiKeyJson.get(apiKeyName).toString(); - if (apiKey.equals("")) { - System.err.println("WARNING! API key named " + apiKeyName + " in file " + FILENAME + " is blank."); - } - return apiKey; - } - - public List<String> importData() throws IOException { - List<String> data = new LinkedList<>(); - BufferedReader bufferedReader; - try { - URLConnection connection = new URI(protocol, host, path, null).toURL().openConnection(); - connection.setRequestProperty("Cookie", apiKey); - bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream())); - } catch (URISyntaxException | MalformedURLException originalException) { - throw new IOException("Could not retrieve usable data from " + host + ".", originalException); - } - while (bufferedReader.ready()) { - data.add(bufferedReader.readLine()); - } - return data; - } -} diff --git a/2022/src/main/java/edu/unl/cse/bohn/ImportData.java b/2022/src/main/java/edu/unl/cse/bohn/ImportData.java new file mode 120000 index 0000000..c9636cf --- /dev/null +++ b/2022/src/main/java/edu/unl/cse/bohn/ImportData.java @@ -0,0 +1 @@ +../../../../../../../../scaffolding/ImportData.java \ No newline at end of file diff --git a/2022/src/main/java/edu/unl/cse/bohn/Main.java b/2022/src/main/java/edu/unl/cse/bohn/Main.java deleted file mode 100644 index 875e4b5..0000000 --- a/2022/src/main/java/edu/unl/cse/bohn/Main.java +++ /dev/null @@ -1,49 +0,0 @@ -package edu.unl.cse.bohn; - -import java.lang.reflect.InvocationTargetException; -import java.util.Calendar; -import java.util.Scanner; - -public class Main { - public static final String defaultApiKey = "aoc"; - - private static String getUserInput(String prompt, String defaultValue, Scanner scanner) { - System.out.print(prompt + " [" + defaultValue + "]: "); - String userInput = scanner.nextLine(); - return userInput.equals("") ? defaultValue : userInput; - } - - public static void main(String... arguments) { - Scanner scanner = new Scanner(System.in); - String apiKey = getUserInput("Enter API key", defaultApiKey, scanner); - Calendar calendar = Calendar.getInstance(); - int year = Integer.parseInt(getUserInput("Enter puzzle year", - Integer.toString(calendar.get(Calendar.YEAR)), scanner)); - int day = Integer.parseInt(getUserInput("Enter puzzle day", - Integer.toString(calendar.get(Calendar.DAY_OF_MONTH)), scanner)); - boolean useProductionData = getUserInput("Use sample data (Y/N)?", "Y", scanner).toUpperCase().charAt(0) != 'Y'; - scanner.close(); - ImportData dataSource = new ImportData(apiKey, year, day); - String className = Main.class.getPackageName() + ".year" + year + ".Day" + day; - Puzzle puzzle = null; - try { - puzzle = (Puzzle)Class.forName(className).getConstructors()[0].newInstance(useProductionData); - } catch (ClassNotFoundException classNotFoundException) { - System.err.println("Could not find class " + className); - System.exit(1); - } catch (InstantiationException ignored) { - System.err.println(className + " is an abstract class."); - System.exit(1); - } catch (IllegalAccessException ignored) { - System.err.println("The requested constructor for " + className + " is inaccessible."); - System.exit(1); - } catch (InvocationTargetException invocationTargetException) { - System.err.println("The constructor for " + className + " threw an exception."); - System.err.println(invocationTargetException.getMessage()); - Throwable originalException = invocationTargetException.getCause(); - System.err.println("Caused by: " + originalException); - System.exit(1); - } - puzzle.solvePuzzle(dataSource); - } -} diff --git a/2022/src/main/java/edu/unl/cse/bohn/Main.java b/2022/src/main/java/edu/unl/cse/bohn/Main.java new file mode 120000 index 0000000..5982251 --- /dev/null +++ b/2022/src/main/java/edu/unl/cse/bohn/Main.java @@ -0,0 +1 @@ +../../../../../../../../scaffolding/Main.java \ No newline at end of file diff --git a/2022/src/main/java/edu/unl/cse/bohn/Puzzle.java b/2022/src/main/java/edu/unl/cse/bohn/Puzzle.java deleted file mode 100644 index 100704c..0000000 --- a/2022/src/main/java/edu/unl/cse/bohn/Puzzle.java +++ /dev/null @@ -1,28 +0,0 @@ -package edu.unl.cse.bohn; - -import java.io.IOException; -import java.util.List; - -public abstract class Puzzle { - protected String sampleData = ""; - protected boolean isProductionReady; - - public Puzzle(boolean isProductionReady) { - this.isProductionReady = isProductionReady; - } - - public abstract long computePart1(List<String> data); - public abstract long computePart2(List<String> data); - - public void solvePuzzle(ImportData dataSource) { - List<String> data = null; - try { - data = isProductionReady ? dataSource.importData() : List.of(sampleData.split(System.lineSeparator())); - } catch (IOException ioException) { - System.err.println("Could not retrieve data: " + ioException); - System.exit(1); - } - System.out.println("Part 1: " + computePart1(data)); - System.out.println("Part 2: " + computePart2(data)); - } -} diff --git a/2022/src/main/java/edu/unl/cse/bohn/Puzzle.java b/2022/src/main/java/edu/unl/cse/bohn/Puzzle.java new file mode 120000 index 0000000..1350b61 --- /dev/null +++ b/2022/src/main/java/edu/unl/cse/bohn/Puzzle.java @@ -0,0 +1 @@ +../../../../../../../../scaffolding/Puzzle.java \ No newline at end of file diff --git a/2022/src/main/java/edu/unl/cse/bohn/year2022/Day1.java b/2022/src/main/java/edu/unl/cse/bohn/year2022/Day1.java new file mode 100644 index 0000000..ab8431d --- /dev/null +++ b/2022/src/main/java/edu/unl/cse/bohn/year2022/Day1.java @@ -0,0 +1,87 @@ +package edu.unl.cse.bohn.year2022; + +import edu.unl.cse.bohn.Puzzle; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@SuppressWarnings({"unused", "CommentedOutCode"}) +public class Day1 extends Puzzle { + public Day1(boolean isProductionReady) { + super(isProductionReady); + sampleData = """ + 1000 + 2000 + 3000 + + 4000 + + 5000 + 6000 + + 7000 + 8000 + 9000 + + 10000"""; + } + +/* + @Override + public long computePart1(List<String> data) { + long maximumCalorieCount = Long.MIN_VALUE; + long currentCalorieCount = 0; + for (String datum: data) { + Long calories = datum.equals("") ? null : Long.valueOf(datum); + if (calories == null) { + maximumCalorieCount = Long.max(maximumCalorieCount, currentCalorieCount); + currentCalorieCount = 0; // make it ready for the next elf + } else { + currentCalorieCount += calories; + } + } + // we still need to compare the last elf's calorie count, because they didn't have a blank line following their last food item + maximumCalorieCount = Long.max(maximumCalorieCount, currentCalorieCount); + return maximumCalorieCount; + } +*/ + + @Override + public long computePart1(List<String> data) { + return getSumOfMaximumCalorieCounts(data, 1); + } + + @Override + public long computePart2(List<String> data) { + return getSumOfMaximumCalorieCounts(data, 3); + } + + private static Long getSumOfMaximumCalorieCounts(List<String> data, int numberOfMaximumCalorieCounts) { + Set<Long> maximumCalorieCounts = new HashSet<>(); + long currentCalorieCount = 0; + for (String datum: data) { + Long calories = datum.equals("") ? null : Long.valueOf(datum); + if (calories == null) { + updateMaximumCounts(maximumCalorieCounts, currentCalorieCount, numberOfMaximumCalorieCounts); + currentCalorieCount = 0; + } else { + currentCalorieCount += calories; + } + } + updateMaximumCounts(maximumCalorieCounts, currentCalorieCount, numberOfMaximumCalorieCounts); + return maximumCalorieCounts.stream().reduce(0L, Long::sum); + } + + private static void updateMaximumCounts(Set<Long> maximumCalorieCounts, + long currentCalorieCount, + int numberOfMaximumCalorieCounts) { + long calorieCountToAdd = currentCalorieCount; + if (maximumCalorieCounts.size() > numberOfMaximumCalorieCounts - 1) { + long thirdMaximumCalorieCount = maximumCalorieCounts.stream().min(Long::compareTo).orElseThrow(); + maximumCalorieCounts.remove(thirdMaximumCalorieCount); + calorieCountToAdd = Long.max(thirdMaximumCalorieCount, calorieCountToAdd); + } + maximumCalorieCounts.add(calorieCountToAdd); + } +} diff --git a/README.md b/README.md index 1b9a1d6..d5c6768 100644 --- a/README.md +++ b/README.md @@ -2,5 +2,5 @@ My solutions to the [Advent of Code](https://adventofcode.com/) puzzles. -I'm making [notes](2021/README.md) about the 2021 puzzles and my solutions. +I'm making [notes](2022/README.md) about the 2022 puzzles and my solutions. diff --git a/scaffolding/ImportData.java b/scaffolding/ImportData.java index e472acd..c6459ae 100644 --- a/scaffolding/ImportData.java +++ b/scaffolding/ImportData.java @@ -78,7 +78,7 @@ public class ImportData { BufferedReader bufferedReader; try { URLConnection connection = new URI(protocol, host, path, null).toURL().openConnection(); - connection.setRequestProperty("Cookie", apiKey); + connection.setRequestProperty("Cookie", "session=" + apiKey); bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream())); } catch (URISyntaxException | MalformedURLException originalException) { throw new IOException("Could not retrieve usable data from " + host + ".", originalException); -- GitLab