발생 원인

  1. 자식 Component에서 부모 Component의 값을 변경하는 경우
  2. ngAfterViewInit을 이용하여 값을 변경한 경우

에러 발생 과정

  1. Develop 모드에서 변화가 감지되었을 때, Change Detaction이 발생하고 부모 Component부터 체크한다.
  2. OldValue[0]에 값이 저장된다.
  3. OldValue에 저장했던 값과 현재 값을 비교하여 값이 다르면 ExpressionChangedAfterItHasBeenCheckedError를 발생한다.

에러가 발생하는 과정은 위의 3가지가 핵심이다.

 

Angular의 완결함으로 인해 lifecycle이 종료되지 않은 상황에서 lifecycle 시작 단계의 값과 중간 단계의 값이 다르기 때문에 에러가 발생한다.

해결 방법

  • 비동기 처리 핵(hack) : JVM 콜 스택 마지막에 값을 변경하는 방법으로 setTimeout에 시간을 지정하지 않으면 JVM의 콜 스택 마지막에 등록이 된다.
    ...
    
    public isOk: boolean = false;
    
    ngAfterViewInit() {
    	setTimeout(() => {
        	this.isOk = !this.isOk;
        }
    }
    
    ...​
  • ChangeDetectorRef 서비스 : 부모 Component의 ngAfterViewInit 단계에서 강제로 Change Detection를 발생시켜 check 과정을 취소하고 자식 Component에서 변경된 값으로 Check 한다.
    ...
    
    constructor(private changeDetector: ChangeDetectorRef) {}
    
    ngAfeterViewInit() {
    	this.changeDetector.detectChanges();
    }
    
    ...​

 

참고 자료

 

Slacking studio x BLOG

Slacking studio blog.

blog.eunsatio.io

 

ExpressionChangedAfterItHasBeenCheckedError 에러에 대하여

# ExpressionChangedAfterItHasBeenCheckedError 란? - 자식 component에서 부모 component의 값을 바꾸는 경우 - product mode에서는 굳이 에러를 던지진 않지만 developer 모드에서는 에러가 발생 - 값 자체는..

sddev.tistory.com

 

문제 링크 : https://www.acmicpc.net/problem/5904
 

5904번: Moo 게임

Moo는 술자리에서 즐겁게 할 수 있는 게임이다. 이 게임은 Moo수열을 각 사람이 하나씩 순서대로 외치면 되는 게임이다. Moo 수열은 길이가 무한대이며, 다음과 같이 생겼다.  m o o m o o o m o o m o o o

www.acmicpc.net


Moo 게임 문제를 접했을 때 메모리 제한을 간과하고 moomooomoo... 이런 식으로 문자열을 만들었다.
하지만 메모리 초과가 발생하여 다시 풀었지만 접근조차 하지 못했다.
문제 자체는 이해하기 쉽지만 구현해 내기에는 센스가 필요하다고 생각됐다.
결국 그 센스를 발휘하지 못하여 상당히 분했지만 이런 유형의 문제를 익힌다는 마음으로 학습하고 머릿속에 각인시켰다.

 

아래와 같이 소스를 공유 합니다.

import java.util.Scanner;

public class Main {
    public static String ans;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        
        int n = sc.nextInt();
        
        moo(n);
        
        System.out.println(ans);
    }
    
    public static void moo(int num) {
        int side = 3;
        int center = 0;
        
        while(num > side) {
        	// 4를 더하는 이유는 두번째 수열을 더하기 위함
            side = center + 4 + side * 2;
            center++;
        }
        
        // 3을 빼주는 이유는 while문에서 center는 1씩 증가해서
        // center+4가 실제 가운데 영역보다 1크기 때문에 
        int fb = (side - center - 3) / 2;
        
        if(side - fb+1 <= num) {
            moo(num - side+fb);
        }else if(num == fb+1) {
            ans = "m";
        }else {
            ans = "o";
        }
    }
}

 

문제 링크 : https://www.acmicpc.net/problem/1780
 

1780번: 종이의 개수

