서론

기본적으로 was나 spring boot에서 error page를 제공해 주고 있으나

보기 이쁘지도 않을뿐더러 stacktrace 가 그대로 노출되기 때문에 보안상으로도 좋을 것이 없다.


본론

하여 보통 에러 페이지를 따로 개발하여 보여주게 되는데 thymeleaf과 spring boot을 사용할 경우

https://www.thymeleaf.org/doc/articles/springsecurity.html

위 주소의 tymeleaf 공식 홈페이지에서 제시한 방법처럼 적용하면 된다.

이 경우 spring security가 전혀 관여하지 않기 때문에 ExceptionHandler를 추가할 것을 권하고 있다.

나는 에러 유형에 따라 이미지 파일을 보여주도록 custom error page 기능을 아래 처럼 적용했다.

Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Controller
public class CustomErrorController implements ErrorController {

private static String IMAGE_PATH = "images/error/";

@ExceptionHandler(Throwable.class)
@GetMapping("/error")
public String handleError(HttpServletRequest request, Model model) {
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
String statusMsg = status.toString();

HttpStatus httpStatus = HttpStatus.valueOf(Integer.valueOf(statusMsg));

model.addAttribute("msg", statusMsg + " " + httpStatus.getReasonPhrase());
model.addAttribute("src", IMAGE_PATH + statusMsg + ".jpg");

return "thymeleaf/error";
}

@Override
public String getErrorPath() {
// TODO Auto-generated method stub
return "/error";
}
}

View

1
2
3
4
5
6
7
8
9
10
11
12

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Error page</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="css/error.css" th:href="@{/css/error.css}" />
</head>
<body>
<img th:alt="${msg}" th:src="${src}">
</body>
</html>

thymeleaf를 사용하지 않는 경우

thymeleaf를 사용하지 않을 경우엔 resource 폴더 하위에 static 폴더에 error 페이지를 만들어놓고 CustomController를 아래처럼 IllegalStateException 을 throw 하기만 하면 된다.

Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

public class CustomErrorController implements ErrorController {

private String PATH = "/error";

@GetMapping("/error")
public String errorpage(){
throw new IllegalStateException("Error");
}

@Override
public String getErrorPath() {
// TODO Auto-generated method stub
return PATH;
}
}

결론

적용 결과

결과

나는 https://http.cat/ 라는 http status code마다 위처럼 고양이 사진을 보여주는 사이트에서 이미지 파일을 가져와 적용하였다.

그 외에 다른 사이트들에선 어떻게 에러 페이지를 보여주는지 확인해보고 각자 에러 페이지를 작성하면 좋을 것이다.

[번역] ‘404 에러페이지’ 24가지 분석 -1


참고 사이트

댓글 공유

https://programmers.co.kr/learn/courses/30/lessons/17682?language=java


소스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public int solution(String dartResult) {
char[] charArray = dartResult.toCharArray();

int[] scores = new int[3];
int i = 0;
StringBuilder sb = new StringBuilder();
for (char ch : charArray) {
switch (ch) {
case 'S':
scores[i++] = Integer.parseInt(sb.toString());

sb.setLength(0);
break;
case 'D':
int a = Integer.parseInt(sb.toString());
scores[i++] = a * a;

sb.setLength(0);
break;
case 'T':
int b = Integer.parseInt(sb.toString());
scores[i++] = b * b * b;

sb.setLength(0);
break;
case '*':
if (i > 1) {
scores[i-2] = scores[i-2] * 2;
}
scores[i-1] = scores[i-1] * 2;
break;
case '#':
scores[i-1] = scores[i-1] * -1;
break;
default:
sb.append(ch);
break;
}
}

int answer = scores[0] + scores[1] + scores[2];
return answer;
}

흐름

  1. 문자열을 charArray로 치환하고 loop 돈다.

  2. char를 비교하여 각 문자별로 맞게 계산을 실행하고, index를 증가 시킨 후 다음 점수 저장을 위해 StringBuilder를 초기화 한다.

  3. *‘ 일 경우 이전 점수와 현재 점수 모두 2 배씩 해야 하므로 2 번 던진 경우엔 이전 점수도 2를 곱 해준다.

  4. 계산된 세 점수 모두 더해서 return 한다.


