From 07dd4ec0470db998dea38f1eab882486f076f300 Mon Sep 17 00:00:00 2001 From: choincnp Date: Tue, 22 Apr 2025 13:55:00 +0900 Subject: [PATCH 01/14] refactor : refactored anonymous class to Lambda expression --- src/test/java/nextstep/fp/CarTest.java | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/test/java/nextstep/fp/CarTest.java b/src/test/java/nextstep/fp/CarTest.java index 1ab1106fe2..ecab481aec 100644 --- a/src/test/java/nextstep/fp/CarTest.java +++ b/src/test/java/nextstep/fp/CarTest.java @@ -8,24 +8,14 @@ public class CarTest { @Test public void 이동() { Car car = new Car("pobi", 0); - Car actual = car.move(new MoveStrategy() { - @Override - public boolean isMovable() { - return true; - } - }); + Car actual = car.move(() -> true); assertThat(actual).isEqualTo(new Car("pobi", 1)); } @Test public void 정지() { Car car = new Car("pobi", 0); - Car actual = car.move(new MoveStrategy() { - @Override - public boolean isMovable() { - return false; - } - }); + Car actual = car.move(() -> false); assertThat(actual).isEqualTo(new Car("pobi", 0)); } } From 0cdb9c6533d3ea65e91eaad3079553ed8106231a Mon Sep 17 00:00:00 2001 From: choincnp Date: Tue, 22 Apr 2025 15:35:15 +0900 Subject: [PATCH 02/14] refactor : add functional interface and refactored to use anonymous class with interface --- src/main/java/nextstep/fp/Lambda.java | 33 +++++++++++++++--------- src/main/java/nextstep/fp/SumFilter.java | 8 ++++++ 2 files changed, 29 insertions(+), 12 deletions(-) create mode 100644 src/main/java/nextstep/fp/SumFilter.java diff --git a/src/main/java/nextstep/fp/Lambda.java b/src/main/java/nextstep/fp/Lambda.java index bd68fe1ce6..73b383d961 100644 --- a/src/main/java/nextstep/fp/Lambda.java +++ b/src/main/java/nextstep/fp/Lambda.java @@ -27,27 +27,36 @@ public void run() { } public static int sumAll(List numbers) { - int total = 0; - for (int number : numbers) { - total += number; - } - return total; + return sumAllWithFilter(numbers, new SumFilter() { + @Override + public boolean filter(Integer integer) { + return true; + } + }); } public static int sumAllEven(List numbers) { - int total = 0; - for (int number : numbers) { - if (number % 2 == 0) { - total += number; + return sumAllWithFilter(numbers, new SumFilter() { + @Override + public boolean filter(Integer integer) { + return integer % 2 == 0; } - } - return total; + }); } public static int sumAllOverThree(List numbers) { + return sumAllWithFilter(numbers, new SumFilter() { + @Override + public boolean filter(Integer integer) { + return integer > 3; + } + }); + } + + public static int sumAllWithFilter(List numbers, SumFilter sumFilter) { int total = 0; for (int number : numbers) { - if (number > 3) { + if (sumFilter.filter(number)) { total += number; } } diff --git a/src/main/java/nextstep/fp/SumFilter.java b/src/main/java/nextstep/fp/SumFilter.java new file mode 100644 index 0000000000..59c946c78b --- /dev/null +++ b/src/main/java/nextstep/fp/SumFilter.java @@ -0,0 +1,8 @@ +package nextstep.fp; + +import java.util.List; + +@FunctionalInterface +public interface SumFilter { + boolean filter(Integer integer); +} From 5286c1b9cd70a037bdca9bf56e8ee2d1c2586482 Mon Sep 17 00:00:00 2001 From: choincnp Date: Tue, 22 Apr 2025 15:39:24 +0900 Subject: [PATCH 03/14] refactor : replace Anonymous class to lambda expression --- src/main/java/nextstep/fp/Lambda.java | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/src/main/java/nextstep/fp/Lambda.java b/src/main/java/nextstep/fp/Lambda.java index 73b383d961..643740eb42 100644 --- a/src/main/java/nextstep/fp/Lambda.java +++ b/src/main/java/nextstep/fp/Lambda.java @@ -27,30 +27,15 @@ public void run() { } public static int sumAll(List numbers) { - return sumAllWithFilter(numbers, new SumFilter() { - @Override - public boolean filter(Integer integer) { - return true; - } - }); + return sumAllWithFilter(numbers, integer -> true); } public static int sumAllEven(List numbers) { - return sumAllWithFilter(numbers, new SumFilter() { - @Override - public boolean filter(Integer integer) { - return integer % 2 == 0; - } - }); + return sumAllWithFilter(numbers, integer -> integer % 2 == 0); } public static int sumAllOverThree(List numbers) { - return sumAllWithFilter(numbers, new SumFilter() { - @Override - public boolean filter(Integer integer) { - return integer > 3; - } - }); + return sumAllWithFilter(numbers, integer -> integer > 3); } public static int sumAllWithFilter(List numbers, SumFilter sumFilter) { From 94217233fd1612b2ab120c9ad991ff44cf299f4f Mon Sep 17 00:00:00 2001 From: choincnp Date: Tue, 22 Apr 2025 15:56:19 +0900 Subject: [PATCH 04/14] refactor : implement stream methods --- src/main/java/nextstep/fp/StreamStudy.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/main/java/nextstep/fp/StreamStudy.java b/src/main/java/nextstep/fp/StreamStudy.java index b446983a02..c070c58eeb 100644 --- a/src/main/java/nextstep/fp/StreamStudy.java +++ b/src/main/java/nextstep/fp/StreamStudy.java @@ -5,6 +5,7 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.Arrays; +import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; @@ -27,7 +28,14 @@ public static void printLongestWordTop100() throws IOException { .get("src/main/resources/fp/war-and-peace.txt")), StandardCharsets.UTF_8); List words = Arrays.asList(contents.split("[\\P{L}]+")); - // TODO 이 부분에 구현한다. + words.stream() + .filter(word -> word.length() > 12) + .sorted(Comparator.comparingInt(String::length).reversed()) + .distinct() + .limit(100) + .map(String::toLowerCase) + .forEach(System.out::println); + } public static List doubleNumbers(List numbers) { @@ -39,6 +47,9 @@ public static long sumAll(List numbers) { } public static long sumOverThreeAndDouble(List numbers) { - return 0; + return numbers.stream() + .filter(i -> i > 3) + .map(i -> i * 2) + .reduce(0, Integer::sum); } -} \ No newline at end of file +} From 9bdd0753c906cded27fc86212de30b35eed9eee3 Mon Sep 17 00:00:00 2001 From: choincnp Date: Tue, 22 Apr 2025 16:03:12 +0900 Subject: [PATCH 05/14] refactor : using optional method to filter results - 1 --- src/main/java/nextstep/optional/User.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/nextstep/optional/User.java b/src/main/java/nextstep/optional/User.java index 9614c2f43f..e1e7637be1 100644 --- a/src/main/java/nextstep/optional/User.java +++ b/src/main/java/nextstep/optional/User.java @@ -1,5 +1,7 @@ package nextstep.optional; +import java.util.Optional; + public class User { private String name; private Integer age; @@ -33,7 +35,10 @@ public static boolean ageIsInRange1(User user) { } public static boolean ageIsInRange2(User user) { - return false; + return Optional.ofNullable(user) + .map(User::getAge) + .filter(i -> i >= 30 && i <= 45) + .isPresent(); } @Override From c0f7bf57815a880731e1e27524d6dc4dec67ba5b Mon Sep 17 00:00:00 2001 From: choincnp Date: Tue, 22 Apr 2025 16:05:59 +0900 Subject: [PATCH 06/14] refactor : using optional method to filter results - 2 --- src/main/java/nextstep/optional/Users.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/nextstep/optional/Users.java b/src/main/java/nextstep/optional/Users.java index 6293040de8..2efd248a62 100644 --- a/src/main/java/nextstep/optional/Users.java +++ b/src/main/java/nextstep/optional/Users.java @@ -2,6 +2,7 @@ import java.util.Arrays; import java.util.List; +import java.util.Optional; public class Users { static final User DEFAULT_USER = new User("codesquad", 100); @@ -13,11 +14,9 @@ public class Users { new User("honux", 45)); User getUser(String name) { - for (User user : users) { - if (user.matchName(name)) { - return user; - } - } - return DEFAULT_USER; + return users.stream() + .filter(user -> user.getName().equals(name)) + .findFirst() + .orElse(DEFAULT_USER); } } From a305a44bcf5dd7e949e1997a06145257f3e546f7 Mon Sep 17 00:00:00 2001 From: choincnp Date: Tue, 22 Apr 2025 16:14:56 +0900 Subject: [PATCH 07/14] refactor : using optional method to filter results - 3 --- src/main/java/nextstep/optional/Expression.java | 13 ++++++------- src/test/java/nextstep/optional/ExpressionTest.java | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main/java/nextstep/optional/Expression.java b/src/main/java/nextstep/optional/Expression.java index 1c98cd6a62..25827bbc75 100644 --- a/src/main/java/nextstep/optional/Expression.java +++ b/src/main/java/nextstep/optional/Expression.java @@ -1,5 +1,7 @@ package nextstep.optional; +import java.util.Arrays; + enum Expression { PLUS("+"), MINUS("-"), TIMES("*"), DIVIDE("/"); @@ -14,12 +16,9 @@ private static boolean matchExpression(Expression e, String expression) { } static Expression of(String expression) { - for (Expression v : values()) { - if (matchExpression(v, expression)) { - return v; - } - } - - throw new IllegalArgumentException(String.format("%s는 사칙연산에 해당하지 않는 표현식입니다.", expression)); + return Arrays.stream(Expression.values()) + .filter(e -> matchExpression(e, expression)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(String.format("%s는 사칙연산에 해당하지 않는 표현식입니다.", expression))); } } diff --git a/src/test/java/nextstep/optional/ExpressionTest.java b/src/test/java/nextstep/optional/ExpressionTest.java index 3235626140..1a9f2bc773 100644 --- a/src/test/java/nextstep/optional/ExpressionTest.java +++ b/src/test/java/nextstep/optional/ExpressionTest.java @@ -17,6 +17,6 @@ public void notValidExpression() { assertThatIllegalArgumentException() .isThrownBy(() -> { Expression.of("&"); - }); + }).withMessage("&는 사칙연산에 해당하지 않는 표현식입니다."); } } From 605a7bba9028c5d831a00929a48d6b9260f02380 Mon Sep 17 00:00:00 2001 From: choincnp Date: Wed, 23 Apr 2025 02:31:31 +0900 Subject: [PATCH 08/14] test : Add domain - Line and some invariants test --- src/main/java/nextstep/domain/Line.java | 49 +++++++++++++++++++++ src/test/java/nextstep/domain/LineTest.java | 25 +++++++++++ 2 files changed, 74 insertions(+) create mode 100644 src/main/java/nextstep/domain/Line.java create mode 100644 src/test/java/nextstep/domain/LineTest.java diff --git a/src/main/java/nextstep/domain/Line.java b/src/main/java/nextstep/domain/Line.java new file mode 100644 index 0000000000..e668e4335a --- /dev/null +++ b/src/main/java/nextstep/domain/Line.java @@ -0,0 +1,49 @@ +package nextstep.domain; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.stream.IntStream; + +/** + * 사다리의 각 행을 관리하는 객체 + */ +public class Line { + private final int column; + private final List rowStatus; + private final Random random = new Random(); + + /* 생성자 */ + private Line(int column) { + this(column, new ArrayList<>(Collections.nCopies(column - 1, false))); + } + + // 테스트를 위한 생성자 + public Line(int column, List rowStatus) { + this.column = column; + this.rowStatus = rowStatus; + } + + /* 생성의도 명확하게 하는 팩토리 메서드 */ + public static Line ofColumn(int column) { + return new Line(column); + } + + /* 핵심 메서드 : 순회하면서 이전 상태를 참고해서 false면 랜덤, true면 false로 */ + public void generateRandomStatus() { + IntStream.range(0, column - 1) + .forEach(this::setRandomStatus); + } + + private void setRandomStatus(int index) { + boolean prevStatus = (index > 0 && rowStatus.get(index - 1)); + rowStatus.set(index, !prevStatus && random.nextBoolean()); + } + + /* 단순 정보 확인용 getter */ + public List rowStatus() { + return Collections.unmodifiableList(rowStatus); + } + +} diff --git a/src/test/java/nextstep/domain/LineTest.java b/src/test/java/nextstep/domain/LineTest.java new file mode 100644 index 0000000000..b86396001e --- /dev/null +++ b/src/test/java/nextstep/domain/LineTest.java @@ -0,0 +1,25 @@ +package nextstep.domain; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class LineTest { + + @Test + @DisplayName("한 라인에는 인접한 True가 없어야 한다.") + void adjacentPointTest() { + Line line = Line.ofColumn(5); + line.generateRandomStatus(); + List points = line.rowStatus(); + System.out.println("points = " + points); + IntStream.range(0, points.size() - 1) + .filter(points::get) + .forEach(i -> assertFalse(points.get(i + 1))); + } +} From 78384d2e2a0232352934dca71733b7c8e8bb0e86 Mon Sep 17 00:00:00 2001 From: choincnp Date: Wed, 23 Apr 2025 03:00:09 +0900 Subject: [PATCH 09/14] test : Add domain - Ladder and add prototype test --- src/main/java/nextstep/domain/Ladder.java | 50 +++++++++++++++++++ src/test/java/nextstep/domain/LadderTest.java | 36 +++++++++++++ src/test/java/nextstep/domain/LineTest.java | 2 - 3 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 src/main/java/nextstep/domain/Ladder.java create mode 100644 src/test/java/nextstep/domain/LadderTest.java diff --git a/src/main/java/nextstep/domain/Ladder.java b/src/main/java/nextstep/domain/Ladder.java new file mode 100644 index 0000000000..d3e7ac02be --- /dev/null +++ b/src/main/java/nextstep/domain/Ladder.java @@ -0,0 +1,50 @@ +package nextstep.domain; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +/** + * 사다리 + */ +public class Ladder { + private final List lines; + + // 테스트를 위한 생성자 + public Ladder(List lines) { + this.lines = lines; + } + + public static Ladder of(int column, int height) { + List lines = IntStream.range(0, height) // 전체 행 지정 + .mapToObj(i -> Line.ofColumn(column)) // 전체 열 생성 + .peek(Line::generateRandomStatus) // 사다리 만들기 + .collect(Collectors.toList()); + return new Ladder(lines); + } + + /* 핵심 메서드 - 사다리 타기 */ + public int play(int index) { + int position = index; + for (Line line : lines) { + List points = line.rowStatus(); + if (position > 0 && points.get(position - 1)) { + position = position - 1; + } else if (position < points.size() && points.get(position)) { + position = position + 1; + } + } + return position; + } + + /* 정보제공 */ + public int height() { + return lines.size(); + } + + public void print() { + for (Line line : lines) { + System.out.println(line.rowStatus()); + } + } +} diff --git a/src/test/java/nextstep/domain/LadderTest.java b/src/test/java/nextstep/domain/LadderTest.java new file mode 100644 index 0000000000..0aec7d708a --- /dev/null +++ b/src/test/java/nextstep/domain/LadderTest.java @@ -0,0 +1,36 @@ +package nextstep.domain; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class LadderTest { + + @Test + @DisplayName("높이에 맞춰서 Line이 생성된다.") + void createLinesTest() { + int columns = 4, height = 6; + Ladder ladder = Ladder.of(columns, height); + assertEquals(height, ladder.height()); + } + + @Test + @DisplayName("모든 행이 false로만 이루어져 있다면, 출발과 도착 위치가 같다.") + void playStraightTest() { + int columns = 4, height = 6; + List straightLines = IntStream.range(0, height) + .mapToObj(i -> new Line(columns, new ArrayList<>(Collections.nCopies(columns - 1, false)))) + .collect(Collectors.toList()); + Ladder ladder = new Ladder(straightLines); + for (int i = 0; i < columns; i++) { + assertEquals(i, ladder.play(i)); + } + } +} diff --git a/src/test/java/nextstep/domain/LineTest.java b/src/test/java/nextstep/domain/LineTest.java index b86396001e..27a1744e01 100644 --- a/src/test/java/nextstep/domain/LineTest.java +++ b/src/test/java/nextstep/domain/LineTest.java @@ -15,9 +15,7 @@ public class LineTest { @DisplayName("한 라인에는 인접한 True가 없어야 한다.") void adjacentPointTest() { Line line = Line.ofColumn(5); - line.generateRandomStatus(); List points = line.rowStatus(); - System.out.println("points = " + points); IntStream.range(0, points.size() - 1) .filter(points::get) .forEach(i -> assertFalse(points.get(i + 1))); From bf5c875fb7b08b889bd2ca857f8f1ab1e39e3f2f Mon Sep 17 00:00:00 2001 From: choincnp Date: Thu, 24 Apr 2025 01:17:10 +0900 Subject: [PATCH 10/14] refactor : split into small entity --- src/main/java/nextstep/Main.java | 22 +++++++ src/main/java/nextstep/domain/Edge.java | 39 ++++++++++++ src/main/java/nextstep/domain/Ladder.java | 14 ++--- src/main/java/nextstep/domain/Line.java | 60 ++++++++++++++----- src/test/java/nextstep/domain/LadderTest.java | 14 +++-- src/test/java/nextstep/domain/LineTest.java | 1 - 6 files changed, 118 insertions(+), 32 deletions(-) create mode 100644 src/main/java/nextstep/Main.java create mode 100644 src/main/java/nextstep/domain/Edge.java diff --git a/src/main/java/nextstep/Main.java b/src/main/java/nextstep/Main.java new file mode 100644 index 0000000000..87ff0ab41d --- /dev/null +++ b/src/main/java/nextstep/Main.java @@ -0,0 +1,22 @@ +package nextstep; + +import java.util.List; + +import nextstep.domain.Ladder; +import nextstep.view.InputView; +import nextstep.view.OutputView; + +public class Main { + public static void main(String[] args) { + /* I/O 선언 */ + InputView in = new InputView(); + OutputView out = new OutputView(); + + List name = in.getName(); + int height = in.getHeight(); + + Ladder ladder = Ladder.of(name.size(), height); + out.printResult(name, ladder); + + } +} diff --git a/src/main/java/nextstep/domain/Edge.java b/src/main/java/nextstep/domain/Edge.java new file mode 100644 index 0000000000..f6282c6843 --- /dev/null +++ b/src/main/java/nextstep/domain/Edge.java @@ -0,0 +1,39 @@ +package nextstep.domain; + +import java.util.Optional; + +/** + * Line 내부 각 Node를 연결하는 엣지 + */ +public class Edge { + private final int index; // 왼쪽 기준 노드를 인덱스로 잡음 + private final boolean connected; + + /* 기본 생성자 */ + public Edge(int index, boolean connected) { + if (index < 0) { + throw new IndexOutOfBoundsException("인덱스는 0이 될 수 없습니다."); + } + this.index = index; + this.connected = connected; + } + + /* Edge의 상태 반환 */ + public boolean isConnected() { + return connected; + } + + /* Optional로 연결되어 있다면 다른쪽 반환 */ + public Optional otherSide(int pos) { + if (!connected) { + return Optional.empty(); + } + if (pos == index) { + return Optional.of(index + 1); + } + if (pos == index + 1) { + return Optional.of(index); + } + return Optional.empty(); + } +} diff --git a/src/main/java/nextstep/domain/Ladder.java b/src/main/java/nextstep/domain/Ladder.java index d3e7ac02be..813126abbd 100644 --- a/src/main/java/nextstep/domain/Ladder.java +++ b/src/main/java/nextstep/domain/Ladder.java @@ -1,5 +1,6 @@ package nextstep.domain; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -27,12 +28,7 @@ public static Ladder of(int column, int height) { public int play(int index) { int position = index; for (Line line : lines) { - List points = line.rowStatus(); - if (position > 0 && points.get(position - 1)) { - position = position - 1; - } else if (position < points.size() && points.get(position)) { - position = position + 1; - } + position = line.move(position); } return position; } @@ -42,9 +38,7 @@ public int height() { return lines.size(); } - public void print() { - for (Line line : lines) { - System.out.println(line.rowStatus()); - } + public List lines() { + return Collections.unmodifiableList(lines); } } diff --git a/src/main/java/nextstep/domain/Line.java b/src/main/java/nextstep/domain/Line.java index e668e4335a..9d1aafa11d 100644 --- a/src/main/java/nextstep/domain/Line.java +++ b/src/main/java/nextstep/domain/Line.java @@ -1,28 +1,33 @@ package nextstep.domain; import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.Random; +import java.util.stream.Collectors; import java.util.stream.IntStream; /** * 사다리의 각 행을 관리하는 객체 + * 0 1 2 + * |-----|-----| */ public class Line { private final int column; - private final List rowStatus; + private final List edges = new ArrayList<>(); private final Random random = new Random(); - /* 생성자 */ + /** 기본 생성자: 모든 연결을 false 상태로 초기화 */ private Line(int column) { - this(column, new ArrayList<>(Collections.nCopies(column - 1, false))); + this(column, IntStream.range(0, column - 1) + .mapToObj(i -> new Edge(i, false)) + .collect(Collectors.toList())); } - // 테스트를 위한 생성자 - public Line(int column, List rowStatus) { + /** 내부 생성자: 미리 연산된 Edge 리스트를 주입 */ + private Line(int column, List edges) { this.column = column; - this.rowStatus = rowStatus; + this.edges.addAll(edges); } /* 생성의도 명확하게 하는 팩토리 메서드 */ @@ -30,20 +35,45 @@ public static Line ofColumn(int column) { return new Line(column); } - /* 핵심 메서드 : 순회하면서 이전 상태를 참고해서 false면 랜덤, true면 false로 */ + /** 테스트용 팩토리: 외부에서 상태 리스트 주입 */ + public static Line ofStatus(int column, List statuses) { + if (statuses.size() != column - 1) { + throw new IllegalArgumentException("statuses 크기는 column-1 이어야 합니다."); + } + List edges = IntStream.range(0, column - 1) + .mapToObj(i -> new Edge(i, statuses.get(i))) + .collect(Collectors.toList()); + return new Line(column, edges); + } + + /** 랜덤으로 연결 상태를 다시 생성 */ public void generateRandomStatus() { - IntStream.range(0, column - 1) - .forEach(this::setRandomStatus); + edges.clear(); + for (int i = 0; i < column - 1; i++) { + boolean prev = (i > 0 && edges.get(i - 1).isConnected()); + boolean conn = !prev && random.nextBoolean(); + edges.add(new Edge(i, conn)); + } } - private void setRandomStatus(int index) { - boolean prevStatus = (index > 0 && rowStatus.get(index - 1)); - rowStatus.set(index, !prevStatus && random.nextBoolean()); + /** + * 현재 위치(position)에서 이 라인을 타고 이동할 최종 인덱스를 반환. + */ + public int move(int position) { + for (Edge edge : edges) { + Optional next = edge.otherSide(position); + if (next.isPresent()) { + return next.get(); + } + } + return position; } - /* 단순 정보 확인용 getter */ + /** boolean 리스트*/ public List rowStatus() { - return Collections.unmodifiableList(rowStatus); + return edges.stream() + .map(Edge::isConnected) + .collect(Collectors.toUnmodifiableList()); } } diff --git a/src/test/java/nextstep/domain/LadderTest.java b/src/test/java/nextstep/domain/LadderTest.java index 0aec7d708a..b7fb913f37 100644 --- a/src/test/java/nextstep/domain/LadderTest.java +++ b/src/test/java/nextstep/domain/LadderTest.java @@ -2,7 +2,6 @@ import static org.junit.jupiter.api.Assertions.*; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -25,12 +24,15 @@ void createLinesTest() { @DisplayName("모든 행이 false로만 이루어져 있다면, 출발과 도착 위치가 같다.") void playStraightTest() { int columns = 4, height = 6; - List straightLines = IntStream.range(0, height) - .mapToObj(i -> new Line(columns, new ArrayList<>(Collections.nCopies(columns - 1, false)))) + List falseEdge = Collections.nCopies(columns - 1, false); + List straightLine = IntStream.range(0, height) + .mapToObj(i -> Line.ofStatus(columns, falseEdge)) .collect(Collectors.toList()); - Ladder ladder = new Ladder(straightLines); - for (int i = 0; i < columns; i++) { - assertEquals(i, ladder.play(i)); + + Ladder ladder = new Ladder(straightLine); + + for (int start = 0; start < columns; start++) { + assertEquals(start, ladder.play(start)); } } } diff --git a/src/test/java/nextstep/domain/LineTest.java b/src/test/java/nextstep/domain/LineTest.java index 27a1744e01..14135c6ee2 100644 --- a/src/test/java/nextstep/domain/LineTest.java +++ b/src/test/java/nextstep/domain/LineTest.java @@ -2,7 +2,6 @@ import static org.junit.jupiter.api.Assertions.*; -import java.util.ArrayList; import java.util.List; import java.util.stream.IntStream; From d935f02b423aaea373c4834c6a33a8ed22261245 Mon Sep 17 00:00:00 2001 From: choincnp Date: Thu, 24 Apr 2025 01:17:45 +0900 Subject: [PATCH 11/14] feat : Add I/O class / method --- src/main/java/nextstep/view/InputView.java | 50 ++++++++++++++++ src/main/java/nextstep/view/OutputView.java | 66 +++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 src/main/java/nextstep/view/InputView.java create mode 100644 src/main/java/nextstep/view/OutputView.java diff --git a/src/main/java/nextstep/view/InputView.java b/src/main/java/nextstep/view/InputView.java new file mode 100644 index 0000000000..106f138f45 --- /dev/null +++ b/src/main/java/nextstep/view/InputView.java @@ -0,0 +1,50 @@ +package nextstep.view; + +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; +import java.util.stream.Collectors; + +/** + * 입력 클래스 + */ +public class InputView { + private static final int MAX_NAME_LENGTH = 5; + private final Scanner scanner = new Scanner(System.in); + + /** 참여 인원 입력 */ + public List getName() { + System.out.println("참여할 사람 이름을 입력하세요. (이름은 쉼표(,)로 구분하세요)"); + String name = scanner.nextLine(); + return parseName(name); + } + + /** 사다리 높이 */ + public int getHeight() { + System.out.println("최대 사다리 높이는 몇 개인가요?"); + int height = scanner.nextInt(); + return height; + } + + /** 이름 파싱 */ + private List parseName(String input) { + List names = Arrays.stream(input.split(",")) + .map(String::trim) + .collect(Collectors.toList()); + // 빈 이름 체크 + if (names.stream().anyMatch(String::isEmpty)) { + throw new IllegalArgumentException("이름은 빈 문자열이 될 수 없습니다."); + } + // 길이 체크 + if (names.stream().anyMatch(this::isGraterThanMaxLength)) { + throw new IllegalArgumentException("이름은 최대 " + MAX_NAME_LENGTH + "자까지 허용됩니다."); + } + return names; + } + + /** 이름 길이 validation */ + private boolean isGraterThanMaxLength(String name) { + return name.length() > MAX_NAME_LENGTH; + } + +} diff --git a/src/main/java/nextstep/view/OutputView.java b/src/main/java/nextstep/view/OutputView.java new file mode 100644 index 0000000000..7710b03f58 --- /dev/null +++ b/src/main/java/nextstep/view/OutputView.java @@ -0,0 +1,66 @@ +package nextstep.view; + +import java.util.List; + +import nextstep.domain.Ladder; +import nextstep.domain.Line; + +/** + * 출력 클래스 + */ +public class OutputView { + private static final char V = '|'; + private static final char H = '-'; + + /** 결과 */ + public void printResult(List names, Ladder ladder) { + System.out.println("실행 결과"); + + int width = calculateWidth(names); + + // 1) 이름 줄 + System.out.println(formatNameLine(names, width)); + + // 2) 사다리 줄 + ladder.lines().forEach(line -> + System.out.println(formatLadderLine(line, width)) + ); + } + + /** 최대 이름 길이를 계산 */ + private static int calculateWidth(List names) { + return names.stream() + .mapToInt(String::length) + .max() + .orElse(0); + } + + /** 이름 formatting */ + private static String formatNameLine(List names, int width) { + StringBuilder sb = new StringBuilder(); + for (String name : names) { + sb.append(" ") + .append(padLeft(name, width)); + } + return sb.toString(); + } + + /** 사다리 한 행을 formatting */ + private static String formatLadderLine(Line line, int width) { + StringBuilder sb = new StringBuilder(); + sb.append(" ".repeat(width)).append(V); + for (boolean conn : line.rowStatus()) { + String fill = conn + ? String.valueOf(H).repeat(width) + : " ".repeat(width); + sb.append(fill).append(V); + } + return sb.toString(); + } + + /** 좌측 공백 */ + private static String padLeft(String s, int width) { + if (s.length() >= width) return s; + return " ".repeat(width - s.length()) + s; + } +} From 2a8c926732938bd8cc0ab55cd56cccb16c7cccd7 Mon Sep 17 00:00:00 2001 From: choincnp Date: Thu, 24 Apr 2025 10:47:48 +0900 Subject: [PATCH 12/14] feat : Refactor I/O class/method --- src/main/java/nextstep/Main.java | 3 +- src/main/java/nextstep/domain/Ladder.java | 9 ++++ src/main/java/nextstep/view/InputView.java | 25 ++++++++-- src/main/java/nextstep/view/OutputView.java | 52 ++++++++++++++++----- 4 files changed, 73 insertions(+), 16 deletions(-) diff --git a/src/main/java/nextstep/Main.java b/src/main/java/nextstep/Main.java index 87ff0ab41d..80b65afac5 100644 --- a/src/main/java/nextstep/Main.java +++ b/src/main/java/nextstep/Main.java @@ -13,10 +13,11 @@ public static void main(String[] args) { OutputView out = new OutputView(); List name = in.getName(); + List bonus = in.getBonus(name.size()); int height = in.getHeight(); Ladder ladder = Ladder.of(name.size(), height); - out.printResult(name, ladder); + out.printResult(name, ladder, bonus); } } diff --git a/src/main/java/nextstep/domain/Ladder.java b/src/main/java/nextstep/domain/Ladder.java index 813126abbd..364ac583b6 100644 --- a/src/main/java/nextstep/domain/Ladder.java +++ b/src/main/java/nextstep/domain/Ladder.java @@ -1,7 +1,9 @@ package nextstep.domain; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -41,4 +43,11 @@ public int height() { public List lines() { return Collections.unmodifiableList(lines); } + + public Map result() { + Map result = new HashMap<>(); + IntStream.range(0, lines.size()) + .forEach(i -> result.put(i, play(i))); + return result; + } } diff --git a/src/main/java/nextstep/view/InputView.java b/src/main/java/nextstep/view/InputView.java index 106f138f45..3885486e3e 100644 --- a/src/main/java/nextstep/view/InputView.java +++ b/src/main/java/nextstep/view/InputView.java @@ -26,11 +26,15 @@ public int getHeight() { return height; } + public List getBonus(int count) { + System.out.println("실행 결과를 입력하세요. (결과는 쉼표(,)로 구분하세요)"); + String bonus = scanner.nextLine(); + return parseBonus(bonus, count); + } + /** 이름 파싱 */ private List parseName(String input) { - List names = Arrays.stream(input.split(",")) - .map(String::trim) - .collect(Collectors.toList()); + List names = splitAndTrim(input); // 빈 이름 체크 if (names.stream().anyMatch(String::isEmpty)) { throw new IllegalArgumentException("이름은 빈 문자열이 될 수 없습니다."); @@ -47,4 +51,19 @@ private boolean isGraterThanMaxLength(String name) { return name.length() > MAX_NAME_LENGTH; } + /** 결과 파싱 */ + private List parseBonus(String input, int count) { + List bonus = splitAndTrim(input); + // 보너스 개수 validation + if (bonus.size() != count) { + throw new IllegalArgumentException("보너스 개수는 사람 수와 일치해야 합니다."); + } + return bonus; + } + + private List splitAndTrim(String input) { + return Arrays.stream(input.split(",")) + .map(String::trim) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/nextstep/view/OutputView.java b/src/main/java/nextstep/view/OutputView.java index 7710b03f58..86c0243d80 100644 --- a/src/main/java/nextstep/view/OutputView.java +++ b/src/main/java/nextstep/view/OutputView.java @@ -1,6 +1,8 @@ package nextstep.view; import java.util.List; +import java.util.Map; +import java.util.stream.IntStream; import nextstep.domain.Ladder; import nextstep.domain.Line; @@ -13,8 +15,8 @@ public class OutputView { private static final char H = '-'; /** 결과 */ - public void printResult(List names, Ladder ladder) { - System.out.println("실행 결과"); + public void printResult(List names, Ladder ladder, List bonus) { + System.out.println("사다리 결과"); int width = calculateWidth(names); @@ -25,14 +27,39 @@ public void printResult(List names, Ladder ladder) { ladder.lines().forEach(line -> System.out.println(formatLadderLine(line, width)) ); + + // 3) 보너스 + System.out.println(formatNameLine(bonus, width)); + + // 5) 전체 결과 + System.out.println(printPersonalResult(names, ladder, bonus)); + } + + // TODO 분기처리 + public void printResult(String input) { + + } + + public String printPersonalResult(List names, Ladder ladder, List bonus) { + StringBuilder builder = new StringBuilder(); + System.out.println("실행 결과"); + Map result = ladder.result(); + + IntStream.range(0, names.size()) + .forEach(i -> builder.append(names.get(i)).append(" : ").append(bonus.get(matchResult(i, result))).append("\n")); + return builder.toString(); + } + + private int matchResult(int index, Map result) { + return result.get(index); } /** 최대 이름 길이를 계산 */ private static int calculateWidth(List names) { return names.stream() - .mapToInt(String::length) - .max() - .orElse(0); + .mapToInt(String::length) + .max() + .orElse(0); } /** 이름 formatting */ @@ -40,11 +67,18 @@ private static String formatNameLine(List names, int width) { StringBuilder sb = new StringBuilder(); for (String name : names) { sb.append(" ") - .append(padLeft(name, width)); + .append(padLeft(name, width)); } return sb.toString(); } + /** 좌측 공백 */ + private static String padLeft(String s, int width) { + if (s.length() >= width) + return s; + return " ".repeat(width - s.length()) + s; + } + /** 사다리 한 행을 formatting */ private static String formatLadderLine(Line line, int width) { StringBuilder sb = new StringBuilder(); @@ -57,10 +91,4 @@ private static String formatLadderLine(Line line, int width) { } return sb.toString(); } - - /** 좌측 공백 */ - private static String padLeft(String s, int width) { - if (s.length() >= width) return s; - return " ".repeat(width - s.length()) + s; - } } From d9d7c8214a9f203d36c9deb3650167fcfd6a2e7c Mon Sep 17 00:00:00 2001 From: choincnp Date: Thu, 24 Apr 2025 16:15:47 +0900 Subject: [PATCH 13/14] refactor : change outputs into fixed type, use strategy pattern --- src/main/java/nextstep/Main.java | 9 ++- .../java/nextstep/domain/EdgeStrategy.java | 16 +++++ src/main/java/nextstep/domain/Height.java | 39 +++++++++++ src/main/java/nextstep/domain/Ladder.java | 4 +- src/main/java/nextstep/domain/Line.java | 64 ++++++++----------- .../nextstep/domain/ManualEdgeStrategy.java | 22 +++++++ src/main/java/nextstep/domain/Name.java | 51 +++++++++++++++ src/main/java/nextstep/domain/Names.java | 47 ++++++++++++++ .../nextstep/domain/RandomEdgeStrategy.java | 15 +++++ src/main/java/nextstep/view/InputView.java | 30 ++------- src/main/java/nextstep/view/OutputView.java | 41 +++++------- src/test/java/nextstep/domain/HeightTest.java | 18 ++++++ src/test/java/nextstep/domain/LadderTest.java | 2 +- src/test/java/nextstep/domain/LineTest.java | 4 +- src/test/java/nextstep/domain/NameTest.java | 29 +++++++++ src/test/java/nextstep/domain/NamesTest.java | 15 +++++ 16 files changed, 309 insertions(+), 97 deletions(-) create mode 100644 src/main/java/nextstep/domain/EdgeStrategy.java create mode 100644 src/main/java/nextstep/domain/Height.java create mode 100644 src/main/java/nextstep/domain/ManualEdgeStrategy.java create mode 100644 src/main/java/nextstep/domain/Name.java create mode 100644 src/main/java/nextstep/domain/Names.java create mode 100644 src/main/java/nextstep/domain/RandomEdgeStrategy.java create mode 100644 src/test/java/nextstep/domain/HeightTest.java create mode 100644 src/test/java/nextstep/domain/NameTest.java create mode 100644 src/test/java/nextstep/domain/NamesTest.java diff --git a/src/main/java/nextstep/Main.java b/src/main/java/nextstep/Main.java index 87ff0ab41d..0d4be7b2b0 100644 --- a/src/main/java/nextstep/Main.java +++ b/src/main/java/nextstep/Main.java @@ -1,8 +1,7 @@ package nextstep; -import java.util.List; - import nextstep.domain.Ladder; +import nextstep.domain.Names; import nextstep.view.InputView; import nextstep.view.OutputView; @@ -12,11 +11,11 @@ public static void main(String[] args) { InputView in = new InputView(); OutputView out = new OutputView(); - List name = in.getName(); + Names names = in.getName(); int height = in.getHeight(); - Ladder ladder = Ladder.of(name.size(), height); - out.printResult(name, ladder); + Ladder ladder = Ladder.of(names.size(), height); + out.printResult(names, ladder); } } diff --git a/src/main/java/nextstep/domain/EdgeStrategy.java b/src/main/java/nextstep/domain/EdgeStrategy.java new file mode 100644 index 0000000000..5c27448f95 --- /dev/null +++ b/src/main/java/nextstep/domain/EdgeStrategy.java @@ -0,0 +1,16 @@ +package nextstep.domain; + +/** + * 단일 Edge 생성 시 전략 인터페이스 + */ +public interface EdgeStrategy { + /** + * 0 1 2 : index + * |-----| | : Edge + * true false : connected + * @param index 이전 인덱스 + * @param connected 이전 Edge의 연결 여부 + * @return |-----| 으로 반환받아야 할 경우 true + */ + boolean generate(int index, boolean connected); +} diff --git a/src/main/java/nextstep/domain/Height.java b/src/main/java/nextstep/domain/Height.java new file mode 100644 index 0000000000..515c9d4fc7 --- /dev/null +++ b/src/main/java/nextstep/domain/Height.java @@ -0,0 +1,39 @@ +package nextstep.domain; + +import java.util.Objects; + +public class Height { + private final Integer height; + + private Height(Integer height) { + validate(height); + this.height = height; + } + + public static Height of(Integer height) { + return new Height(height); + } + + private void validate(int height) { + if (height <= 0) { + throw new IllegalArgumentException("높이는 0이나 음수가 될 수 없습니다."); + } + } + + public int height() { + return height; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) + return false; + Height height1 = (Height)o; + return Objects.equals(height, height1.height); + } + + @Override + public int hashCode() { + return Objects.hashCode(height); + } +} diff --git a/src/main/java/nextstep/domain/Ladder.java b/src/main/java/nextstep/domain/Ladder.java index 813126abbd..9ab34a229b 100644 --- a/src/main/java/nextstep/domain/Ladder.java +++ b/src/main/java/nextstep/domain/Ladder.java @@ -11,15 +11,13 @@ public class Ladder { private final List lines; - // 테스트를 위한 생성자 public Ladder(List lines) { this.lines = lines; } public static Ladder of(int column, int height) { List lines = IntStream.range(0, height) // 전체 행 지정 - .mapToObj(i -> Line.ofColumn(column)) // 전체 열 생성 - .peek(Line::generateRandomStatus) // 사다리 만들기 + .mapToObj(i -> Line.ofRandom(column)) // 전체 열 생성 .collect(Collectors.toList()); return new Ladder(lines); } diff --git a/src/main/java/nextstep/domain/Line.java b/src/main/java/nextstep/domain/Line.java index 9d1aafa11d..8d8c64f1a4 100644 --- a/src/main/java/nextstep/domain/Line.java +++ b/src/main/java/nextstep/domain/Line.java @@ -3,9 +3,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; -import java.util.Random; -import java.util.stream.Collectors; -import java.util.stream.IntStream; /** * 사다리의 각 행을 관리하는 객체 @@ -15,14 +12,6 @@ public class Line { private final int column; private final List edges = new ArrayList<>(); - private final Random random = new Random(); - - /** 기본 생성자: 모든 연결을 false 상태로 초기화 */ - private Line(int column) { - this(column, IntStream.range(0, column - 1) - .mapToObj(i -> new Edge(i, false)) - .collect(Collectors.toList())); - } /** 내부 생성자: 미리 연산된 Edge 리스트를 주입 */ private Line(int column, List edges) { @@ -30,30 +19,32 @@ private Line(int column, List edges) { this.edges.addAll(edges); } - /* 생성의도 명확하게 하는 팩토리 메서드 */ - public static Line ofColumn(int column) { - return new Line(column); - } - - /** 테스트용 팩토리: 외부에서 상태 리스트 주입 */ - public static Line ofStatus(int column, List statuses) { - if (statuses.size() != column - 1) { - throw new IllegalArgumentException("statuses 크기는 column-1 이어야 합니다."); + /** + * 전략 패턴을 이용한 팩토리 메서드 + */ + private static Line of(int column, EdgeStrategy strategy) { + List edges = new ArrayList<>(); + boolean prev = false; + for (int i = 0; i < column - 1; i++) { + boolean connected = strategy.generate(i, prev); + edges.add(new Edge(i, connected)); + prev = connected; } - List edges = IntStream.range(0, column - 1) - .mapToObj(i -> new Edge(i, statuses.get(i))) - .collect(Collectors.toList()); return new Line(column, edges); } - /** 랜덤으로 연결 상태를 다시 생성 */ - public void generateRandomStatus() { - edges.clear(); - for (int i = 0; i < column - 1; i++) { - boolean prev = (i > 0 && edges.get(i - 1).isConnected()); - boolean conn = !prev && random.nextBoolean(); - edges.add(new Edge(i, conn)); - } + /** + * 자동 라인 생성 + */ + public static Line ofRandom(int column) { + return of(column, new RandomEdgeStrategy()); + } + + /** + * 라인 수동 생성 + */ + public static Line ofManual(int column, List statues) { + return of(column, new ManualEdgeStrategy(statues)); } /** @@ -69,11 +60,12 @@ public int move(int position) { return position; } - /** boolean 리스트*/ - public List rowStatus() { - return edges.stream() - .map(Edge::isConnected) - .collect(Collectors.toUnmodifiableList()); + public List statues() { + List statues = new ArrayList<>(); + for (Edge edge : edges) { + statues.add(edge.isConnected()); + } + return statues; } } diff --git a/src/main/java/nextstep/domain/ManualEdgeStrategy.java b/src/main/java/nextstep/domain/ManualEdgeStrategy.java new file mode 100644 index 0000000000..48ff243320 --- /dev/null +++ b/src/main/java/nextstep/domain/ManualEdgeStrategy.java @@ -0,0 +1,22 @@ +package nextstep.domain; + +import java.util.List; + +/** + * 수동으로 라인 잡아줄 경우 + */ +public class ManualEdgeStrategy implements EdgeStrategy { + private final List statues; + + public ManualEdgeStrategy(List statues) { + this.statues = statues; + } + + @Override + public boolean generate(int index, boolean connected) { + if (index >= statues.size() || index < 0) { + throw new IndexOutOfBoundsException(); + } + return statues.get(index); + } +} diff --git a/src/main/java/nextstep/domain/Name.java b/src/main/java/nextstep/domain/Name.java new file mode 100644 index 0000000000..56bd1e9b06 --- /dev/null +++ b/src/main/java/nextstep/domain/Name.java @@ -0,0 +1,51 @@ +package nextstep.domain; + +import java.util.Objects; + +/** + * 이름 클래스 + */ +public class Name { + private static final Integer MAX_NAME_LENGTH = 5; + private final String name; + + private Name(String name) { + validate(name); + this.name = name; + } + + public static Name of(String name) { + return new Name(name); + } + + public String getName() { + return name; + } + + private void validate(String input) { + if (input == null || input.trim().isEmpty()) { + throw new IllegalArgumentException("이름은 빈 문자열이 될 수 없습니다."); + } + if (input.length() > MAX_NAME_LENGTH) { + throw new IllegalArgumentException("이름은 최대 " + MAX_NAME_LENGTH + "자까지 허용됩니다."); + } + } + + @Override + public String toString() { + return name; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) + return false; + Name name1 = (Name)o; + return Objects.equals(name, name1.name); + } + + @Override + public int hashCode() { + return Objects.hashCode(name); + } +} diff --git a/src/main/java/nextstep/domain/Names.java b/src/main/java/nextstep/domain/Names.java new file mode 100644 index 0000000000..fafd374da0 --- /dev/null +++ b/src/main/java/nextstep/domain/Names.java @@ -0,0 +1,47 @@ +package nextstep.domain; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 전체 인원 + */ +public class Names { + private final List names; + + public Names(String names) { + List list = makeNamesStringToList(names); + validate(list); + this.names = list; + } + + public static Names of(String name) { + return new Names(name); + } + + private void validate(List names) { + int distinct = (int)names.stream() + .distinct() + .count(); + if (distinct != names.size()) { + throw new IllegalArgumentException("중복된 이름은 올 수 없습니다."); + } + } + + private List makeNamesStringToList(String names) { + return Arrays.stream(names.split(",")) + .map(String::trim) + .map(Name::of) + .collect(Collectors.toList()); + } + + public int size() { + return names.size(); + } + + public List unmodifiableNames() { + return Collections.unmodifiableList(names); + } +} diff --git a/src/main/java/nextstep/domain/RandomEdgeStrategy.java b/src/main/java/nextstep/domain/RandomEdgeStrategy.java new file mode 100644 index 0000000000..c9cac1054d --- /dev/null +++ b/src/main/java/nextstep/domain/RandomEdgeStrategy.java @@ -0,0 +1,15 @@ +package nextstep.domain; + +import java.util.Random; + +/** + * 랜덤으로 라인 잡아줄 경우 + */ +public class RandomEdgeStrategy implements EdgeStrategy { + private static final Random RANDOM = new Random(); + + @Override + public boolean generate(int index, boolean connected) { + return !connected && RANDOM.nextBoolean(); + } +} diff --git a/src/main/java/nextstep/view/InputView.java b/src/main/java/nextstep/view/InputView.java index 106f138f45..e25967a9be 100644 --- a/src/main/java/nextstep/view/InputView.java +++ b/src/main/java/nextstep/view/InputView.java @@ -1,9 +1,8 @@ package nextstep.view; -import java.util.Arrays; -import java.util.List; import java.util.Scanner; -import java.util.stream.Collectors; + +import nextstep.domain.Names; /** * 입력 클래스 @@ -13,10 +12,10 @@ public class InputView { private final Scanner scanner = new Scanner(System.in); /** 참여 인원 입력 */ - public List getName() { + public Names getName() { System.out.println("참여할 사람 이름을 입력하세요. (이름은 쉼표(,)로 구분하세요)"); String name = scanner.nextLine(); - return parseName(name); + return new Names(name); } /** 사다리 높이 */ @@ -26,25 +25,4 @@ public int getHeight() { return height; } - /** 이름 파싱 */ - private List parseName(String input) { - List names = Arrays.stream(input.split(",")) - .map(String::trim) - .collect(Collectors.toList()); - // 빈 이름 체크 - if (names.stream().anyMatch(String::isEmpty)) { - throw new IllegalArgumentException("이름은 빈 문자열이 될 수 없습니다."); - } - // 길이 체크 - if (names.stream().anyMatch(this::isGraterThanMaxLength)) { - throw new IllegalArgumentException("이름은 최대 " + MAX_NAME_LENGTH + "자까지 허용됩니다."); - } - return names; - } - - /** 이름 길이 validation */ - private boolean isGraterThanMaxLength(String name) { - return name.length() > MAX_NAME_LENGTH; - } - } diff --git a/src/main/java/nextstep/view/OutputView.java b/src/main/java/nextstep/view/OutputView.java index 7710b03f58..602b46c523 100644 --- a/src/main/java/nextstep/view/OutputView.java +++ b/src/main/java/nextstep/view/OutputView.java @@ -4,6 +4,8 @@ import nextstep.domain.Ladder; import nextstep.domain.Line; +import nextstep.domain.Name; +import nextstep.domain.Names; /** * 출력 클래스 @@ -11,56 +13,47 @@ public class OutputView { private static final char V = '|'; private static final char H = '-'; + private static final int WIDTH = 5; /** 결과 */ - public void printResult(List names, Ladder ladder) { + public void printResult(Names names, Ladder ladder) { System.out.println("실행 결과"); - int width = calculateWidth(names); - // 1) 이름 줄 - System.out.println(formatNameLine(names, width)); + System.out.println(formatNameLine(names)); // 2) 사다리 줄 ladder.lines().forEach(line -> - System.out.println(formatLadderLine(line, width)) + System.out.println(formatLadderLine(line)) ); } - /** 최대 이름 길이를 계산 */ - private static int calculateWidth(List names) { - return names.stream() - .mapToInt(String::length) - .max() - .orElse(0); - } - /** 이름 formatting */ - private static String formatNameLine(List names, int width) { + private static String formatNameLine(Names names) { StringBuilder sb = new StringBuilder(); - for (String name : names) { + for (Name name : names.unmodifiableNames()) { sb.append(" ") - .append(padLeft(name, width)); + .append(padLeft(name.toString())); } return sb.toString(); } /** 사다리 한 행을 formatting */ - private static String formatLadderLine(Line line, int width) { + private static String formatLadderLine(Line line) { StringBuilder sb = new StringBuilder(); - sb.append(" ".repeat(width)).append(V); - for (boolean conn : line.rowStatus()) { + sb.append(" ".repeat(WIDTH)).append(V); + for (boolean conn : line.statues()) { String fill = conn - ? String.valueOf(H).repeat(width) - : " ".repeat(width); + ? String.valueOf(H).repeat(WIDTH) + : " ".repeat(WIDTH); sb.append(fill).append(V); } return sb.toString(); } /** 좌측 공백 */ - private static String padLeft(String s, int width) { - if (s.length() >= width) return s; - return " ".repeat(width - s.length()) + s; + private static String padLeft(String s) { + if (s.length() >= WIDTH) return s; + return " ".repeat(WIDTH - s.length()) + s; } } diff --git a/src/test/java/nextstep/domain/HeightTest.java b/src/test/java/nextstep/domain/HeightTest.java new file mode 100644 index 0000000000..306f93b25b --- /dev/null +++ b/src/test/java/nextstep/domain/HeightTest.java @@ -0,0 +1,18 @@ +package nextstep.domain; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +public class HeightTest { + @ParameterizedTest + @ValueSource(ints = {0, -1}) + @DisplayName("높이는 0이나 음수가 될 수 없다") + void negativeNumberTest(int number) { + assertThatThrownBy( + () -> Height.of(number) + ).isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/nextstep/domain/LadderTest.java b/src/test/java/nextstep/domain/LadderTest.java index b7fb913f37..16775da913 100644 --- a/src/test/java/nextstep/domain/LadderTest.java +++ b/src/test/java/nextstep/domain/LadderTest.java @@ -26,7 +26,7 @@ void playStraightTest() { int columns = 4, height = 6; List falseEdge = Collections.nCopies(columns - 1, false); List straightLine = IntStream.range(0, height) - .mapToObj(i -> Line.ofStatus(columns, falseEdge)) + .mapToObj(i -> Line.ofManual(columns, falseEdge)) .collect(Collectors.toList()); Ladder ladder = new Ladder(straightLine); diff --git a/src/test/java/nextstep/domain/LineTest.java b/src/test/java/nextstep/domain/LineTest.java index 14135c6ee2..fe2108e4da 100644 --- a/src/test/java/nextstep/domain/LineTest.java +++ b/src/test/java/nextstep/domain/LineTest.java @@ -13,8 +13,8 @@ public class LineTest { @Test @DisplayName("한 라인에는 인접한 True가 없어야 한다.") void adjacentPointTest() { - Line line = Line.ofColumn(5); - List points = line.rowStatus(); + Line line = Line.ofRandom(5); + List points = line.statues(); IntStream.range(0, points.size() - 1) .filter(points::get) .forEach(i -> assertFalse(points.get(i + 1))); diff --git a/src/test/java/nextstep/domain/NameTest.java b/src/test/java/nextstep/domain/NameTest.java new file mode 100644 index 0000000000..0a15a76c17 --- /dev/null +++ b/src/test/java/nextstep/domain/NameTest.java @@ -0,0 +1,29 @@ +package nextstep.domain; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.NullAndEmptySource; + +public class NameTest { + @ParameterizedTest + @CsvSource({ + "neojjc" + }) + @DisplayName("이름은 최대 5글자까지 가능하다.") + void nameRequirementsTest(String name) { + Assertions.assertThatThrownBy( + () -> Name.of(name) + ).isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @NullAndEmptySource + @DisplayName("이름은 빈 문자열이 될 수 없다.") + void nameValidationTest(String name) { + Assertions.assertThatThrownBy( + () -> Name.of(name) + ).isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/nextstep/domain/NamesTest.java b/src/test/java/nextstep/domain/NamesTest.java new file mode 100644 index 0000000000..f7fe77537d --- /dev/null +++ b/src/test/java/nextstep/domain/NamesTest.java @@ -0,0 +1,15 @@ +package nextstep.domain; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class NamesTest { + @Test + @DisplayName("중복된 이름 불가능") + public void validate() { + Assertions.assertThatThrownBy( + () -> new Names("pobi, pobi") + ).isInstanceOf(IllegalArgumentException.class); + } +} From d5ced350773a1152233454d9a2a67227c353836c Mon Sep 17 00:00:00 2001 From: choincnp Date: Thu, 24 Apr 2025 19:06:36 +0900 Subject: [PATCH 14/14] refactor : change I/O views and modified some logics --- src/main/java/nextstep/Main.java | 25 ++++++- src/main/java/nextstep/domain/Bonus.java | 29 +++++++++ .../java/nextstep/domain/LadderResult.java | 26 ++++++++ src/main/java/nextstep/domain/Name.java | 7 +- src/main/java/nextstep/domain/Names.java | 6 ++ src/main/java/nextstep/view/InputView.java | 37 +++++------ src/main/java/nextstep/view/OutputView.java | 65 +++++++++++-------- 7 files changed, 140 insertions(+), 55 deletions(-) create mode 100644 src/main/java/nextstep/domain/Bonus.java create mode 100644 src/main/java/nextstep/domain/LadderResult.java diff --git a/src/main/java/nextstep/Main.java b/src/main/java/nextstep/Main.java index 9336e70cdc..b9e6acab73 100644 --- a/src/main/java/nextstep/Main.java +++ b/src/main/java/nextstep/Main.java @@ -1,6 +1,11 @@ package nextstep; +import java.util.List; + +import nextstep.domain.Bonus; import nextstep.domain.Ladder; +import nextstep.domain.LadderResult; +import nextstep.domain.Name; import nextstep.domain.Names; import nextstep.view.InputView; import nextstep.view.OutputView; @@ -12,11 +17,27 @@ public static void main(String[] args) { OutputView out = new OutputView(); Names names = in.getName(); - List bonus = in.getBonus(name.size()); + List bonus = in.getBonus(names.size()); int height = in.getHeight(); Ladder ladder = Ladder.of(names.size(), height); - out.printResult(names, ladder, bonus); + LadderResult result = new LadderResult(ladder); + out.printLadder(names, ladder, bonus); + while (true) { + String target = in.readTarget().trim(); + boolean isAll = "all".equals(target); + if (isAll) { + out.printAllResults(names, result, bonus); + break; + } + Name n = Name.of(target); + int idx = names.unmodifiableNames().indexOf(n); + if (idx < 0) { + out.printPersonalResult(-1, result, bonus); + continue; + } + out.printPersonalResult(idx, result, bonus); + } } } diff --git a/src/main/java/nextstep/domain/Bonus.java b/src/main/java/nextstep/domain/Bonus.java new file mode 100644 index 0000000000..78e59b6565 --- /dev/null +++ b/src/main/java/nextstep/domain/Bonus.java @@ -0,0 +1,29 @@ +package nextstep.domain; + +public class Bonus { + private static final Integer MAX_NAME_LENGTH = 5; + private final String bonusName; + + public Bonus(String bonusName) { + validate(bonusName); + this.bonusName = bonusName; + } + + public static Bonus of(String bonusName) { + return new Bonus(bonusName); + } + + private void validate(String input) { + if (input == null || input.trim().isEmpty()) { + throw new IllegalArgumentException("이름은 빈 문자열이 될 수 없습니다."); + } + if (input.length() > MAX_NAME_LENGTH) { + throw new IllegalArgumentException("이름은 최대 " + MAX_NAME_LENGTH + "자까지 허용됩니다."); + } + } + + @Override + public String toString() { + return bonusName; + } +} diff --git a/src/main/java/nextstep/domain/LadderResult.java b/src/main/java/nextstep/domain/LadderResult.java new file mode 100644 index 0000000000..000ad8683e --- /dev/null +++ b/src/main/java/nextstep/domain/LadderResult.java @@ -0,0 +1,26 @@ +package nextstep.domain; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class LadderResult { + private final Map result; + + public LadderResult(Ladder ladder) { + result = ladder.result(); + } + + public List getOrderedBonuses(List bonusList) { + List ordered = new ArrayList<>(bonusList.size()); + for (int i = 0; i < bonusList.size(); i++) { + int bonusIndex = result.get(i); + ordered.add(bonusList.get(bonusIndex)); + } + return ordered; + } + + public int get(int index) { + return result.get(index); + } +} diff --git a/src/main/java/nextstep/domain/Name.java b/src/main/java/nextstep/domain/Name.java index 56bd1e9b06..6ab63328a6 100644 --- a/src/main/java/nextstep/domain/Name.java +++ b/src/main/java/nextstep/domain/Name.java @@ -18,10 +18,6 @@ public static Name of(String name) { return new Name(name); } - public String getName() { - return name; - } - private void validate(String input) { if (input == null || input.trim().isEmpty()) { throw new IllegalArgumentException("이름은 빈 문자열이 될 수 없습니다."); @@ -29,6 +25,9 @@ private void validate(String input) { if (input.length() > MAX_NAME_LENGTH) { throw new IllegalArgumentException("이름은 최대 " + MAX_NAME_LENGTH + "자까지 허용됩니다."); } + if (input.equals("all")) { + throw new IllegalArgumentException("사용할 수 없는 이름입니다."); + } } @Override diff --git a/src/main/java/nextstep/domain/Names.java b/src/main/java/nextstep/domain/Names.java index fafd374da0..1412981cb8 100644 --- a/src/main/java/nextstep/domain/Names.java +++ b/src/main/java/nextstep/domain/Names.java @@ -3,7 +3,9 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * 전체 인원 @@ -41,6 +43,10 @@ public int size() { return names.size(); } + public int getIndex(Name name) { + return names.indexOf(name); + } + public List unmodifiableNames() { return Collections.unmodifiableList(names); } diff --git a/src/main/java/nextstep/view/InputView.java b/src/main/java/nextstep/view/InputView.java index 041b5130d3..1278bf1620 100644 --- a/src/main/java/nextstep/view/InputView.java +++ b/src/main/java/nextstep/view/InputView.java @@ -1,14 +1,18 @@ package nextstep.view; +import java.util.Arrays; +import java.util.List; import java.util.Scanner; +import java.util.stream.Collectors; +import nextstep.domain.Bonus; +import nextstep.domain.Name; import nextstep.domain.Names; /** * 입력 클래스 */ public class InputView { - private static final int MAX_NAME_LENGTH = 5; private final Scanner scanner = new Scanner(System.in); /** 참여 인원 입력 */ @@ -22,37 +26,25 @@ public Names getName() { public int getHeight() { System.out.println("최대 사다리 높이는 몇 개인가요?"); int height = scanner.nextInt(); + scanner.nextLine(); return height; } - public List getBonus(int count) { + public List getBonus(int count) { System.out.println("실행 결과를 입력하세요. (결과는 쉼표(,)로 구분하세요)"); String bonus = scanner.nextLine(); return parseBonus(bonus, count); } - /** 이름 파싱 */ - private List parseName(String input) { - List names = splitAndTrim(input); - // 빈 이름 체크 - if (names.stream().anyMatch(String::isEmpty)) { - throw new IllegalArgumentException("이름은 빈 문자열이 될 수 없습니다."); - } - // 길이 체크 - if (names.stream().anyMatch(this::isGraterThanMaxLength)) { - throw new IllegalArgumentException("이름은 최대 " + MAX_NAME_LENGTH + "자까지 허용됩니다."); - } - return names; - } - - /** 이름 길이 validation */ - private boolean isGraterThanMaxLength(String name) { - return name.length() > MAX_NAME_LENGTH; + public String readTarget() { + System.out.println("결과를 보고 싶은 사람은?"); + String target = scanner.nextLine(); + return target; } /** 결과 파싱 */ - private List parseBonus(String input, int count) { - List bonus = splitAndTrim(input); + private List parseBonus(String input, int count) { + List bonus = splitAndTrim(input); // 보너스 개수 validation if (bonus.size() != count) { throw new IllegalArgumentException("보너스 개수는 사람 수와 일치해야 합니다."); @@ -60,9 +52,10 @@ private List parseBonus(String input, int count) { return bonus; } - private List splitAndTrim(String input) { + private List splitAndTrim(String input) { return Arrays.stream(input.split(",")) .map(String::trim) + .map(Bonus::new) .collect(Collectors.toList()); } } diff --git a/src/main/java/nextstep/view/OutputView.java b/src/main/java/nextstep/view/OutputView.java index f81131a262..023663c0ba 100644 --- a/src/main/java/nextstep/view/OutputView.java +++ b/src/main/java/nextstep/view/OutputView.java @@ -1,10 +1,10 @@ package nextstep.view; import java.util.List; -import java.util.Map; -import java.util.stream.IntStream; +import nextstep.domain.Bonus; import nextstep.domain.Ladder; +import nextstep.domain.LadderResult; import nextstep.domain.Line; import nextstep.domain.Name; import nextstep.domain.Names; @@ -18,41 +18,42 @@ public class OutputView { private static final int WIDTH = 5; /** 결과 */ - public void printResult(Names names, Ladder ladder) { - System.out.println("실행 결과"); + public void printLadder(Names names, Ladder ladder, List bonusList) { + System.out.println("사다리 결과"); // 1) 이름 줄 System.out.println(formatNameLine(names)); // 2) 사다리 줄 - ladder.lines().forEach(line -> - System.out.println(formatLadderLine(line)) - ); + ladder.lines() + .forEach(line -> System.out.println(formatLadderLine(line))); // 3) 보너스 - System.out.println(formatNameLine(bonus, width)); - - // 5) 전체 결과 - System.out.println(printPersonalResult(names, ladder, bonus)); - } - - // TODO 분기처리 - public void printResult(String input) { - + System.out.println(formatBonusLine(bonusList)); } - public String printPersonalResult(List names, Ladder ladder, List bonus) { - StringBuilder builder = new StringBuilder(); + /** + * 단일 결과 + */ + public void printPersonalResult(int index, LadderResult result, List bonusList) { System.out.println("실행 결과"); - Map result = ladder.result(); - - IntStream.range(0, names.size()) - .forEach(i -> builder.append(names.get(i)).append(" : ").append(bonus.get(matchResult(i, result))).append("\n")); - return builder.toString(); + if (index == -1) { + System.out.println("게임에 참여하지 않은 사람입니다."); + return; + } + System.out.println(bonusList.get(result.get(index))); } - private int matchResult(int index, Map result) { - return result.get(index); + /** + * all 조회 결과 출력 + */ + public void printAllResults(Names names, LadderResult result, List bonuses) { + System.out.println("실행 결과"); + for (int i = 0; i < names.size(); i++) { + Name name = names.unmodifiableNames().get(i); + Bonus bonus = bonuses.get(result.get(i)); + System.out.printf("%s : %s%n", name, bonus); + } } /** 이름 formatting */ @@ -60,7 +61,16 @@ private static String formatNameLine(Names names) { StringBuilder sb = new StringBuilder(); for (Name name : names.unmodifiableNames()) { sb.append(" ") - .append(padLeft(name.toString())); + .append(padLeft(name.toString())); + } + return sb.toString(); + } + + private static String formatBonusLine(List bonusList) { + StringBuilder sb = new StringBuilder(); + for (Bonus bonus : bonusList) { + sb.append(" ") + .append(padLeft(bonus.toString())); } return sb.toString(); } @@ -80,7 +90,8 @@ private static String formatLadderLine(Line line) { /** 좌측 공백 */ private static String padLeft(String s) { - if (s.length() >= WIDTH) return s; + if (s.length() >= WIDTH) + return s; return " ".repeat(WIDTH - s.length()) + s; } }