N×N크기의 행렬로 표현되는 종이가 있다. 종이의 각 칸에는 -1, 0, 1의 세 값 중 하나가 저장되어 있다. 우리는 이 행렬을 적절한 크기로 자르려고 하는데, 이때 다음의 규칙에 따라 자르려고 한다.

www.acmicpc.net


종이의 개수 문제를 접했을 때 분할 하는 방법을 몰랐다.

그래서 아래의 색종이 만들기 문제를 먼저 풀면서 분할 정복의 핵심 로직을 익혔다.

두 문제를 다 풀어보니 둘중 뭐가 더 어렵다고 말하기 힘들정도로 비슷하다.

종이의 개수 문제가 분할의 범위가 커졌을뿐이다.

그래도 분할의 범위가 작은 색종이 만들기 부터 풀고나니 종이의 개수 문제를 대하는 태도가 달라졌다.

난이도 낮은 문제를 푼게 아주 큰 도움이 되었다.

nkt-docs.tistory.com/21

 

[BOJ 분할정복] 색종이 만들기 2630.JAVA

문제 링크 : www.acmicpc.net/problem/2630 2630번: 색종이 만들기 첫째 줄에는 전체 종이의 한 변의 길이 N이 주어져 있다. N은 2, 4, 8, 16, 32, 64, 128 중 하나이다. 색종이의 각 가로줄의 정사각형칸들의 색..

nkt-docs.tistory.com

다음과 같이 소스를 공유합니다.

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.util.StringTokenizer;

public class 종이의 개수 {
    public static int N;
    public static int[][] map;
    public static int[] res = new int[3];
    public static void main(String[] arsg) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        
        N = Integer.parseInt(br.readLine());
        
        map = new int[N][N];
        
        for(int i = 0; i < N; i++) {
            StringTokenizer st = new StringTokenizer(br.readLine(), " ");
            for(int j = 0; j < N; j++) {
                map[i][j] = Integer.parseInt(st.nextToken());
            }
        }
        
        divide(0, 0, N);
        
        StringBuilder sb = new StringBuilder();
        
        sb.append(res[2]).append("\n");
        sb.append(res[0]).append("\n");
        sb.append(res[1]).append("\n");
        
        System.out.println(sb.toString());
    }
    
    public static void divide(int r, int c, int s) {
        if(checkNum(r, c, s)) {
            int num = map[r][c];
            if(num == -1) {
                res[2]++;
            }else {
                res[num]++;
            }
            return;
        }
        
        int ns = s/3;
        
        divide(r, c, ns);
        
        divide(r, c+ns, ns);
        divide(r, c+ns*2, ns);
        
        divide(r+ns, c, ns);
        divide(r+ns*2, c, ns);
        
        divide(r+ns, c+ns, ns);
        
        divide(r+ns, c+ns*2, ns);
        divide(r+ns*2, c+ns, ns);        
        
        divide(r+ns*2, c+ns*2, ns);
    }
    
    public static boolean checkNum(int r, int c, int s) {
        int num = map[r][c];
        
        for(int i = r; i < r+s; i++) {
            for(int j = c; j < c+s; j++) {
                if(map[i][j] != num) return false;
            }
        }
        
        return true;
    }
}

핵심 로직은 divide를 재귀로 호출하는 부분이다.

divide의 파라미터를 r(row), c(col), s(size)로 받는다.

r = 0, c = 0, s = 9

예를 들면

1. 배열[0][0] ~ 배열[8][8] 까지 배열을 체크(checkNum)하여 같은 종이인지 확인(if문) 후 해당 번호의 종이를 +1한다.

2. s(size)를 3으로 나누어 분할 한다.

3. 배열[0][0] ~ 배열[2][2] 까지 배열을 체크(checkNum)하여 같은 종이인지 확인(if문) 후 해당 번호의 종이를 +1한다.

4. 배열[0][2] ~ 배열[2][4] 까지 배열을 체크(checkNum)하여 같은 종이인지 확인(if문) 후 해당 번호의 종이를 +1한다.