결과

번호 속도
테스트 1 통과 (0.79ms, 52.4MB)
테스트 2 통과 (0.86ms, 52.2MB)
테스트 3 통과 (0.86ms, 52.4MB)
테스트 4 통과 (0.84ms, 52.3MB)
테스트 5 통과 (0.92ms, 54.5MB)
테스트 6 통과 (0.82ms, 52.5MB)
테스트 7 통과 (0.83ms, 52.1MB)
테스트 8 통과 (0.83ms, 52.2MB)
테스트 9 통과 (0.90ms, 50.8MB)
테스트 10 통과 (0.83ms, 52.7MB)
테스트 11 통과 (0.84ms, 52.9MB)
테스트 12 통과 (0.84ms, 52.5MB)
테스트 13 통과 (0.82ms, 50.4MB)
테스트 14 통과 (0.74ms, 52.6MB)
테스트 15 통과 (0.90ms, 52.1MB)
테스트 16 통과 (0.86ms, 52.9MB)
테스트 17 통과 (0.81ms, 52.7MB)
테스트 18 통과 (0.86ms, 52.4MB)
테스트 19 통과 (0.85ms, 50.1MB)
테스트 20 통과 (0.95ms, 52.4MB)
테스트 21 통과 (0.81ms, 50.4MB)
테스트 22 통과 (0.80ms, 51.9MB)
테스트 23 통과 (0.88ms, 52.3MB)
테스트 24 통과 (0.87ms, 51.9MB)
테스트 25 통과 (0.87ms, 50.4MB)
테스트 26 통과 (0.88ms, 52.1MB)
테스트 27 통과 (0.89ms, 52.4MB)
테스트 28 통과 (0.88ms, 52.2MB)
테스트 29 통과 (0.85ms, 52.7MB)
테스트 30 통과 (0.89ms, 51.8MB)
테스트 31 통과 (0.85ms, 50.2MB)
테스트 32 통과 (0.83ms, 52MB)

테스트 케이스

1
2
3
4
5
6
7
assertEquals(37, test.solution("1S2D*3T"));
assertEquals(9, test.solution("1D2S#10S"));
assertEquals(3, test.solution("1D2S0T"));
assertEquals(23, test.solution("1S*2T*3S"));
assertEquals(5, test.solution("1D#2S*3S"));
assertEquals(-4, test.solution("1T2D3D#"));
assertEquals(59, test.solution("1D2S3T*"));
  • 프로그래머스에서 제공한 예제 테스트 케이스이므로
    위 테스트케이스를 모두 통과하여도 실제 테스트에선 통과하지 못할 수 있음.

댓글 공유

문제

https://programmers.co.kr/learn/courses/30/lessons/42889


소스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public int[] solution(int N, int[] stages) {
int[] failUsers = new int[N+2];
for (int stage : stages) {
failUsers[stage] += 1;
}

Map<Integer, Double> map = new HashMap<Integer, Double>();
double userCount = stages.length;
for (int i = 1; i <= N; i++) {
double value = 0.0;
if (failUsers[i] != 0 && userCount != 0) {
value = (failUsers[i] / userCount) * 100;
userCount -= failUsers[i];
}
map.put(i, value);
}

List<Integer> list = new ArrayList<Integer>(map.keySet());
Collections.sort(list, new Comparator<Integer>() {

@Override
public int compare(Integer o1, Integer o2) {
return map.get(o2).compareTo(map.get(o1));
}
});

int[] answer = list.stream().mapToInt(i -> i).toArray();
return answer;
}

