diff --git a/README.md b/README.md index ac758abe3b..2535762d88 100644 --- a/README.md +++ b/README.md @@ -23,4 +23,15 @@ * map(String::toLowerCase) 적용 -> forEach(System.out::println) 으로 출 * [X] Optional 실습 1 : Optional을 활용해 조건에 따른 반환 * [X] Optional 실습 2 : Optional에서 값을 반환 -* [X] Optional 실습 3 : Optional에서 exception 처리 \ No newline at end of file +* [X] Optional 실습 3 : Optional에서 exception 처리 + +# STEP 2 +* [X] 참여할 사람 명단 입력받기 + * [X] 쉼표 구분자를 파싱하기 +* [X] 최대 사다리 높이 입력받기 +* [X] 실행결과 출력하기 +* [X] 사다리 만들기 + * [X] 사다리 한 줄(Line) 만들기 + * [X] 사다리 다리 구성하기 (Bridge) + * [X] 캐시 전략 사용하기 + * [X] 사다리 다리 리스트 구성하기 \ No newline at end of file diff --git a/src/main/java/nextstep/ladder/LadderApplication.java b/src/main/java/nextstep/ladder/LadderApplication.java new file mode 100644 index 0000000000..a9ccadb2e4 --- /dev/null +++ b/src/main/java/nextstep/ladder/LadderApplication.java @@ -0,0 +1,11 @@ +package nextstep.ladder; + +import nextstep.ladder.controller.LadderController; + +public class LadderApplication { + + public static void main(String[] args) { + LadderController ladderController = new LadderController(); + ladderController.game(); + } +} diff --git a/src/main/java/nextstep/ladder/controller/LadderController.java b/src/main/java/nextstep/ladder/controller/LadderController.java new file mode 100644 index 0000000000..ad617c94ca --- /dev/null +++ b/src/main/java/nextstep/ladder/controller/LadderController.java @@ -0,0 +1,22 @@ +package nextstep.ladder.controller; + +import nextstep.ladder.model.Ladder; +import nextstep.ladder.model.Lines; +import nextstep.ladder.model.LinesGenerator; +import nextstep.ladder.model.Participants; +import nextstep.ladder.view.InputView; +import nextstep.ladder.view.OutputView; + +public class LadderController { + + private final LinesGenerator linesGenerator = new LinesGenerator(); + + public void game() { + Participants participants = new Participants(InputView.inputGameParticipants()); + int height = InputView.inputLadderHeight(); + Lines lines = new Lines(linesGenerator.generatorLines(participants.getNumbersOfParticipants(), height)); + + Ladder ladder = new Ladder(participants, lines, height); + OutputView.printLadder(ladder); + } +} diff --git a/src/main/java/nextstep/ladder/model/Bridge.java b/src/main/java/nextstep/ladder/model/Bridge.java new file mode 100644 index 0000000000..41dfeb15c2 --- /dev/null +++ b/src/main/java/nextstep/ladder/model/Bridge.java @@ -0,0 +1,41 @@ +package nextstep.ladder.model; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; + +public class Bridge { + private final boolean value; + + private static final Map cacheBridge = new HashMap<>(); + + static { + cacheBridge.put(Boolean.TRUE, new Bridge(true)); + cacheBridge.put(Boolean.FALSE, new Bridge(false)); + } + + private Bridge(boolean value) { + this.value = value; + } + + public static Bridge of(boolean value) { + return cacheBridge.get(value); + } + + public boolean canCrossBridge() { + return value; + } + + public Bridge next() { + if (this.value) { + return of(false); + } + return of(ThreadLocalRandom.current().nextBoolean()); + } + + public void compareToNextBridge(Bridge next) { + if (this.value && next.value) { + throw new IllegalArgumentException("연속해서 true인 Bridge가 있습니다."); + } + } +} diff --git a/src/main/java/nextstep/ladder/model/BridgesGenerator.java b/src/main/java/nextstep/ladder/model/BridgesGenerator.java new file mode 100644 index 0000000000..3cf8413737 --- /dev/null +++ b/src/main/java/nextstep/ladder/model/BridgesGenerator.java @@ -0,0 +1,7 @@ +package nextstep.ladder.model; + +import java.util.List; + +public interface BridgesGenerator { + List generateBridges(Integer numbersOfPeople); +} diff --git a/src/main/java/nextstep/ladder/model/BridgesGeneratorByCreatingCandidates.java b/src/main/java/nextstep/ladder/model/BridgesGeneratorByCreatingCandidates.java new file mode 100644 index 0000000000..c89f95232a --- /dev/null +++ b/src/main/java/nextstep/ladder/model/BridgesGeneratorByCreatingCandidates.java @@ -0,0 +1,49 @@ +package nextstep.ladder.model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class BridgesGeneratorByCreatingCandidates implements BridgesGenerator { + + private static List> candidates = new ArrayList<>(); + + static void generateLadders(int numbersOfPeople) { + List> result = new ArrayList<>(List.of(List.of(Bridge.of(false)), List.of(Bridge.of(true)))); + + for (int i = 2; i < numbersOfPeople ; i++) { + result = dp(result); + } + + candidates = result; + } + + static List> dp(List> result) { + return result.stream() + .flatMap(ladder -> { + List withFalse = new ArrayList<>(ladder); + withFalse.add(Bridge.of(false)); + List withTrue = new ArrayList<>(ladder); + if (!ladder.get(ladder.size() - 1).canCrossBridge()) { + withTrue.add(Bridge.of(true)); + return Stream.of(withFalse, withTrue); + } + return Stream.of(withFalse); + }) + .collect(Collectors.toList()); + } + + @Override + public List generateBridges(Integer numbersOfPeople) { + if (candidates.isEmpty()) { + generateLadders(numbersOfPeople); + } + Collections.shuffle(candidates); + return candidates.get(0); + } + + + +} diff --git a/src/main/java/nextstep/ladder/model/BridgesGeneratorBySequentialRandom.java b/src/main/java/nextstep/ladder/model/BridgesGeneratorBySequentialRandom.java new file mode 100644 index 0000000000..96e947bbdf --- /dev/null +++ b/src/main/java/nextstep/ladder/model/BridgesGeneratorBySequentialRandom.java @@ -0,0 +1,19 @@ +package nextstep.ladder.model; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +public class BridgesGeneratorBySequentialRandom implements BridgesGenerator { + + @Override + public List generateBridges(Integer numbersOfPeople) { + List bridges = new LinkedList<>(); + Bridge currentBridge = Bridge.of(ThreadLocalRandom.current().nextBoolean()); + for (int i = 0; i < numbersOfPeople - 1; i++) { + bridges.add(currentBridge); + currentBridge = currentBridge.next(); + } + return bridges; + } +} diff --git a/src/main/java/nextstep/ladder/model/Ladder.java b/src/main/java/nextstep/ladder/model/Ladder.java new file mode 100644 index 0000000000..bc89eba56f --- /dev/null +++ b/src/main/java/nextstep/ladder/model/Ladder.java @@ -0,0 +1,31 @@ +package nextstep.ladder.model; + +public class Ladder { + + private static final int MIN_HEIGHT = 1; + + private final Participants participants; + private final Lines lines; + private final Integer height; + + public Ladder(Participants participants, Lines lines, Integer height) { + validateHeight(height); + this.participants = participants; + this.lines = lines; + this.height = height; + } + + private void validateHeight(Integer height) { + if (height < MIN_HEIGHT) { + throw new IllegalArgumentException("높이는 최소 1 이상이어야 합니다."); + } + } + + public Participants getParticipants() { + return participants; + } + + public Lines getLines() { + return lines; + } +} diff --git a/src/main/java/nextstep/ladder/model/Line.java b/src/main/java/nextstep/ladder/model/Line.java new file mode 100644 index 0000000000..1e4f656aab --- /dev/null +++ b/src/main/java/nextstep/ladder/model/Line.java @@ -0,0 +1,45 @@ +package nextstep.ladder.model; + +import java.util.List; + +public class Line { + + private static final Integer MIN_NUMBERS_OF_PEOPLE = 2; + + private final List bridges; + + public Line(Integer numbersOfPeople, List bridges) { + validateLine(numbersOfPeople, bridges); + this.bridges = bridges; + } + + private void validateLine(Integer numbersOfPeople, List bridges) { + validateBridgeCount(numbersOfPeople, bridges); + validateContinuousTrueBridge(bridges); + validateNumbersOfPeople(numbersOfPeople); + } + + private void validateBridgeCount(Integer numbersOfPeople, List bridges) { + if (bridges.size() != numbersOfPeople - 1) { + throw new IllegalArgumentException("다리의 개수는 사람의 수보다 1 작아야합니다"); + } + } + + private void validateContinuousTrueBridge(List bridges) { + bridges.stream() + .reduce((prevBridge, currentBridge) -> { + prevBridge.compareToNextBridge(currentBridge); + return currentBridge; + }); + } + + private void validateNumbersOfPeople(Integer numbersOfPeople) { + if (numbersOfPeople < MIN_NUMBERS_OF_PEOPLE) { + throw new IllegalArgumentException("사람은 최소 2명 이상이어야 합니다."); + } + } + + public List getBridges() { + return bridges; + } +} diff --git a/src/main/java/nextstep/ladder/model/Lines.java b/src/main/java/nextstep/ladder/model/Lines.java new file mode 100644 index 0000000000..8aa8aa8fd2 --- /dev/null +++ b/src/main/java/nextstep/ladder/model/Lines.java @@ -0,0 +1,16 @@ +package nextstep.ladder.model; + +import java.util.List; + +public class Lines { + + private final List lines; + + public Lines(List lines) { + this.lines = lines; + } + + public List getLines() { + return lines; + } +} diff --git a/src/main/java/nextstep/ladder/model/LinesGenerator.java b/src/main/java/nextstep/ladder/model/LinesGenerator.java new file mode 100644 index 0000000000..1ddcd8a46a --- /dev/null +++ b/src/main/java/nextstep/ladder/model/LinesGenerator.java @@ -0,0 +1,16 @@ +package nextstep.ladder.model; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class LinesGenerator { + + private final BridgesGenerator bridgesGenerator = new BridgesGeneratorBySequentialRandom(); + + public List generatorLines(Integer numbersOfPeople, Integer height) { + return IntStream.range(0, height) + .mapToObj(index -> new Line(numbersOfPeople, bridgesGenerator.generateBridges(numbersOfPeople))) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/nextstep/ladder/model/Participant.java b/src/main/java/nextstep/ladder/model/Participant.java new file mode 100644 index 0000000000..8fefccf582 --- /dev/null +++ b/src/main/java/nextstep/ladder/model/Participant.java @@ -0,0 +1,24 @@ +package nextstep.ladder.model; + +public class Participant { + + private static final Integer MIN_NAME_LENGTH = 1; + private static final Integer MAX_NAME_LENGTH = 5; + + private final String name; + + public Participant(String name) { + validateNameLength(name); + this.name = name; + } + + private void validateNameLength(String name) { + if (name.length() < MIN_NAME_LENGTH || name.length() > MAX_NAME_LENGTH) { + throw new IllegalArgumentException("0글자 이하이거나 5글자를 초과하는 참가자 이름이 포함되어 있습니다."); + } + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/nextstep/ladder/model/Participants.java b/src/main/java/nextstep/ladder/model/Participants.java new file mode 100644 index 0000000000..0c43e10e25 --- /dev/null +++ b/src/main/java/nextstep/ladder/model/Participants.java @@ -0,0 +1,33 @@ +package nextstep.ladder.model; + +import java.util.List; +import java.util.stream.Collectors; + +public class Participants { + + private static final String DELIMITER = ","; + + private final List participants; + + public Participants(String inputParticipants) { + this.participants = createParticipants(parseInputParticipants(inputParticipants)); + } + + private List parseInputParticipants(String inputParticipants) { + return List.of(inputParticipants.split(DELIMITER)); + } + + private List createParticipants(List inputParticipants) { + return inputParticipants.stream() + .map(Participant::new) + .collect(Collectors.toList()); + } + + public Integer getNumbersOfParticipants() { + return participants.size(); + } + + public List getParticipants() { + return participants; + } +} diff --git a/src/main/java/nextstep/ladder/view/InputView.java b/src/main/java/nextstep/ladder/view/InputView.java new file mode 100644 index 0000000000..8b583a7271 --- /dev/null +++ b/src/main/java/nextstep/ladder/view/InputView.java @@ -0,0 +1,17 @@ +package nextstep.ladder.view; + +import java.util.Scanner; + +public class InputView { + private static final Scanner scanner = new Scanner(System.in); + + public static String inputGameParticipants() { + System.out.println("참여할 사람 이름을 입력하세요. (이름은 쉼표(,)로 구분하세요)"); + return scanner.nextLine(); + } + + public static int inputLadderHeight() { + System.out.println("최대 사다리 높이는 몇 개인가요?"); + return Integer.parseInt(scanner.nextLine()); + } +} diff --git a/src/main/java/nextstep/ladder/view/OutputView.java b/src/main/java/nextstep/ladder/view/OutputView.java new file mode 100644 index 0000000000..10b6c80bf2 --- /dev/null +++ b/src/main/java/nextstep/ladder/view/OutputView.java @@ -0,0 +1,45 @@ +package nextstep.ladder.view; + +import java.util.stream.Collectors; +import nextstep.ladder.model.Ladder; +import nextstep.ladder.model.Line; +import nextstep.ladder.model.Lines; +import nextstep.ladder.model.Participant; +import nextstep.ladder.model.Participants; + +public class OutputView { + private static final StringBuffer buffer = new StringBuffer(); + private static final String INITIAL_BRIDGE = " |"; + private static final String PASSABLE_BRIDGE = "-----|"; + private static final String IMPASSABLE_BRIDGE = " |"; + + public static void printLadder(Ladder ladder) { + + buffer.append("\n실행결과\n"); + printParticipants(ladder.getParticipants()); + printLadderLines(ladder.getLines()); + System.out.println(buffer); + } + + public static void printParticipants(Participants participants) { + for (Participant participant : participants.getParticipants()) { + buffer.append(String.format ("%6s", participant.getName())); + } + buffer.append("\n"); + } + + private static void printLadderLines(Lines lines) { + for (Line line : lines.getLines()) { + buffer.append(INITIAL_BRIDGE); + printLine(line); + buffer.append("\n"); + } + } + + private static void printLine(Line line) { + buffer.append( + line.getBridges().stream() + .map(bridge -> bridge.canCrossBridge() ? PASSABLE_BRIDGE : IMPASSABLE_BRIDGE) + .collect(Collectors.joining())); + } +} diff --git a/src/test/java/nextstep/ladder/model/BridgeTest.java b/src/test/java/nextstep/ladder/model/BridgeTest.java new file mode 100644 index 0000000000..3b834d662a --- /dev/null +++ b/src/test/java/nextstep/ladder/model/BridgeTest.java @@ -0,0 +1,49 @@ +package nextstep.ladder.model; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; + +public class BridgeTest { + + @Test + public void canCrossBridge_다리_존재_시_True_반환() { + Bridge bridge = Bridge.of(true); + + assertThat(bridge.canCrossBridge()).isTrue(); + } + + @Test + public void canCrossBridge_다리_없을_시_False_반환() { + Bridge bridge = Bridge.of(false); + + assertThat(bridge.canCrossBridge()).isFalse(); + } + + @Test + public void next_현재_다리_value가_TRUE_일때_무조건_False반환() { + Bridge now = Bridge.of(true); + + assertThat(now.next()).isEqualTo(Bridge.of(false)); + } + + @Test + public void compareToNextBridge_연속된_두개가_True일시_에러_반환() { + Bridge prev = Bridge.of(true); + Bridge next = Bridge.of(true); + + assertThatThrownBy(() -> prev.compareToNextBridge(next)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void compareToNextBridge_연속된_두개가_True가_아닐시_정상동작_테스트() { + Bridge prev = Bridge.of(false); + Bridge next = Bridge.of(true); + + assertThatCode(() -> prev.compareToNextBridge(next)) + .doesNotThrowAnyException(); // 예외가 발생하지 않아야 함 + } +} diff --git a/src/test/java/nextstep/ladder/model/BridgesGeneratorBySequentialRandomTest.java b/src/test/java/nextstep/ladder/model/BridgesGeneratorBySequentialRandomTest.java new file mode 100644 index 0000000000..2b573763fd --- /dev/null +++ b/src/test/java/nextstep/ladder/model/BridgesGeneratorBySequentialRandomTest.java @@ -0,0 +1,15 @@ +package nextstep.ladder.model; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +public class BridgesGeneratorBySequentialRandomTest { + + private final BridgesGenerator bridgesGenerator = new BridgesGeneratorBySequentialRandom(); + + @Test + public void generateBridges_다리_개수_확인() { + assertThat(bridgesGenerator.generateBridges(5)).hasSize(4); + } +} diff --git a/src/test/java/nextstep/ladder/model/LadderTest.java b/src/test/java/nextstep/ladder/model/LadderTest.java new file mode 100644 index 0000000000..14e9020c44 --- /dev/null +++ b/src/test/java/nextstep/ladder/model/LadderTest.java @@ -0,0 +1,28 @@ +package nextstep.ladder.model; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class LadderTest { + + private Participants participants; + private Lines lines; + + @BeforeEach + public void setup() { + List bridges = List.of(Bridge.of(true)); + List line = List.of(new Line(2, bridges)); + + participants = new Participants("a,b"); + lines = new Lines(line); + } + + @Test + public void generatorLines_높이가_0일때_에러_테스트() { + assertThatThrownBy( () -> new Ladder(participants, lines, 0)) + .isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/nextstep/ladder/model/LineTest.java b/src/test/java/nextstep/ladder/model/LineTest.java new file mode 100644 index 0000000000..5a43f78fbe --- /dev/null +++ b/src/test/java/nextstep/ladder/model/LineTest.java @@ -0,0 +1,30 @@ +package nextstep.ladder.model; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.LinkedList; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class LineTest { + + @Test + public void 다리가_연속으로_True_일때_에러반환_테스트() { + List bridges = List.of(Bridge.of(true), Bridge.of(true)); + assertThatThrownBy(() -> new Line(5, bridges)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void 다리가_사람수의_MINUS1이_아닐때_에러반환_테스트() { + List bridges = List.of(Bridge.of(true), Bridge.of(false)); + assertThatThrownBy(() -> new Line(5, bridges)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void 사람수_1명일때_에러_테스트() { + assertThatThrownBy(() -> new Line(1, new LinkedList<>())) + .isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/nextstep/ladder/model/LinesGeneratorTest.java b/src/test/java/nextstep/ladder/model/LinesGeneratorTest.java new file mode 100644 index 0000000000..5dfd103914 --- /dev/null +++ b/src/test/java/nextstep/ladder/model/LinesGeneratorTest.java @@ -0,0 +1,15 @@ +package nextstep.ladder.model; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +public class LinesGeneratorTest { + + private final LinesGenerator linesGenerator = new LinesGenerator(); + + @Test + public void generatorLines_높이에_맞게_생성하는지_테스트() { + assertThat(linesGenerator.generatorLines(5, 6)).hasSize(6); + } +} diff --git a/src/test/java/nextstep/ladder/model/ParticipantTest.java b/src/test/java/nextstep/ladder/model/ParticipantTest.java new file mode 100644 index 0000000000..3d346914df --- /dev/null +++ b/src/test/java/nextstep/ladder/model/ParticipantTest.java @@ -0,0 +1,14 @@ +package nextstep.ladder.model; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; + +public class ParticipantTest { + + @Test + public void 이름_5글자_이상일때_에러_반환_테스트() { + assertThatThrownBy(() -> new Participant("aaaaaa")) + .isInstanceOf(IllegalArgumentException.class); + } +}