5. 배열[0][4] ~ 배열[2][6] 까지 배열을 체크(checkNum)하여 같은 종이인지 확인(if문) 후 해당 번호의 종이를 +1한다.

 

이런식으로 전체 배열의 시작 위치와 탐색해야 하는 크기를 이용하여 1칸의 종이까지 체크할 수 있다.

문제 링크 : www.acmicpc.net/problem/2630
 

2630번: 색종이 만들기

첫째 줄에는 전체 종이의 한 변의 길이 N이 주어져 있다. N은 2, 4, 8, 16, 32, 64, 128 중 하나이다. 색종이의 각 가로줄의 정사각형칸들의 색이 윗줄부터 차례로 둘째 줄부터 마지막 줄까지 주어진다.

www.acmicpc.net


스터디에서 분할 정복 문제인 백준 1780(종이의 개수)를 처음 접했다.
처음 접했을 때 문제의 흐름이나 코드 구성을 어떤 방식으로 구현해야 할지 단박에 이해됐다.
하지만 핵심 로직인 2차원 배열의 위치(1~4사분면)을 분할하는 로직을 생각해내지 못했다.
핵심 로직을 도움 없이 머릿속으로 구상하려고 하니 시간이 지날수록 머릿속은 복잡해졌다.
조금 더 여려 운 문제인 종이의 개수(Silver II)보다는 쉬운 색종이 만들기(Silver III)를 먼저 풀면서 핵심 로직을 익히기로 하였다.
등급이 조금 더 낮다고 해서 핵심 로직이 떠오르지는 않아 내가 모르는 것을 인정하고(상당히 분하지만..) 다른 분들의 코드를 보고 분석하면서 문제를 풀었다.

 

다음과 같이 소스를 공유합니다.

import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.StringTokenizer;

public class 색종이 만들기 {
    public static int N;
    public static int[][] map;
    public static int[] res = new int[2];    
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        
        N = Integer.parseInt(br.readLine());
        map = new int[N][N];
        for(int i = 0; i < N; i++) {
            StringTokenizer st = new StringTokenizer(br.readLine(), " ");
            for(int j = 0; j < N; j++) {
               map[i][j] = Integer.parseInt(st.nextToken());
            }
        }
        
        divide(0, 0, N);
        
        StringBuilder sb = new StringBuilder();
        for(int i = 0; i < res.length; i++) {
            sb.append(res[i]).append("\n");
        }
        
        System.out.println(sb.toString());
    }
    
    public static void divide(int r, int c, int s) {
        if(checkColor(r, c, s)) {
            if(map[r][c] == 0) {
                res[0]++;
            }else {
                res[1]++;
            }
            return;
        }
        
        int ns = s/2;
        
        divide(r, c, ns);
        divide(r, c+ns, ns);
        divide(r+ns, c, ns);
        divide(r+ns, c+ns, ns);
    }
    
    public static boolean checkColor(int r, int c, int s) {
        
        int color = map[r][c];
        
        for(int i = r; i < r+s; i++) {
            for(int j = c; j < c+s; j++) {
                if(map[i][j] != color) {
                    return false;
                }
            }
        }
        
        return true;
    }
}

핵심 로직은 divide를 재귀로 호출하는 부분이다.

divide의 파라미터를 r(row), c(col), s(size)로 받는다.

r = 0, c = 0, s = 8

예를 들면

1. 배열[0][0] ~ 배열[7][7] 까지 배열을 체크(checkColor)하여 같은 종이인지 확인(if문) 후 해당 번호의 종이를 +1한다.

2. s(size)를 2으로 나누어 분할 한다.

3. 배열[0][0] ~ 배열[3][3] 까지 배열을 체크(checkColor)하여 같은 종이인지 확인(if문) 후 해당 번호의 종이를 +1한다.

4. 배열[0][4] ~ 배열[3][7] 까지 배열을 체크(checkColor)하여 같은 종이인지 확인(if문) 후 해당 번호의 종이를 +1한다.

 

이런식으로 전체 배열의 시작 위치와 탐색해야 하는 크기를 이용하여 1칸의 종이까지 체크할 수 있다.

+ Recent posts