흐름

  1. 유저 수(stages.length) 만큼 loop 돌면서 각 stage 별로 실패한 유저 수를 array에 저장한다.
  2. 스테이지별로 틀린 인원 수를 저장히기 위한 Map 변수를 하나 생성하고,
  3. 스테이지 수(N) 만큼 loop 돌면서 해당 스테이지에 실패한 사람이 있으면서, 전체 시도한 사람이 0명이 아닌 경우,
  4. $$ 해당 스테이지에서 실패한 유저 수 / 해당 스테이지에 도전한 유저 수 * 100 $$ 해서 해당 스테이지의 실패율을 구한다.
    • 이 때 스테이지에 도전한 유저 수는 이전 스테이지를 성공한 사람들 만이다.
  5. 이번 스테이지에서 실패한 유저들은 다음 스테이지에 도전 할 수 없으므로, 총 유저 수에서 실패한 유저 수를 제한다.
  6. 위에서 선언한 Map 변수에 key를 스테이지 번호로, value를 실패율로 하여 put 한다.
  7. 실패율을 저장한 Map을 Value 기준으로 내림차순 정렬을 한다.
  8. ArrayList를 int[]로 변환한다.
    • stream에 경우 속도가 느리므로, loop를 돌면서 직접 int[]로 만드는 방법이 퍼포먼스에 더 이로울 듯 하다.

결과

번호 속도
테스트 1 통과 (7.39ms, 52.6MB)
테스트 2 통과 (6.26ms, 54.7MB)
테스트 3 통과 (11.14ms, 54.2MB)
테스트 4 통과 (12.53ms, 58.2MB)
테스트 5 통과 (13.17ms, 63.3MB)
테스트 6 통과 (12.28ms, 52.3MB)
테스트 7 통과 (9.12ms, 53.5MB)
테스트 8 통과 (11.79ms, 58.6MB)
테스트 9 통과 (13.39ms, 61.4MB)
테스트 10 통과 (11.68ms, 55.8MB)
테스트 11 통과 (12.76ms, 59.8MB)
테스트 12 통과 (11.43ms, 60MB)
테스트 13 통과 (12.27ms, 62.7MB)
테스트 14 통과 (5.77ms, 54.3MB)
테스트 15 통과 (10.07ms, 54MB)
테스트 16 통과 (9.50ms, 55.6MB)
테스트 17 통과 (7.79ms, 53.4MB)
테스트 18 통과 (9.16ms, 54.8MB)
테스트 19 통과 (6.27ms, 52.8MB)
테스트 20 통과 (7.16ms, 53.2MB)
테스트 21 통과 (8.94ms, 55.3MB)
테스트 22 통과 (12.98ms, 62.7MB)
테스트 23 통과 (11.22ms, 58MB)
테스트 24 통과 (17.40ms, 59.7MB)
테스트 25 통과 (6.25ms, 52.9MB)
테스트 26 통과 (6.00ms, 52.3MB)
테스트 27 통과 (5.83ms, 50.4MB)

테스트 케이스

1
2
3
4
5
6
assertArrayEquals(new int[] {3,4,2,1,5}, test.solution(5, new int[] {2, 1, 2, 6, 2, 4, 3, 3}));
assertArrayEquals(new int[] {4,1,2,3}, test.solution(4, new int[] {4,4,4,4,4}));
assertArrayEquals(new int[] {2,1,3,4}, test.solution(4, new int[] {1,1,1,1,2}));
assertArrayEquals(new int[] {4,2,3,1}, test.solution(4, new int[] {3,2,5,4,2}));
assertArrayEquals(new int[] {7,6,2,3,5,4,1}, test.solution(7, new int[] {2, 1, 2, 6, 2, 4, 3, 3,7,5}));
assertArrayEquals(new int[] {1,2,3,4,5}, test.solution(5, new int[] {}));
  • Junit5로 테스트 했음.

  • 프로그래머스에서 제공하는 모든 케이스에 대한 것이 아니라,
    필자가 마음대로 넣은 것 이므로 이 소스를 통과하여도 프로그래머스에선 통과되지 못할 수 있음.


다른 멋진 분들의 해결방법

댓글 공유

