1시간 이내에 문제 해결이 안되었다. 오랜 시간 고민하고 학습하며 문제를 해결했다.
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Stream;
public class PracticeBilliardsTest {
static Stream<Arguments> testCaseSupplier() {
return Stream.of(
Arguments.arguments(10, 10, 3, 7, new int[][]{{7, 7}, {2, 7}, {7, 3}}, new int[]{52, 37, 116})
);
}
@ParameterizedTest
@MethodSource("testCaseSupplier")
void testSolution(int m, int n, int startX, int startY, int[][] balls, int[] expected) {
int[] result = solution(m, n, startX, startY, balls);
Assertions.assertArrayEquals(expected, result);
}
public int[] solution(int m, int n, int startX, int startY, int[][] balls) {
return Arrays.stream(balls)
.mapToInt(target -> minDistance(new Point(startX, startY), new Point(target[0], target[1]), m, n))
.toArray();
}
public int minDistance(Point start, Point target, int width, int height) {
return Stream.of(
distanceBounceLeft(start, target, width, height),
distanceBounceRight(start, target, width, height),
distanceBounceBottom(start, target, width, height),
distanceBounceTop(start, target, width, height))
.filter(Optional::isPresent)
.mapToInt(Optional::get)
.min()
.orElse(-1);
}
Optional<Integer> distanceBounceLeft(Point start, Point target, int width, int height) {
if (blockedToLeft(start, target)) {
return Optional.empty();
}
int xSum = sumOfDistanceXFromBase(start, target, 0);
int ySum = distanceY(start, target);
int distance = sumOfPowerEachDistance(xSum, ySum);
return Optional.of(distance);
}
Optional<Integer> distanceBounceRight(Point start, Point target, int width, int height) {
if (blockedToRight(start, target)) {
return Optional.empty();
}
int xSum = sumOfDistanceXFromBase(start, target, width);
int ySum = distanceY(start, target);
int distance = sumOfPowerEachDistance(xSum, ySum);
return Optional.of(distance);
}
Optional<Integer> distanceBounceBottom(Point start, Point target, int width, int height) {
if (blockedToBottom(start, target)) {
return Optional.empty();
}
int xSum = distanceX(start, target);
int ySum = sumOfDistanceYFromBase(start, target, 0);
int distance = sumOfPowerEachDistance(xSum, ySum);
return Optional.of(distance);
}
Optional<Integer> distanceBounceTop(Point start, Point target, int width, int height) {
if (blockedToTop(start, target)) {
return Optional.empty();
}
int xSum = distanceX(start, target);
int ySum = sumOfDistanceYFromBase(start, target, height);
int distance = sumOfPowerEachDistance(xSum, ySum);
return Optional.of(distance);
}
private boolean blockedToLeft(Point start, Point target) {
return start.sameY(target) && start.greaterThanX(target);
}
private boolean blockedToRight(Point start, Point target) {
return start.sameY(target) && target.greaterThanX(start);
}
private boolean blockedToTop(Point start, Point target) {
return start.sameX(target) && target.greaterThanY(start);
}
private boolean blockedToBottom(Point start, Point target) {
return start.sameX(target) && start.greaterThanY(target);
}
private int sumOfDistanceXFromBase(Point start, Point target, int base) {
return Math.abs(start.x - base) + Math.abs(target.x - base);
}
private int sumOfDistanceYFromBase(Point start, Point target, int base) {
return Math.abs(start.y - base) + Math.abs(target.y - base);
}
private int distanceX(Point start, Point target) {
return Math.abs(start.x - target.x);
}
private int distanceY(Point start, Point target) {
return Math.abs(start.y - target.y);
}
private int sumOfPowerEachDistance(int xDistance, int yDistance) {
return (int) Math.pow(xDistance, 2) + (int) Math.pow(yDistance, 2);
}
class Point {
int x;
int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
boolean sameX(Point other) {
return x == other.x;
}
boolean sameY(Point other) {
return y == other.y;
}
boolean greaterThanX(Point other) {
return x > other.x;
}
boolean greaterThanY(Point other) {
return y > other.y;
}
}
}
처음 문제에 접근하며 몇몇 복잡한 조건들이 생각났는데 이미 문제에서 제약 조건을 주고 있었다. 한 번 문제를 읽는 것으로 완벽하게 머리에 담지 못한 것 같다.
처음에는 통째로 ‘어떻게’ 해결하는 코드를 작성해서 코드를 검증하기 어려웠다. 차츰 문제를 해결하는 단계를 나눌 수 있었는데 코드를 읽기 쉬움은 물론이고 각 단계를 눈으로 라도 검증하기가 훨씬 좋았다.
처음에 문제가 왜 거리가 아닌 x 성분, y 성분 제곱의 합을 반환하는지 의문점이 있었는데 루트를 씌우면 부동소수점이 생기기 때문에 정확한 결과 확인이 복잡해 지기 때문이란 걸 문제에 접근하면서 이해하게 되었다.
처음에 x 벡터의 크기를 계산하고 y 벡터의 크기 계산하는 과정을 쪼개고 있었는데 그러면 불필요하게 중간 데이터가 생김을 깨달았다. 함께 처리되는 것은 함께 두는 것이 좋다.
Point 같이 한 쌍으로 처리되는 데이터가 나오면 클래스로 묶어서 다루면 코드가 일단 단순해지고, 데이터 처리도 항상 함께 일어남으로 메서드로 전달하거나 반환 받을 때 좋음을 느낀다.
타겟 공이 시작 공을 가리고 있어서 벽을 맞추지 못하는 경우의 조건을 계산할 때 실수를 했다. 해당 조건을 계산하는 함수로 분리하고 그리고 직접 그림을 그려본 후 조건 계산의 오류를 발견할 수 있었다.
코너에 맞는 케이스를 생각하느라 많은 시간을 허비했다. 코너에 맞고 튕겨져 나와 타겟을 맞는 조건(기울기가 같은 조건)을 열심히 계산했는데 다시 생각해보니 코너에 맞고 나오는 경우는 항상 벽을 맞는 최소거리가 존재 했다. 따라서 코너를 맞는 경우는 생각할 필요가 없었고 문제의 난이도를 높이는 조건으로 주어진 것 같았다.
같은 기능인데 x, y 만 다른 함수를 distanceX, distanceY 와 같이 하면 헷갈리는 경우가 많았다. 함수 이름이 더 길어지는 sumOfDistanceFromXBase 같은 메서드는 바꿔서 사용하는 실수를 했다. x, y 보다 더 식별하기 좋은 이름을 사용하는 것이 좋겠다.
많이 배우고 성장한 시간이었습니다. 감사합니다.