본문 바로가기

삽질

방향 이동 이중 반복문 추상화 해보기

현재의 좌표를 (i, j)라고 했을 때 상, 하, 좌, 우로 한 칸 움직이기 위해서는 다음과 같습니다.

int[] dx = {-1, 1, 0, 0}; // 순서대로 상, 하, 좌, 우
int[] dy = {0, 0, -1, 1}; 

for(int d=0; d<4; d++){
    int nx = i + dx[d];
    int ny = j + dy[d];
}

 

그러면, 한 쪽 방향으로 벽이나 다른 숫자에 부딪칠 때 까지 움직이기 위해서는 어떻게 해야 할까요?

이의 관한 문제로는 백준 2048(Easy), 구슬 탈출 2 문제 등이 있습니다.

2048(Easy) 구슬 탈출 2
https://www.acmicpc.net/problem/12100 https://www.acmicpc.net/problem/13460

 

로 움직이기 위해서 아래와 같은 반복문을 사용해 쭉 이동시킵니다.

for (int i = 0; i < n-1; i++) {
    for (int j = 0; j < n; j++) {
        ...
    }
}

 

아래로 움직이기 위해서 아래와 같은 반복문을 사용합니다.

for (int i = n-1; i > 0; i--) {
    for (int j = 0; j < n; j++) {
        ...
    }
}

 

로 움직이기 위해서 아래와 같은 반복문을 사용합니다.

for (int i = 0; i < n; i++) {
    for (int j = 0; j < n-1; j++) {
        ...
    }
}

 

로 움직이기 위해서 아래와 같은 반복문을 사용합니다.

for (int i = 0; i < n; i++) {
    for (int j = n-1; j > 0; j--) {
        ...
    }
}

 

 

일반 반복문은 초기식, 조건식, 변화식으로 이루어져 있습니다.

 

네 개 방향 모두 이중 반복문이 필요합니다.

하지만, 이동 방향에 따라 초기식, 조건식, 변화식이 조금씩 다릅니다.

 

이를 추상화 해 볼 수 있지 않을까요?

 

초기식을 추상화해보겠습니다.

좌표는 (i, j)로 두 개를 사용합니다.

이는 일반 변수에 저장해서 방향에 따라 변화시키면 됩니다.

// 상, 좌는 0 부터 시작
int startRow = 0; // i
int startColumn = 0; // j

if (d == Direction.DOWN) { // 아래
    startRow = n - 1;
} else if (d == Direction.RIGHT) { // 우측
    startColumn = n - 1;
}

 

조건식을 추상화해보겠습니다.

조건식은 bool 값입니다.

즉, Predicate를 사용할 수 있습니다.

// 상, 좌는 조건식이 n보다 작음
Predicate<Integer> rowPredicate = (i) -> (i < n);
Predicate<Integer> columnPredicate = (j) -> (j < n);

if (d == Direction.DOWN) { // 아래
    rowPredicate = (i) -> (i >= 0);
} else if (d == Direction.RIGHT) { // 우측
    columnPredicate = (j) -> (j >= 0);
}

 

변화식을 추상화해보겠습니다.

방향에 따라 1이 더해지거나 빼집니다.

이는 방향 순서를 정하고 그 순서에 맞에 배열에서 값을 가져와 더하면 됩니다.

/*
 상 : i++, j++
 하 : i--, j++
 좌 : i++, j++
 우 : i++, j--
*/
int[] ix = new int[]{1, -1, 1, 1}; // 상, 하, 좌, 우
int[] jy = new int[]{1, 1, 1, -1};

i += ix[d]
j += jy[d]

 

이를 하나의 클래스로 추상화하면, 다음과 같이 만들어 사용할 수 있습니다.

더보기
private enum Direction {
    UP(0),
    DOWN(1),
    LEFT(2),
    RIGHT(3);

    private final int value;

    Direction(int value, int[] dxdy) {
        this.value = value;
    }
}
import java.util.function.Predicate;

private static class MoveCondition {
    int startRow, startColumn;
    Predicate<Integer> rowPredicate, columnPredicate;
    int[] ix, jy;

    private MoveCondition(int startRow, int startColumn,
                          Predicate<Integer> rowPredicate,
                          Predicate<Integer> columnPredicate) {
        this.startRow = startRow;
        this.startColumn = startColumn;
        this.rowPredicate = rowPredicate;
        this.columnPredicate = columnPredicate;
        this.ix = new int[]{1, -1, 1, 1};
        this.jy = new int[]{1, 1, 1, -1};
    }

    private boolean test(Predicate<Integer> predicate, Integer value) {
        return predicate.test(value);
    }

    private static MoveCondition of(Direction d, int n) {
        int startRow = 0;
        int startColumn = 0;

        Predicate<Integer> rowPredicate = (i) -> (i < n);
        Predicate<Integer> columnPredicate = (j) -> (j < n);

        if (d == Direction.DOWN) {
            startRow = n - 1;
            rowPredicate = (i) -> (i >= 0);
        } else if (d == Direction.RIGHT) {
            startColumn = n - 1;
            columnPredicate = (j) -> (j >= 0);
        }

        return new MoveCondition(startRow, startColumn, rowPredicate, columnPredicate);
    }
}
MoveCondition mc = MoveCondition.of(d, n);

for (int i = mc.startRow; mc.test(mc.rowPredicate, i); i += mc.ix[d.value]) {
    for (int j = mc.startColumn; mc.test(mc.columnPredicate, j); j += mc.jy[d.value]) {
        ...
    }
}

 

위의 더보기를 클릭하면 됩니다.

 

근데, 무조건 위와 같이 추상화 하는게 좋을까요?

저는 위의 방법을 백준의 12100번 문제인 2048(Easy)을 풀고, 리팩토링하다가 생각난 방법입니다.

 

저는 12100을 백트랙킹으로 풀었습니다.

즉, 가지치기를 하더라도 그 경우의 수가 많습니다.

 

리팩토링을 하기 에는 다음과 같은 메모리와 속도에 풀 수 있었습니다.

리팩토링 에는 다음과 같은 메모리와 속도에 풀 수 있었습니다.

 

속도가 더 느려지고, 메모리의 사용량이 거의 3배가 증가했습니다.

 

아무래도 객체를 탐색할 때 마다 생성하니 그런거 같습니다.

 

코드가 객체지향적으로 변해 유지보수가 쉬워진 것에 대해 이점이 있지만,

알고리즘 메모리와 속도의 단점을 매울 만큼의 이점이 있는지에 대해 생각을 해 봐야할 거 같습니다...