diff --git a/src/main/java/org/basseur/adventofcode/advent2018/days/day07/Day07.java b/src/main/java/org/basseur/adventofcode/advent2018/days/day07/Day07.java new file mode 100644 index 0000000..83ad69e --- /dev/null +++ b/src/main/java/org/basseur/adventofcode/advent2018/days/day07/Day07.java @@ -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 Day 7: The Sum of Its Parts. + * + * @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 problemStatus; + + /** A list containing the instructions */ + private final List instructions; + + /** A {@code Map} containing all the {@link Step}s mapped to their IDs */ + private final Map 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 getProblemStatus() { + return problemStatus; + } + + /** + * Primary Method for Day 7, Part 1. + *

+ * 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 availableStepsIds = new ArrayList<>(); + final List 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. + *

+ * 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 steps = getLocalCopyOfStepsMapAsArrayList(); + int availableWorkers = workers; + + final List availableSteps = new ArrayList<>(); + final Map 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 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 stepsToRemoveFromInProgress = new ArrayList<>(); + for (final Map.Entry 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 getLocalCopyOfStepsMapAsArrayList() { + final List 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 stepsList, final char currentStepId) { + for (final Iterator steps = stepsList.iterator(); steps.hasNext(); ) { + final Step step = steps.next(); + step.removePrevious(currentStepId); + if (step.id == currentStepId) { + steps.remove(); + } + } + } +} diff --git a/src/main/java/org/basseur/adventofcode/advent2018/days/day07/Step.java b/src/main/java/org/basseur/adventofcode/advent2018/days/day07/Step.java new file mode 100644 index 0000000..645c911 --- /dev/null +++ b/src/main/java/org/basseur/adventofcode/advent2018/days/day07/Step.java @@ -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 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 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); + } +} diff --git a/src/main/java/org/basseur/adventofcode/advent2018/days/day07/package-info.java b/src/main/java/org/basseur/adventofcode/advent2018/days/day07/package-info.java new file mode 100644 index 0000000..93e40f8 --- /dev/null +++ b/src/main/java/org/basseur/adventofcode/advent2018/days/day07/package-info.java @@ -0,0 +1,6 @@ +/** + * Contains Day 7: The Sum of Its Parts. + * + * @author Jan Philipp Gölz + */ +package org.basseur.adventofcode.advent2018.days.day07; \ No newline at end of file diff --git a/src/main/resources/puzzleInputs/Input07.txt b/src/main/resources/puzzleInputs/Input07.txt new file mode 100644 index 0000000..f309b05 --- /dev/null +++ b/src/main/resources/puzzleInputs/Input07.txt @@ -0,0 +1,101 @@ +Step X must be finished before step C can begin. +Step C must be finished before step G can begin. +Step F must be finished before step G can begin. +Step U must be finished before step Y can begin. +Step O must be finished before step S can begin. +Step D must be finished before step N can begin. +Step M must be finished before step H can begin. +Step J must be finished before step Q can begin. +Step G must be finished before step R can begin. +Step I must be finished before step N can begin. +Step R must be finished before step K can begin. +Step A must be finished before step Z can begin. +Step Y must be finished before step L can begin. +Step H must be finished before step P can begin. +Step K must be finished before step S can begin. +Step Z must be finished before step P can begin. +Step T must be finished before step S can begin. +Step N must be finished before step P can begin. +Step E must be finished before step S can begin. +Step S must be finished before step W can begin. +Step W must be finished before step V can begin. +Step L must be finished before step V can begin. +Step P must be finished before step B can begin. +Step Q must be finished before step V can begin. +Step B must be finished before step V can begin. +Step P must be finished before step Q can begin. +Step S must be finished before step V can begin. +Step C must be finished before step Q can begin. +Step I must be finished before step H can begin. +Step A must be finished before step E can begin. +Step H must be finished before step Q can begin. +Step G must be finished before step V can begin. +Step N must be finished before step L can begin. +Step R must be finished before step Q can begin. +Step W must be finished before step L can begin. +Step X must be finished before step L can begin. +Step X must be finished before step J can begin. +Step W must be finished before step P can begin. +Step U must be finished before step B can begin. +Step P must be finished before step V can begin. +Step O must be finished before step P can begin. +Step W must be finished before step Q can begin. +Step S must be finished before step Q can begin. +Step U must be finished before step Z can begin. +Step Z must be finished before step T can begin. +Step M must be finished before step T can begin. +Step A must be finished before step P can begin. +Step Z must be finished before step B can begin. +Step N must be finished before step S can begin. +Step H must be finished before step N can begin. +Step J must be finished before step E can begin. +Step M must be finished before step J can begin. +Step R must be finished before step A can begin. +Step A must be finished before step Y can begin. +Step F must be finished before step V can begin. +Step L must be finished before step P can begin. +Step K must be finished before step L can begin. +Step F must be finished before step P can begin. +Step G must be finished before step L can begin. +Step I must be finished before step Q can begin. +Step C must be finished before step L can begin. +Step I must be finished before step Y can begin. +Step G must be finished before step B can begin. +Step H must be finished before step L can begin. +Step X must be finished before step U can begin. +Step I must be finished before step K can begin. +Step R must be finished before step N can begin. +Step I must be finished before step L can begin. +Step M must be finished before step I can begin. +Step K must be finished before step V can begin. +Step G must be finished before step E can begin. +Step F must be finished before step B can begin. +Step O must be finished before step Y can begin. +Step Y must be finished before step Q can begin. +Step F must be finished before step K can begin. +Step N must be finished before step W can begin. +Step O must be finished before step R can begin. +Step N must be finished before step E can begin. +Step M must be finished before step V can begin. +Step H must be finished before step T can begin. +Step Y must be finished before step T can begin. +Step F must be finished before step J can begin. +Step F must be finished before step O can begin. +Step W must be finished before step B can begin. +Step T must be finished before step E can begin. +Step T must be finished before step P can begin. +Step F must be finished before step M can begin. +Step U must be finished before step I can begin. +Step H must be finished before step S can begin. +Step S must be finished before step P can begin. +Step T must be finished before step W can begin. +Step A must be finished before step N can begin. +Step O must be finished before step N can begin. +Step L must be finished before step B can begin. +Step U must be finished before step K can begin. +Step Z must be finished before step W can begin. +Step X must be finished before step D can begin. +Step Z must be finished before step L can begin. +Step I must be finished before step T can begin. +Step O must be finished before step W can begin. +Step I must be finished before step B can begin. \ No newline at end of file diff --git a/src/test/java/org/basseur/adventofcode/advent2018/days/day07/Day07Test.java b/src/test/java/org/basseur/adventofcode/advent2018/days/day07/Day07Test.java new file mode 100644 index 0000000..b43bf48 --- /dev/null +++ b/src/test/java/org/basseur/adventofcode/advent2018/days/day07/Day07Test.java @@ -0,0 +1,67 @@ +package org.basseur.adventofcode.advent2018.days.day07; + +import org.basseur.adventofcode.advent2018.days.Days; +import org.basseur.adventofcode.advent2018.utils.FileReaders; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(SpringRunner.class) +public class Day07Test { + + @MockBean + private FileReaders fileReaders; + private List instructions = new ArrayList<>(); + private Days day07; + + @Before + public void setUp() { + instructions.add("Step C must be finished before step A can begin."); + instructions.add("Step C must be finished before step F can begin."); + instructions.add("Step A must be finished before step B can begin."); + instructions.add("Step A must be finished before step D can begin."); + instructions.add("Step B must be finished before step E can begin."); + instructions.add("Step D must be finished before step E can begin."); + instructions.add("Step F must be finished before step E can begin."); + + Mockito.when(fileReaders.readFileIntoStringList(Mockito.anyString())).thenReturn(instructions); + day07 = new Day07(fileReaders); + } + + @Test + public void getDay() { + int expectedResult = 7; + int actualResult = day07.getDay(); + + Assert.assertEquals(expectedResult, actualResult); + } + + @Test + public void firstPart() { + String expectedResult = "Part 1 - Order in which the steps in the instructions should be completed: CABDFE"; + String actualResult = day07.firstPart(); + String actualResult2 = day07.firstPart(); + + Assert.assertEquals(expectedResult, actualResult); + Assert.assertEquals(expectedResult, actualResult2); + } + + @Test + public void secondPart() { + ReflectionTestUtils.setField(day07, "workers", 2); + ReflectionTestUtils.setField(day07, "minTimePerTask", 0); + + String expectedResult = "Part 2 - Time required to complete all of the steps: 15 seconds"; + String actualResult = day07.secondPart(); + + Assert.assertEquals(expectedResult, actualResult); + } +} \ No newline at end of file diff --git a/src/test/java/org/basseur/adventofcode/advent2018/days/day07/StepTest.java b/src/test/java/org/basseur/adventofcode/advent2018/days/day07/StepTest.java new file mode 100644 index 0000000..0511d9f --- /dev/null +++ b/src/test/java/org/basseur/adventofcode/advent2018/days/day07/StepTest.java @@ -0,0 +1,45 @@ +package org.basseur.adventofcode.advent2018.days.day07; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.junit4.SpringRunner; + + +@RunWith(SpringRunner.class) +public class StepTest { + + private Step step; + + @Before + public void setUp() { + step = new Step('A'); + } + + @Test + public void testAddRemoveAndHasPrevious() { + step.addPrevious('B'); + Assert.assertTrue(step.hasPrevious()); + + step.removePrevious('B'); + Assert.assertFalse(step.hasPrevious()); + } + + @Test + public void testRemoveFromEmptyPreviousSteps() { + step.removePrevious('Z'); + Assert.assertFalse(step.hasPrevious()); + } + + @Test + public void testCopiedStepIsNotJustReference() { + Step copiedStep = new Step(step); + + step.addPrevious('B'); + + Assert.assertTrue(step.hasPrevious()); + Assert.assertFalse(copiedStep.hasPrevious()); + + } +} \ No newline at end of file