https://programmers.co.kr/learn/courses/30/lessons/64061


소스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public int solution(int[][] board, int[] moves) {
int[] tops = setTop(board);

Stack<Integer> stack = new Stack<Integer>();
int answer = 0;
for (int i = 0; i < moves.length; i++) {
int move = moves[i] - 1;
int top = tops[move];
if (top == 0) {
continue;
}

int height = board.length - top;
int value = board[height][move];
tops[move] = tops[move] - 1;

if (!stack.isEmpty() && (stack.peek() == value)) {
stack.pop();
answer += 2;
} else {
stack.push(value);
}
}

return answer;
}

private int[] setTop(int[][] board) {
int height = board.length;
int width = board[0].length;
int[] tops = new int[width];

for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
if (tops[j] == 0 && board[i][j] != 0) {
tops[j] = height - i;
}
}
}

return tops;
}

흐름

  1. 인형이 쌓여있는 상자에서 각 열마다 맨 위에 있는 인형의 위치를 배열에 저장함.

  2. 크레인이 뽑을 위치가 담긴 배열 크기(moves) 만큼 loop 돔.

  3. 배열이 0부터 시작하므로, 뽑을 위치(move) 에서 -1 함.

  4. 뽑을 위치에서 맨 위의 위치를 가져오는데 0이면 그 열에 있는 건 모두 뽑아 먹은 것이므로 아무 행동도 하지 않음.

  5. 총 높이에서 그 열에서 제일 위에 있는 인형의 위치를 뻄.

  6. 상자에서 인형을 꺼냄.(value = board[height][move])

  7. 그 열에 한 개 꺼냈으므로, top의 위치를 한 칸 내림.

  8. stack이 비어 있지 않으면서, stack의 top이 현재 뽑은 인형과 같으면 stack에 저장된 인형을 삭제하고, 삭제된 인형의 갯수를 더 함.

  9. 그렇지 않으면 stack에 인형을 추가.


결과

번호 속도
테스트 1 통과 (0.94ms, 50.1MB)
테스트 2 통과 (0.94ms, 52.4MB)
테스트 3 통과 (1.04ms, 52.6MB)
테스트 4 통과 (2.28ms, 50.6MB)
테스트 5 통과 (0.98ms, 50.9MB)
테스트 6 통과 (0.97ms, 50.6MB)
테스트 7 통과 (1.11ms, 52.4MB)
테스트 8 통과 (1.25ms, 52.1MB)
테스트 9 통과 (1.37ms, 54.4MB)
테스트 10 통과 (1.38ms, 52.5MB)
테스트 11 통과 (1.81ms, 52.7MB)

댓글 공유

https://programmers.co.kr/learn/courses/30/lessons/12940


소스

1
2
3
4
5
6
7
8
9
10
11
12
public int[] solution(int n, int m) {
int big = n > m ? n : m;
int small = n < m ? n : m;

while (m != 0) {
int r = n % m;
n = m;
m = r;
}

return new int[] {n, n * (big / n) * (small / n)};
}

흐름

  • 유클리드 호제법으로 처리

    두 양의 정수 $$ a, b (a > b) $$ 에 대하여 $$ a = bq + r(0 ≤ r < a) $$
    라 하면, a, b 의 최대공약수는 b, r의 최대공약수와 같다.

  1. 두 수를 중 큰 수와 작은 수 구분

  2. 큰 수 a의 최대공약수는 b와 r(a mod b)의 최대 공약수와 같으므로 작은 수 m이 0이 될 때 까지 반복

  3. 최소공배수는 최대공약수(G) * (a / G) * (m / G) 이므로 계산해서 배열에 할당한다.

    • $$ (a * b) / G $$ 로 하면 수가 큰 경우 overflow가 발생 할 가능성이 있음.

유클리드 호제법

두 양의 정수 $$ A, B (A > B) $$ 에 대하여 $$ A = Bq + R(0 ≤ R < a) $$
라 하면, A, B 의 최대공약수는 B, R의 최대공약수와 같다.

