This repository was archived by the owner on Dec 19, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
AOC-024: Day 7: The Sum of Its Parts #71
Open
jpgoelz
wants to merge
18
commits into
master
Choose a base branch
from
AOC-024
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
8a948b4
AOC-024: Basic setup.
jpgoelz 6cb16b2
AOC-024: Step class and tests
jpgoelz 43eabf9
AOC-024: Changed String ids to Character. Finished Day 7 Part 1.
jpgoelz c773d80
AOC-024: Refactored, so that result for Day 7 Part 1 can be requested…
jpgoelz 98e1de8
AOC-024: Refactored, so that result for Day 7 Part 1 can be requested…
jpgoelz 1726356
Merge remote-tracking branch 'origin/AOC-024' into AOC-024
jpgoelz fa438b7
AOC-024: Refactored, so there are no duplicates in `availableSteps`
jpgoelz 0fa6cac
AOC-024: initial setup for Part2
jpgoelz eaf668e
AOC-024: Finished Day7, Part 2.
jpgoelz fa3398a
AOC-024: refactored to use `List` instead of `Map`.
jpgoelz 88635a2
Merge branch 'master' into AOC-024
jpgoelz f93e7cf
AOC-024: Updated to current folder structures.
jpgoelz de93b99
final
jpgoelz 63de375
formatting
jpgoelz 5c5552c
final; formatting
jpgoelz a6aae67
use list instead of array list
jpgoelz c0e592a
finals
jpgoelz b273476
use foreach, which is faster
jpgoelz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
226 changes: 226 additions & 0 deletions
226
src/main/java/org/basseur/adventofcode/advent2018/days/day07/Day07.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,226 @@ | ||
| package org.basseur.adventofcode.advent2018.days.day07; | ||
|
|
||
| import org.basseur.adventofcode.advent2018.ProblemStatusEnum; | ||
| import org.basseur.adventofcode.advent2018.days.Days; | ||
| import org.basseur.adventofcode.advent2018.utils.FileReaders; | ||
| import org.springframework.beans.factory.annotation.Autowired; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.util.*; | ||
| import java.util.regex.Matcher; | ||
| import java.util.regex.Pattern; | ||
|
|
||
| /** | ||
| * Implementation for <i>Day 7: The Sum of Its Parts</i>. | ||
| * | ||
| * @author Jan Philipp Gölz | ||
| */ | ||
| @Component | ||
| public class Day07 implements Days { | ||
|
|
||
| /** The location of the puzzle input file */ | ||
| private static final String FILE_LOCATION = "/puzzleInputs/Input07.txt"; | ||
|
|
||
| /** | ||
| * The difference between the integer value of a char and seconds to finish a step, e.g. `(int)char 'A' = 65`, but A | ||
| * takes 1 extra second. | ||
| */ | ||
| private static final int DIFFERENCE_BETWEEN_CHAR_AND_SECONDS = 64; | ||
|
|
||
| /** The puzzle status {@code HashMap} */ | ||
| private final HashMap<String, ProblemStatusEnum> problemStatus; | ||
|
|
||
| /** A list containing the instructions */ | ||
| private final List<String> instructions; | ||
|
|
||
| /** A {@code Map} containing all the {@link Step}s mapped to their IDs */ | ||
| private final Map<Character, Step> stepsMap = new HashMap<>(); | ||
|
|
||
| /** Number of available Workers in Part 2 */ | ||
| private final int workers = 5; | ||
|
|
||
| /** Minimum time per Task */ | ||
| private final int minTimePerTask = 60; | ||
|
|
||
| /** | ||
| * Constructor for Day07. | ||
| * | ||
| * @param fileReaders {@code @Autowired} fileReader | ||
| */ | ||
| @Autowired | ||
| Day07(final FileReaders fileReaders) { | ||
| this.problemStatus = new HashMap<>(); | ||
| this.problemStatus.put("1", ProblemStatusEnum.SOLVED); | ||
| this.problemStatus.put("2", ProblemStatusEnum.SOLVED); | ||
|
|
||
| this.instructions = fileReaders.readFileIntoStringList(FILE_LOCATION); | ||
|
|
||
| parseSteps(); | ||
| } | ||
|
|
||
| @Override | ||
| public int getDay() { | ||
| return 7; | ||
| } | ||
|
|
||
| @Override | ||
| public String firstPart() { | ||
| return "Part 1 - Order in which the steps in the instructions should be completed: " + determineOrder(); | ||
| } | ||
|
|
||
| @Override | ||
| public String secondPart() { | ||
| return "Part 2 - Time required to complete all of the steps: " + determineTime() + " seconds"; | ||
| } | ||
|
|
||
| @Override | ||
| public HashMap<String, ProblemStatusEnum> getProblemStatus() { | ||
| return problemStatus; | ||
| } | ||
|
|
||
| /** | ||
| * Primary Method for Day 7, Part 1. | ||
| * <p> | ||
| * Determines the order, in which the instructions should be completed. For this purpose {@link #stepsMap} is copied | ||
| * into a list. The new HashMap is scanned for all {@link Step}s that have no previous steps in them. These are | ||
| * added to an ArrayList which then gets sorted alphabetically. The first item is the current step, which gets added | ||
| * to the output. Subsequently, this step gets removed from all previous steps and from the HashMap, as well as from | ||
| * the available Steps. This process gets repeated until the HashMap is empty. | ||
| * | ||
| * @return the ordered String of instructions | ||
| */ | ||
| private String determineOrder() { | ||
| final List<Character> availableStepsIds = new ArrayList<>(); | ||
| final List<Step> steps = getLocalCopyOfStepsMapAsArrayList(); | ||
| final StringBuilder order = new StringBuilder(); | ||
|
|
||
| while (!steps.isEmpty()) { | ||
| steps.forEach((step) -> { | ||
| if (!step.hasPrevious() && !availableStepsIds.contains(step.id)) { | ||
| availableStepsIds.add(step.id); | ||
| } | ||
| }); | ||
|
|
||
| Collections.sort(availableStepsIds); | ||
| final Character currentStepId = availableStepsIds.get(0); | ||
| order.append(currentStepId); | ||
|
|
||
| removeStepFromListAndPreviousSteps(steps, currentStepId); | ||
| availableStepsIds.remove(currentStepId); | ||
| } | ||
|
|
||
| return order.toString(); | ||
| } | ||
|
|
||
| /** | ||
| * Primary method for Day 7, Part 2. | ||
| * <p> | ||
| * Determines the time it takes to complete all tasks with the number of workers given in {@link #workers}. For this | ||
| * purpose, {@link #stepsMap} is copied into a list. In a while loop, first the available steps are determined. | ||
| * Secondly, each available step is given to a worker, if one is available. The time it takes for the step to be | ||
| * completed is calculated in this step and stored with the respective ID. In the third step, the time left is | ||
| * reduced. If there is just one second left, the step is removed from the map of Steps, from the list of previous | ||
| * steps in each other step and from the list of steps in progress. Lastly the time taken is increased by 1. If | ||
| * there are no more steps left, the while loop ends and the total time taken is returned. | ||
| * | ||
| * @return time it takes to complete all the tasks | ||
| */ | ||
| private int determineTime() { | ||
| final List<Step> steps = getLocalCopyOfStepsMapAsArrayList(); | ||
| int availableWorkers = workers; | ||
|
|
||
| final List<Character> availableSteps = new ArrayList<>(); | ||
| final Map<Character, Integer> stepsInProgress = new HashMap<>(); | ||
|
|
||
| int timeTaken = 0; | ||
|
|
||
| while (!steps.isEmpty()) { | ||
| steps.forEach((step) -> { | ||
| if (!(step.hasPrevious() || availableSteps.contains(step.id) || stepsInProgress.containsKey(step.id))) { | ||
| availableSteps.add(step.id); | ||
| } | ||
| }); | ||
|
|
||
| final List<Character> stepsToRemoveFromAvailable = new ArrayList<>(); | ||
| for (final Character id : availableSteps) { | ||
| if (availableWorkers > 0) { | ||
| final int timeForThisId = id - DIFFERENCE_BETWEEN_CHAR_AND_SECONDS; | ||
| final int timeToFinish = minTimePerTask + timeForThisId; | ||
|
|
||
| stepsInProgress.put(id, timeToFinish); | ||
| availableWorkers--; | ||
|
|
||
| stepsToRemoveFromAvailable.add(id); | ||
| } | ||
| } | ||
| availableSteps.removeAll(stepsToRemoveFromAvailable); | ||
|
|
||
| final List<Character> stepsToRemoveFromInProgress = new ArrayList<>(); | ||
| for (final Map.Entry<Character, Integer> stepInProgress : stepsInProgress.entrySet()) { | ||
| int timeLeft = stepInProgress.getValue(); | ||
| final char currentStepId = stepInProgress.getKey(); | ||
|
|
||
| if (timeLeft > 1) { | ||
| --timeLeft; | ||
| stepInProgress.setValue(timeLeft); | ||
| } else { | ||
| removeStepFromListAndPreviousSteps(steps, currentStepId); | ||
| stepsToRemoveFromInProgress.add(currentStepId); | ||
| availableWorkers++; | ||
| } | ||
| } | ||
| stepsToRemoveFromInProgress.forEach(stepsInProgress.keySet()::remove); | ||
|
|
||
| timeTaken++; | ||
| } | ||
|
|
||
| return timeTaken; | ||
| } | ||
|
|
||
| /** | ||
| * Parses the {@link #instructions} to create {@link Step}s and add previous IDs. | ||
| */ | ||
| private void parseSteps() { | ||
| for (final String instruction : instructions) { | ||
| final Matcher matcher = Pattern.compile("Step (\\w) must be finished before step (\\w) can begin\\.").matcher(instruction); | ||
|
|
||
| if (matcher.find()) { | ||
| final char firstId = matcher.group(1).charAt(0); | ||
| final char secondId = matcher.group(2).charAt(0); | ||
|
|
||
| stepsMap.putIfAbsent(firstId, new Step(firstId)); | ||
| stepsMap.putIfAbsent(secondId, new Step(secondId)); | ||
| stepsMap.get(secondId).addPrevious(firstId); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Creates a deep copy of the {@link #stepsMap} and returns it as an ArrayList. | ||
| * | ||
| * @return a copy of the {@link #stepsMap} as an ArrayList. | ||
| */ | ||
| private List<Step> getLocalCopyOfStepsMapAsArrayList() { | ||
| final List<Step> copyOfStepHashMap = new ArrayList<>(); | ||
|
|
||
| stepsMap.forEach((id, step) -> copyOfStepHashMap.add(new Step(step))); | ||
|
|
||
| return copyOfStepHashMap; | ||
| } | ||
|
|
||
| /** | ||
| * Removes the given step ID from the given List and the previous steps of its elements. | ||
| * | ||
| * @param stepsList list of Steps to remove from | ||
| * @param currentStepId ID of step to remove | ||
| */ | ||
| private void removeStepFromListAndPreviousSteps(final List<Step> stepsList, final char currentStepId) { | ||
| for (final Iterator<Step> steps = stepsList.iterator(); steps.hasNext(); ) { | ||
| final Step step = steps.next(); | ||
| step.removePrevious(currentStepId); | ||
| if (step.id == currentStepId) { | ||
| steps.remove(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
66 changes: 66 additions & 0 deletions
66
src/main/java/org/basseur/adventofcode/advent2018/days/day07/Step.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| package org.basseur.adventofcode.advent2018.days.day07; | ||
|
|
||
| import java.util.ArrayList; | ||
|
|
||
| public class Step { | ||
| /** Letter of this step */ | ||
| public final char id; | ||
| /** List of previous steps */ | ||
| private final ArrayList<Character> previousSteps = new ArrayList<>(); | ||
|
|
||
| /** | ||
| * Constructs Step with id. | ||
| * | ||
| * @param id the id of this Step | ||
| */ | ||
| public Step(Character id) { | ||
| this.id = id; | ||
| } | ||
|
|
||
| /** | ||
| * Constructs Step with id and previousSteps. | ||
| * | ||
| * @param id the id for this Step | ||
| * @param previousSteps the previous Steps for this Step | ||
| */ | ||
| public Step(Character id, ArrayList<Character> previousSteps) { | ||
| this.id = id; | ||
| this.previousSteps.addAll(previousSteps); | ||
| } | ||
|
|
||
| /** | ||
| * Constructs Step as copy of another Step | ||
| * | ||
| * @param other the other Step to be copied | ||
| */ | ||
| public Step(Step other) { | ||
| this(other.id, other.previousSteps); | ||
| } | ||
|
|
||
| /** | ||
| * Returns {@code true} if this step has previous steps | ||
| * | ||
| * @return {@code true} if this Step has previous steps, {@code false} otherwise. | ||
| */ | ||
| public boolean hasPrevious() { | ||
| return previousSteps.size() > 0; | ||
| } | ||
|
|
||
| /** | ||
| * Adds an id to the {@link Step#previousSteps} ArrayList. | ||
| * | ||
| * @param prevId id of the previous step to be added | ||
| */ | ||
| public void addPrevious(Character prevId) { | ||
| previousSteps.add(prevId); | ||
| } | ||
|
|
||
| /** | ||
| * Removes an id to the {@link Step#previousSteps} ArrayList. | ||
| * | ||
| * @param prevId id of the previous step to be removed | ||
| */ | ||
| public void removePrevious(Character prevId) { | ||
| previousSteps.remove(prevId); | ||
| } | ||
| } |
6 changes: 6 additions & 0 deletions
6
src/main/java/org/basseur/adventofcode/advent2018/days/day07/package-info.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| /** | ||
| * Contains <i>Day 7: The Sum of Its Parts</i>. | ||
| * | ||
| * @author Jan Philipp Gölz | ||
| */ | ||
| package org.basseur.adventofcode.advent2018.days.day07; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This of course is like a "doMagic()" method. Although I am sure that it does what it is supposed to do, it is way to long for somebody to understand who is reading the code for the first time. Not because it is not good to read and not well structured, only because of the underlying complexity of what it does.
Also, consider what would happen if we refactored it? The test would break if we make a mistake, but we would not have a clue about why - or do we (also because we don't test it since it is a private method=?
To improve this, I would attempt to make it testable. For that, maybe moving it to a new class would be the first step, and splitting it into smaller, simpler methods. Then testing each of them in the new class.
If you feel that you have to test a private method, it has gotten too complex/large and something is wrong by design.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right.. hmpfgrmbl. ;)