Skip to content
This repository was archived by the owner on Dec 19, 2021. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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&ouml;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() {
Copy link
Copy Markdown
Collaborator

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.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right.. hmpfgrmbl. ;)

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();
}
}
}
}
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);
}
}
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&ouml;lz
*/
package org.basseur.adventofcode.advent2018.days.day07;
Loading