이 때 R은 A mod B 한 값으로 식으로 나타내면 아래와 같다.

$$ G(A,B) = G(B,R) $$

A와 B에게 최대공약수 G가 있다면,

$$ A = aG, B = bG $$

과 같이 나타 낼수 있고 이 때 a와 b는 반드시 서로소 여야 한다.
위에서 A mod B 한 값이 R 이라고 정의했고 A와 B를 나눈 값을 q라고 한다면,

$$ A = Bq + R $$
$$ aG = bGq + R $$
$$ R = G(a-bq) $$

처럼 전개 될 수 있고, 이 경우 R과 B가 G 라는 최대 공약수를 갖기 떄문에 $$ a-bq $$ 와 b가 서로소 임을 증명하면 된다.

만약 $$ a-bq $$ 와 b가 서로소가 아니라면, 둘은 공약수가 존재 하기 떄문에 아래와 같이 표현 할 수 있다.

$$ a-bq = pm , b = pn $$
$$ a = bq + pm $$
$$ a = pnq + pm $$
$$ a = p(nq + m), b = pn $$

이 때 a와 b는 서로소인데 p 라는 공약수를 갖게 되므로
a와 b가 서로소라는 전제가 모순되어 $$ a-bq $$ 와 b는 서로소임이 증명되고 B와 R의 최대 공약수가 G 인 것이 된다.


참고 사이트

댓글 공유

https://programmers.co.kr/learn/courses/30/lessons/12932


소스

1
2
3
4
5
6
7
8
9
public int[] solution(long n) {
List<Long> list = new ArrayList<Long>();
while (n > 0) {
list.add(n % 10);
n /= 10;
}

return list.stream().mapToInt(i -> (int) (long) i).toArray();
}

흐름

  1. n은 10,000,000,000이하인 자연수이므로 몇 자리의 수가 올지 알 수 없으므로 배열이 아닌 ArrayList를 생성
  2. n mode 10한 값을 저장하고 n을 10 나눔
    • ex) 12345 mod 10 = 5, 12345 / 10 = 1234
  3. List를 stream()을 활용하여 int array로 변경함

다른 풀이

1
2
3
4
5
6
7
8
9
10
public int[] solution(long n) {
int[] arrays = new int[String.valueOf(n).length()];
int i = 0;
while (n > 0) {
arrays[i++] = (int) (n % 10);
n /= 10;
}

return arrays;
}
  1. n이 몇 자리의 수가 올지 알 수 없으므로 n을 String으로 변환 후 String의 length를 구해서 array의 크기를 구함
  2. 이하 상동

시간 비교

첫 번째 풀이

번호 속도
테스트 1 통과 (5.56ms, 50.8MB)
테스트 2 통과 (5.78ms, 50.2MB)
테스트 3 통과 (5.51ms, 50.9MB)
테스트 4 통과 (4.88ms, 52.9MB)
테스트 5 통과 (6.73ms, 52.3MB)
테스트 6 통과 (5.79ms, 52.6MB)
테스트 7 통과 (6.36ms, 50.7MB)
테스트 8 통과 (5.06ms, 50.7MB)
테스트 9 통과 (5.75ms, 52.5MB)
테스트 10 통과 (4.99ms, 50.6MB)
테스트 11 통과 (5.25ms, 52.7MB)
테스트 12 통과 (5.65ms, 52.4MB)
테스트 13 통과 (5.75ms, 55MB)

두 번째 풀이

번호 속도
테스트 1 통과 (1.34ms, 52.5MB)
테스트 2 통과 (1.57ms, 49.9MB)
테스트 3 통과 (1.52ms, 50.3MB)
테스트 4 통과 (1.36ms, 53.1MB)
테스트 5 통과 (1.52ms, 52.3MB)
테스트 6 통과 (1.42ms, 52.4MB)
테스트 7 통과 (1.52ms, 50.4MB)
테스트 8 통과 (1.43ms, 50.4MB)
테스트 9 통과 (1.37ms, 52.2MB)
테스트 10 통과 (1.50ms, 50.2MB)
테스트 11 통과 (1.42ms, 49.9MB)
테스트 12 통과 (1.72ms, 52.2MB)
테스트 13 통과 (1.73ms, 50.7MB)
  • Stream()이 생각보다 더 느리게 동작하는 듯 싶다.

댓글 공유

문제

https://programmers.co.kr/learn/courses/30/lessons/12921


코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public int solution(int n) {
boolean[] prime = new boolean[n+1];

prime[0] = true;
prime[1] = true;

for (int i = 2; (i * i) <= n; i++) {
if (!prime[i]) {
for (int j = i * 2; j <= n; j += i) {
prime[j] = true;
}
}
}

int answer = 0;
for (boolean is : prime) {
if (!is) {
answer++;
}
}

return answer;
}

흐름

  • 에라토스테네스의 체로 해결

Sieve_of_Eratosthenes_animation

  1. n까지의 숫자중 0과 1은 소수가 아니므로 true 할당
  2. 2부터 루트n 까지 반복하면서 배수들에 true 할당
  3. 소수 판별한 배열 prime을 loop 돌면서 false 인 경우에만 갯수 추가

부연 설명

1
for (int i = 2; (i * i) <= n; i++)

입력받은 수 n이 합성수 인 경우 $$n = a * b​$$ 로 나타낼 수 있다.
이 때 a와 b 중 큰거나 같은 수를 a, 작거나 같은 수를 b라고 가정하면 b가 a보다 작으므로 $$ a * a >= a * b $$ 이고
$$ n = a * b $$ 이므로, 큰 수 a의 제곱일 경우 $$ a^2>=n $$ 처럼 된다.
이를 치환하면 $$ a>=\sqrt{n} $$ 이 되므로 a의 최솟값은 $$ \sqrt{n} $$ 이 된다.
그리고 b는 $$ a >= b $$이므로 b의 최댓값은 $$ \sqrt{n} $$ 이 된다.
고로 n이 소수가 아닌 합성수라면 a의 와 b의 합성수로 이루어 짐을 알 수 있으므로, $$ \sqrt{n} $$ 까지만 반복하면 소수임을 판별할 수 있다.

  • ex) 입력받은 수 n이 50일 경우 $$ \sqrt{50} $$ = 7.071067811865475‬‬….이므로, 7.071067811865475‬‬… 을 기준으로 50의 약수 {1,2,5,10,25,50}을 나누면 b = {1, 2, 5}, a = {10, 25, 50} 이므로 b만 검사하면 50이 소수 인지 아닌지 알 수 있다.
1
for (int j = i * 2; j <= n; j += i)

원래는 i보다 작은 수인 경우 이미 이전에 작은 수 * i에서 삭제된 상태이므로 i * i인 게 맞으나,
i의 값이 너무 클 경우 문제가 될 수 있으므로 $$ i * 2 $$ 로 설정한다.
물론 이 문제에선 100만 까지로 한정되어 있으므로 $$ i * i $$ 를 해도 문제는 없다.


참고 사이트

댓글 공유

https://www.acmicpc.net/problem/11399


소스

1
2
3
4
5
6
7
8
9
10
11
12

public static int solution(int n, int[] times) {
Arrays.sort(times);

int answer = 0;
for (int i = 0; i < times.length; i++) {
answer += times[i] * (n-i);
}

return answer;
}


흐름

  1. 시간이 짧게 걸리는 사람이 앞에 올수록 전체 수행 시간이 짧아지므로 sorting부터 실행

  2. 앞에 사람이 걸리는 시간은 그 뒤 사람들도 그만큼 시간이 + 되는 것이므로 n-i 한 값을 곱함

    ex) 첫 번째 사람이 1분 걸리면 2, 3, 4, 5 번째 사람도 1분씩 더 걸리게 됨

  3. 다 더한 값을 출력하면 끝

댓글 공유

Junggu Ji

author.bio


author.job