문제

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


코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public int solution(String[][] clothes) {
Map<String, Integer> map = new HashMap<String, Integer>();

for (String[] s : clothes) {
int value = 1;
String key = s[1];
if (map.containsKey(key)) {
value = map.get(key);
}

map.put(key, ++value);
}

int answer = 1;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
answer *= entry.getValue();
}

return answer - 1;
}

흐름

  1. [[“yellow_hat”, “headgear”], [“blue_sunglasses”, “eyewear”], [“green_turban”, “headgear”]], clothes안에 이런 식으로 옷이름, 종류 로 저장되어 있으므로 chothes 크기 만큼 반복하면서 옷 종류를 꺼냄

  2. 옷 종류를 key로 하는 Map이 있는 지 확인

  3. 있으면 값을 증겨 시켜야 하므로 value를 꺼내옴

  4. ++해서 값을 증가 시켜서 저장하고, 없으면 해당 key로 새로 저장

  5. 순열과 조합 공식을 바탕으로 value를 모두 곱함

  6. 문제에서 최소 1 개의 옷은 입는다고 하였으므로, 옷을 모두 안입는 경우를 제외하기 위해 -1을 함

조합 공식

Every interaction is both precious and an opportunity to delight.

서로 다른 n개의 원소에서 순서에 상관없이 r개를 뽑을 때, 이를 n개에서 r개를 택하는 조합이라고 한다. 이 조합은 순열과 다른 개념으로 순서 차이가 중요하다.

$$ nCr = nPr / r! = n! / (n-r)!r! $$

  • 이 공식으로 위 문제를 풀어보면 헤어 2개, 안경 1개 이므로
  • 헤어 2개에서는 입을경우, 안 입을 경우 2 가지이고 이를 수식으로 표현하면
  • 0개를 뽑는 경우 = $$ 2C0 = 2P0 / 0! = 2! / (2-0)!0! = 2 / 2 = 1 $$
  • 이 되고, 나머지의 경우도 계산하면 $$ 1 + 2 = 3 $$ 이 된다.
  • 안경도 마찬가지로 입을 경우, 안 입을 경우 2 가지로
  • $$ 1C0 + 1C1 = 1 + 1 = 2 $$ 가 되고
  • 마지막으로 둘 다 안 입는 경우는 없으므로 -1을 하면
  • $$ 3 * 2 - 1 = 5 $$가 된다

다른 분의 코드

1
2
3
4
5
6
7
public int solution(String[][] clothes) {
return Arrays.stream(clothes)
.collect(groupingBy(p -> p[1], mapping(p -> p[0], counting())))
.values()
.stream()
.collect(reducing(1L, (x, y) -> x * (y + 1))).intValue() - 1;
}
  • 람다식 대박

결과

1번

번호 속도
테스트 1 통과 (0.93ms, 50.5MB)
테스트 2 통과 (0.85ms, 51.9MB)
테스트 3 통과 (0.86ms, 51.9MB)
테스트 4 통과 (0.93ms, 52.8MB)
테스트 5 통과 (0.80ms, 52MB)
테스트 6 통과 (0.88ms, 50.5MB)
테스트 7 통과 (0.84ms, 52.2MB)
테스트 8 통과 (0.85ms, 52.8MB)
테스트 9 통과 (0.86ms, 51.9MB)
테스트 10 통과 (0.87ms, 52.2MB)
테스트 11 통과 (0.89ms, 52.4MB)
테스트 12 통과 (0.82ms, 51.9MB)
테스트 13 통과 (0.81ms, 52.3MB)
테스트 14 통과 (0.75ms, 51.9MB)
테스트 15 통과 (0.82ms, 50.5MB)
테스트 16 통과 (0.91ms, 50.5MB)
테스트 17 통과 (0.90ms, 52.5MB)
테스트 18 통과 (0.88ms, 52.1MB)
테스트 19 통과 (0.88ms, 51.8MB)
테스트 20 통과 (0.90ms, 52.4MB)
테스트 21 통과 (0.80ms, 52.9MB)
테스트 22 통과 (0.86ms, 51.6MB)
테스트 23 통과 (0.80ms, 50.7MB)
테스트 24 통과 (0.90ms, 50.3MB)
테스트 25 통과 (0.90ms, 52.3MB)
테스트 26 통과 (0.93ms, 52.1MB)
테스트 27 통과 (0.86ms, 52.3MB)
테스트 28 통과 (0.90ms, 50.1MB)

2번

번호 속도
테스트 1 통과 (14.41ms, 53.2MB)
테스트 2 통과 (12.19ms, 52.8MB)
테스트 3 통과 (12.05ms, 52.9MB)
테스트 4 통과 (13.03ms, 50.9MB)
테스트 5 통과 (14.21ms, 50.9MB)
테스트 6 통과 (12.85ms, 51MB)
테스트 7 통과 (13.19ms, 50.9MB)
테스트 8 통과 (13.32ms, 53.6MB)
테스트 9 통과 (13.37ms, 53.1MB)
테스트 10 통과 (12.58ms, 53.2MB)
테스트 11 통과 (12.34ms, 52.7MB)
테스트 12 통과 (13.69ms, 52.4MB)
테스트 13 통과 (13.19ms, 52.7MB)
테스트 14 통과 (12.48ms, 51.1MB)
테스트 15 통과 (11.82ms, 52.5MB)
테스트 16 통과 (12.63ms, 52.7MB)
테스트 17 통과 (13.37ms, 53.1MB)
테스트 18 통과 (13.54ms, 53.1MB)
테스트 19 통과 (13.20ms, 50.7MB)
테스트 20 통과 (13.11ms, 52.7MB)
테스트 21 통과 (12.80ms, 52.6MB)
테스트 22 통과 (13.48ms, 53.2MB)
테스트 23 통과 (12.59ms, 52.5MB)
테스트 24 통과 (13.71ms, 50.9MB)
테스트 25 통과 (12.94ms, 52.4MB)
테스트 26 통과 (13.67ms, 50.9MB)
테스트 27 통과 (13.75ms, 53MB)
테스트 28 통과 (13.04ms, 51.7MB)

테스트 케이스

1
2
assertEquals(5, test.solution(new String[][] {{"yellow_hat", "headgear"}, {"blue_sunglasses", "eyewear"}, {"green_turban", "headgear"}}));
assertEquals(3, test.solution(new String[][] {{"crow_mask", "face"}, {"blue_sunglasses", "face"}, {"green_turban", "face"}}));

참고 사이트

댓글 공유

AOP란

  • AOP는 Ioc, DI, 서비스 추상화와 더불어 스프링의 3대 기반 기술
  • 선언적 트랜잭션 기능이 대표적

트랜잭션 코드의 분리

  • 비즈니스 로직 전 후에 로직과 전혀 상관없는 트랜잭션 경계를 설정해야 하기 때문에 필연적으로 코드가 지저분해짐

메서드 분리를 이용해 분리

  • 비즈니스 로직을 담당하는 코드만 따로 메서드로 추출
  • 하지만 여전히 비즈니스 로직이 아닌 보일 필요가 없는 트랜잭션 코드가 남아있음
  • 그렇다면 클래스에서 빼서 아예 눈에 안띄게 해버리자

DI를 이용한 클래스의 분리

  • DI의 기본 아이디어는 실제 사용할 오브젝트의 클래스 정체는 감춘 채 인터페이스를 통해 간접으로 접근하는 것

  • 그 덕분에 구현 클래스는 얼마든지 외부에서 변경 가능

  • UserService 인터페이스를 상속 받는 클래스를 2 가지로 나눔

    1. ServiceImpl(비즈니스 담당)
    2. ServiceTx(트랜잭션 담당)
  • Impl에선 비즈니스 로직을 담당하는 메서드만 남겨두고 Tx에선 userService를 구현한 다른 오브젝트를 주입 받고 트랜잭션 안에서 동작할 수 있도록 메서드 안에서 트랜잭션 경계를 설정

1
2
3
4
5
6
7
public void upgardeLevels() {
트랜잭션 스타트();

주입받은 userService를 구현한 오브젝트(Impl).upgrardeLevels();

commit() or rollback();
}

DI를 이용한 트랜잭션 경계설정 코드 분리의 장점

  1. 비즈니스 로직만 담긴 Impl 클래스에선 코드을 작성 시 트랜잭션 같은 비즈니스 로직과 관계 없는 부분에 대해 전혀 신경 쓰지 않아도 됌
  2. 테스트가 쉬워짐

다시 돌아와 AOP: 애스펙트(aspect) 지향 프로그래밍

  • 부가기능 모듈화 작업
  • 핵심기능에 부가되어 의미를 갖는 특별한 모듈
  • 에스펙트는 부가될 기능을 정의한 코드인 어드바이스, 어드바이스를 어디에 적용할지를 결정하는 __포인터컷__을 함께 갖고 있음
  • 위에서 트랜잭션 코드는 비즈니스 로직에 상관없는 기능인데 핵심기능에 침투해 들어가면서 설계와 코드가 모두 지저분해짐
  • 이런 부가기능 코든는 여기저기 메서드에 마구 흩어져서 나타나고 코드는 중복됌
  • 런타임 시에는 각 부가기능 에스펙트는 자기가 필요한 위치에 다이내믹하게 참여
  • 하지만 설계와 개발은 다른 특성을 띤 에스펙트들을 독립적인 관점으로 작성
  • AOP는 에스펙트를 분리함으로써 핵심기능을 설계하고 구현할 때 객체지향적인 가치를 지킬 수 있도록 도와주는 것
  • 애플리케이션을 특정한 관점을 기준으로 바라볼 수 있게 해준다는 의미에서 관점 지향 프로그래밍이라고도 함

프록시를 이용한 AOP

  • 스프링은 다양한 기술을 조합해 AOP를 지원하는데 가장 핵심은 프록시를 이용한다는 것
  • 프록시로 만들어서 DI로 연결된 빈 사이에 적용해 타깃의 메서드를 호출 과정에 참여 해서 부가기능을 제공
  • 스프링 AOP의 부가기능을 담은 어드바이스가 적용되는 대상은 오브젝트의 메서드

바이트코드 생성과 조작을 통한 AOP

  • AspectJ는 컴파일된 타깃의 클래스 파일 자체를 수정하거나 JVM에 로딩되는 시점을 가로채서 바이트코드를 조작하는 방식을 사용
  • 이 때 장점은,
    1. DI 컨테이너의 도움을 받아서 자동 프록시 생성방식을 사용하지 않아도 AOP 적용 가능하기 때문에 컨테이너가 없는 환경에서도 AOP의 적용 가능
    2. 프록시 방식보다 훨씬 강력하고 유연한 AOP가 가능, 프록시는 부가기능을 부여할 대상은 클라이언트가 호출 할 떄 사용하는 메서드로 제한되지만, 바이트코드를 직접 조작하기 떄문에 오브젝트의 생성, 필드 값으 조회와 조작, static 초기화등의 다양한 작업에 부가기능 부여 가능

AOP 용어 정리

  • 타깃
    • 부가기능을 부여할 대상
  • 어드바이스
    • 타깃에게 제공할 부가기능을 담을 모듈
  • 조인 포인트
    • 어드바이스가 적용될 수 잇는 위치
    • 스프링 AOP의 조인 포인트는 메서드의 실행단계
    • 타깃 오브젝트가 구현한 인터페이스의 모든 메서드는 조인 포인트가 됌
  • 포인트컷
    • 어드바이스를 적용할 조인 포인트를 선별하는 작업 또는 그 기능을 정의한 모듈
    • 메서드의 시그니처를 비교하는 방법을 주로 사용
  • 프록시
    • 클라이언트와 타깃 사이에 투명하게 존재하면서 부가기능을 제공하는 오브젝트
  • 어드바이저
    • 포인트컷과 어드바이스를 하나씩 갖고 있는 오브젝트
    • 어떤 부가기능(어드바이스)를 어디에(포인트 컷) 전달할 것인가를 알고 있는 AOP의 가장 기본이 되는 모듈
  • 애스펙트
    • AOP의 기본 모듈
    • 한 개 또는 그 이상의 포인트 컷과 어드바이스를 조합
    • 보통 싱글톤 형태의 오브젝트

댓글 공유

문제

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


코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public boolean solution(String[] phone_book) {
int length = phone_book.length;

for (int i = 0; i < length; i++) {
for (int j = 0; j < length; j++) {
if (i == j) {
continue;
}

if (phone_book[j].startsWith(phone_book[i])) {
return false;
}
}
}
boolean answer = true;
return answer;
}

흐름

  1. 배열을 전부 돌면서 해당 인덱스로 시장하는 단어가 있는 지 확인함


결과

번호 속도
테스트 1 통과 (0.78ms, 52.1MB)
테스트 2 통과 (0.52ms, 50.1MB)
테스트 3 통과 (0.65ms, 52.6MB)
테스트 4 통과 (0.51ms, 54.6MB)
테스트 5 통과 (0.55ms, 52.2MB)
테스트 6 통과 (0.66ms, 52.5MB)
테스트 7 통과 (0.87ms, 50.3MB)
테스트 8 통과 (0.77ms, 52.8MB)
테스트 9 통과 (0.64ms, 52.3MB)
테스트 10 통과 (0.57ms, 50.3MB)
테스트 11 통과 (0.65ms, 52.8MB)
  • 효율성 테스트
번호 속도
테스트 1 통과 (1.96ms, 60.3MB)
테스트 2 통과 (2.14ms, 59.8MB)

테스트 케이스

1
2
3
assertFalse(test.solution(new String[] {"119", "97674223", "1195524421"} ));
assertTrue(test.solution(new String[] {"123","456","789"} ));
assertFalse(test.solution(new String[] {"12","123","1235","567","88"} ));

여담

  • 사실 당연히 시간 초과로 안될 줄 알았는데 넘어가서 놀랐다.
  • 찾아보니 Trie 자료구조로 해결하길 바래서 카테고리가 해쉬 인 듯 싶다.
  • 하지만 쉽게 풀 수 있는 문제를 어렵게 풀 이유가 없으니 이 문제는 이렇게 마무리 하려한다.

댓글 공유

문제

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


코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public int solution(int[] citations) {
int answer = 0;
int n = citations.length;

Arrays.sort(citations);

for (int i = 0; i <= citations[n - 1]; i++) {
int high = 0;

for (int j = 0; j < n; j++) {
if (i <= citations[j]) {
high++;
}

if (i <= high) {
answer = i;
}
}
}

return answer;
}

흐름

  1. 논문을 인용한 횟수가 담긴 배열 citations를 정렬

  2. 가장 많이 인용한 횟수 만큼 반복

  3. 0부터 배열 길이만큼 반복하면서 i 값이 인용 횟수보다 작은 경우 i 보다 인용된 횟수가 많은 것이므로 high 변수 증가

  4. h번 이상 인용된 논문이 h편 이상이여야 하므로 i가 high 보다 작을 경우에만 answer에 저장

  5. i가 high보다 커질 경우 answer에 저장되지 않으므로 answer에 마지막으로 저장된 값이 h


다른 해결방법

코드

1
2
3
4
5
6
7
8
9
10
11
public int solution(int[] citations) {
Arrays.sort(citations);

int max = 0;
for(int i = citations.length-1; i > -1; i--){
int min = (int)Math.min(citations[i], citations.length - i);
if(max < min) max = min;
}

return max;
}

위 코드의 로직

  1. 배열을 정렬 후 배열의 마지막 값과 1 부터 증가 시키면서 citations의 길이 만큼 반복함

  2. 예를 들어 {0, 1, 3, 5, 6} 인 경우, citations.length는 5, i = 5 - 1 이므로 4, citations.length - i는 5 - 4가 되므로 1임 그런고로, 1과 citations[4]인 6과 비교해서 작은 놈을 min에 저장

  3. 위 과정을 반복하면
    min = Min(6,1)
    min = Min(5,2)
    min = Min(3,3)
    min = Min(1,4)
    min = Min(0,5)
    가 되고 max는 max가 min 보다 작을 때 max에 min 값이 저장되므로 max엔 최종적으로 3이 저장

  • 어떻게 유추해서 푼 건지 이해가 안간다 천재인 듯

결과

1번

번호 속도
테스트 1 통과 (11.43ms, 50.3MB)
테스트 2 통과 (14.71ms, 52.4MB)
테스트 3 통과 (15.13ms, 50.7MB)
테스트 4 통과 (13.74ms, 50.1MB)
테스트 5 통과 (16.49ms, 50.6MB)
테스트 6 통과 (13.86ms, 50MB)
테스트 7 통과 (10.08ms, 52.4MB)
테스트 8 통과 (7.45ms, 52.6MB)
테스트 9 통과 (8.26ms, 52.9MB)
테스트 10 통과 (10.29ms, 50MB)
테스트 11 통과 (17.04ms, 50.9MB)
테스트 12 통과 (8.21ms, 52.8MB)
테스트 13 통과 (15.30ms, 52.5MB)
테스트 14 통과 (13.62ms, 52.7MB)
테스트 15 통과 (15.64ms, 50.5MB)
테스트 16 통과 (0.97ms, 54.7MB)

2번

번호 속도
테스트 1 통과 (1.10ms, 52.4MB)
테스트 2 통과 (1.36ms, 50.5MB)
테스트 3 통과 (1.36ms, 50.8MB)
테스트 4 통과 (0.85ms, 51.9MB)
테스트 5 통과 (0.95ms, 52.5MB)
테스트 6 통과 (1.35ms, 52.7MB)
테스트 7 통과 (1.00ms, 50.6MB)
테스트 8 통과 (0.87ms, 50.9MB)
테스트 9 통과 (1.06ms, 50.5MB)
테스트 10 통과 (1.11ms, 52.6MB)
테스트 11 통과 (1.39ms, 52.5MB)
테스트 12 통과 (0.91ms, 52.6MB)
테스트 13 통과 (1.22ms, 50.3MB)
테스트 14 통과 (1.50ms, 52.5MB)
테스트 15 통과 (1.36ms, 52.3MB)
테스트 16 통과 (0.94ms, 52.5MB)
  • 속도도 압도적…

테스트 케이스

1
2
3
4
5
6
7
8
9
10
11
assertEquals(3, test.solution(new int[] {3,0,6,1,5}));
assertEquals(3, test.solution(new int[] {6,5,4,3,1,1,1,1,1}));
assertEquals(3, test.solution(new int[] {0, 1, 1, 1, 1, 3, 3, 4}));
assertEquals(3, test.solution(new int[] {5, 5, 5, 0}));
assertEquals(2, test.solution(new int[] {2,2,2,2,2}));
assertEquals(4, test.solution(new int[] {5, 5, 5, 5}));
assertEquals(5, test.solution(new int[] {5, 5, 5, 5, 5}));
assertEquals(1, test.solution(new int[] {7}));
assertEquals(3, test.solution(new int[] {4, 3, 3, 3, 3}));
assertEquals(0, test.solution(new int[] {0,0,0,0,0,0}));
assertEquals(3, test.solution(new int[] {6, 5, 3, 1, 0}));

댓글 공유

문제

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


코드

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
44
45
46
47
48
49
50
51
52
public String solution(int[] numbers) {

List<Numbers> list = new ArrayList<Numbers>();
for (int i = 0; i < numbers.length; i++) {
int number = numbers[i];

char[] str = String.valueOf(number).toCharArray();
StringBuilder fourDigitString = new StringBuilder();

fourDigitString.append(str);
if (str.length < 4) {

for (int j = 0; j < 4 - str.length; j++) {
fourDigitString.append(str[((j) % str.length)]);
}
}

list.add(new Numbers(i, Integer.parseInt(fourDigitString.toString())));
}

Collections.sort(list, new Comparator<Numbers>() {

@Override
public int compare(Numbers o1, Numbers o2) {
return o2.value.compareTo(o1.value);
}
});

StringBuilder sb = new StringBuilder();
int sum = 0;
for (Numbers n : list) {
sum += n.value;
sb.append(numbers[n.postion]);
}

if (sum == 0) {
sb.setLength(0);
sb.append(0);
}

return sb.toString();
}

class Numbers {
public Integer postion;
public Integer value;

public Numbers(Integer postion, Integer value) {
this.postion = postion;
this.value = value;
}
}

흐름

  1. 문제에서 numbers의 원소 값이 1000 이하라고 했으므로 numbers 크기 만큼 돌면서 numbers 안에 값이 4자리가 아닌 경우 자기 숫자를 뒤에 덧붙여서 4자리로 만듬
    • ex) 121 > 1212, 1 > 1111, 23 > 2323, …
  2. 현재 배열의 index와 값을 비교하기 위에 inner class를 하나 만들어서 index와 value를 저장하고 class를 list에 추가
  3. list를 class의 value 값으로 큰 수부터 나오도록 정렬
  4. list를 돌면서 value를 StringBuilder로 붙임
  5. 이 때, 0 0 0 0 일 경우 0 이 되어야 하므로 value들을 모두 더했을 때 값이 0이라면 0 만 붙임
  6. toString() 으로 String형으로 return

다른 분들의 해결방법

다른분의 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public String solution(int[] numbers) {
String answer = "";

List<Integer> list = new ArrayList<>();
for(int i = 0; i < numbers.length; i++) {
list.add(numbers[i]);
}
Collections.sort(list, (a, b) -> {
String as = String.valueOf(a), bs = String.valueOf(b);
return -Integer.compare(Integer.parseInt(as + bs), Integer.parseInt(bs + as));
});
StringBuilder sb = new StringBuilder();
for(Integer i : list) {
sb.append(i);
}
answer = sb.toString();
if(answer.charAt(0) == '0') {
return "0";
}else {
return answer;
}
}

위 코드의 로직

  • list에 numbers array를 그대로 저장하고 list의 저장된 element를 앞뒤로 붙여서 정수로 바꾼 후 비교해서 정렬
  • String을 다루는 부분이 많아서 속도는 살짝 떨어지는 듯

결과

1 번

번호 속도
테스트 1 통과 (139.80ms, 83.2MB)
테스트 2 통과 (88.71ms, 67MB)
테스트 3 통과 (167.96ms, 82.4MB)
테스트 4 통과 (17.95ms, 50.7MB)
테스트 5 통과 (129.09ms, 77.4MB)
테스트 6 통과 (116.84ms, 71.2MB)
테스트 7 통과 (1.44ms, 52.3MB)
테스트 8 통과 (1.38ms, 52.7MB)
테스트 9 통과 (1.20ms, 50.2MB)
테스트 10 통과 (1.46ms, 52.6MB)
테스트 11 통과 (1.65ms, 52.9MB)

2 번

번호 속도
테스트 1 통과 (340.75ms, 105MB)
테스트 2 통과 (236.67ms, 82.1MB)
테스트 3 통과 (350.56ms, 126MB)
테스트 4 통과 (80.72ms, 59.7MB)
테스트 5 통과 (300.18ms, 110MB)
테스트 6 통과 (280.61ms, 87.6MB)
테스트 7 통과 (31.26ms, 57.2MB)
테스트 8 통과 (35.88ms, 53.4MB)
테스트 9 통과 (34.27ms, 55.3MB)
테스트 10 통과 (30.44ms, 55.6MB)
테스트 11 통과 (40.16ms, 55.3MB)

테스트 케이스

1
2
3
4
5
6
7
8
9
assertEquals("6210", test.solution(new int[] {6, 10, 2}));
assertEquals("9534330", test.solution(new int[] {3, 30, 34, 5, 9}));
assertEquals("220200", test.solution(new int[] {2,200,20}));
assertEquals("2200", test.solution(new int[] {2,0,20}));
assertEquals("0", test.solution(new int[] {0,0,0}));
assertEquals("21212", test.solution(new int[] {12, 212}));
assertEquals("21221", test.solution(new int[] {212, 21}));
assertEquals("7000", test.solution(new int[] {0, 0, 70}));
assertEquals("1000000", test.solution(new int[] {0, 0, 0, 1000}));

댓글 공유

문제

![error]](/images/20200505/image.png)

위 화면 처럼 임시 비밀번호 발급 form을 만들어서 버튼엔 onclick, input box엔 enter key event를 달아서 ajax로 처리하였는데,

​클릭 이벤트로는 정상적으로 동작하였으나 enter key 의 경우 http status 415 Unsupported Media Type 에러가 발생하였다

1
2
3
4
5
6
7
8
9
10
There was an unexpected error (type=Unsupported Media Type, status=415).
Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported
org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:225)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:158)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:131)
at org.springframework.web.method.support.
HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:167)
...

보통 ajax 통신 시 415 에러가 발생 할 경우 ajax에서 설정한 content type과 contoroller에서 설정한 content type이 다를 경우 발생하는 데 이 경우엔 모두 json으로 설정해놓은 상태 하였는데도 불구하고 발생하여 매우 당황스러웠다

ajax

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

function resetPassword() {
const userName = document.querySelector("input[name=username]").value.trim();
const data = { username : userName };

$.ajax({
type:"POST"
, url: "/reset/password"
, data: JSON.stringify(data)
, dataType : "json"
, contentType: "application/json"
, cache: false
, success: function(data) {
let alert = document.querySelector(".mb-0");
alert.innerText = data;

document.querySelector(".alert.alert-success").style.display = "block";
}
});
}

Controller

1
2
3
4
5
6
7
8
9
10
@PostMapping(value="/reset/password", produces = "application/json")
@ResponseBody
public String processResetPasswordForm(@RequestBody User user) throws JsonProcessingException {
String tempPassword = userService.setTempPassWord(user);
String msg = "임시 비밀번호 : " + tempPassword;

System.out.println(msg);
ObjectMapper objMapper = getObjectMapperConfig();
return objMapper.writeValueAsString(msg);
}

해결

하여 헤매던 도중 enter key event를 지정하지 않아도 이벤트가 발생하여 똑같이 415 에러를 뱉어내는 것으로

확인돼서 적용한 thymeleaf나, bootstrap 등 어디선가 submit 을 보내고 있다고 판단하여 preventDefault() function으로 이벤트를 정지시키고 작성한 ajax를 실행시켰더니 정상적으로 동작하였다

form의 submit event에 preventDefault()를 추가

1
2
3
4
5
document.querySelector("#frm").addEventListener('submit', function(e) {
e.preventDefault();

resetPassword();
});

정상적으로 실행된 모습

![success]]](/images/20200505/image2.png)


전체 코드

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119

<!DOCTYPE html>
<html lang="ko"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/config :: configFragment"></head>
<body>
<div class="container">
<div class="d-flex justify-content-center h-100">
<div class="card" style="height:auto;">
<div class="card-header">
<h3>Reset Password</h3>
</div>
<div class="card-body">
<form id="frm" th:action=@{/reset/password} method="post">
<div class="alert alert-success" role="alert" style="display:none;">
<h4 class="alert-heading">Well done!</h4>
<p>해당 비밀번호는 임시로 생성된 비밀번호 입니다. <br />로그인 후 꼭 비밀번호를 변경하여 주세요.</p>
<hr>
<p class="mb-0"></p>
</div>
<div class="alert alert-danger" role="alert" style="float:left; margin-right:1rem; display:none;" ></div>
<div class="input-group form-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-user"></i></span>
</div>
<input type="text" onkeyup="checkUserName()" class="form-control" name="username" placeholder="username">
</div>
<div class="form-group">
<input type="submit" onclick="resetPassword()" value="확인" class="btn float-right login_btn" disabled>
</div>
</form>
</div>
<div class="card-footer">
<div class="d-flex justify-content-center links">
Don't have an account?<a href="/login">Sign In</a>
</div>
</div>
</div>
</div>
</div>
</body>
<link th:href="@{/css/login.css}" rel="stylesheet" type="text/css">
<!--Fontawesome CDN-->
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css" integrity="sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU" crossorigin="anonymous">

<script th:inline="javascript">

document.querySelector("#frm").addEventListener('submit', function(e) {
e.preventDefault();

resetPassword();
});

function checkUserName() {
const data = document.querySelector("input[name=username]").value;

let isExist = true;
$.ajax({
type:"POST"
, url: "/reset/password/check"
, data: JSON.stringify(data)
, dataType : "json"
, contentType: "application/json"
, cache: false
, success: function(data) {
let alert = document.querySelector(".alert.alert-danger");

if (empty(data)) {
alert.innerText = "";
alert.style.display = "none";
isExist = true;
document.querySelector(".btn.float-right.login_btn").removeAttribute("disabled");
} else {
alert.innerText = data;
alert.style.display = "initial";
isExist = false;
document.querySelector(".btn.float-right.login_btn").setAttribute("disabled", "disabled");
}
}
});

return isExist;
}

document.querySelector("input[name=username]").addEventListener("keydown", key => {
if (key.keyCode == 13) {
resetPassword();
}
});

function resetPassword() {
const isExist = checkUserName();

if (!isExist) {
return;
}

const userName = document.querySelector("input[name=username]").value.trim();
const data = JSON.stringify({ username : userName });

$.ajax({
type:"POST"
, url: "/reset/password"
, data: data
, dataType : "json"
, contentType: "application/json"
, cache: false
, success: function(data) {
let alert = document.querySelector(".mb-0");
alert.innerText = data;

document.querySelector(".alert.alert-success").style.display = "block";
}
});
}
</script>
</html>

전체 프로젝트 코드


참고 사이트

댓글 공유

문제

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


코드

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
44
45
46
47
48
public int solution(String s) {
int stringLength = s.length();

StringBuilder result = new StringBuilder();
String origin = new String();
char[] tochar = s.toCharArray();
int min = Integer.MAX_VALUE;

for (int i = 1; i <= stringLength / 2; i++) {
int compression = 1;
result.setLength(0);
origin = new String(tochar, 0, i);

String nextWord = new String();
for (int j = i; j < stringLength; j+=i) {
if (j + i > stringLength) {
nextWord = s.substring(j, stringLength);
} else {
nextWord = new String(tochar, j , i);
}

if (origin.equals(nextWord)) {
++compression;
} else {
if (compression != 1) {
result.append(compression);
}

result.append(origin);

origin = nextWord.toString();
compression = 1;
}
}

if (compression != 1) {
result.append(compression);
}

result.append(origin);

if (min > result.length()) {
min = result.length();
}
}

int answer = min > stringLength ? stringLength : min;
return answer;

흐름

  1. 문자열을 반으로 나눠서 2개로 나눈 것 보다 짧을 수 없으므로 문자열 길이 / 2 한 값 만큼 반복

  2. 비교 할 문자열을 구함(origin)

    • 처음엔 1개 짜리 이후엔 2개, 3개, … 증가 할 수 있도록 i를 증가 시키면서 i 번째 까지 자름
  3. i 번째 문자 이후로 비교 할 문자를 구함

  4. 같으면 압축율을 증가시키고

  5. 다르면 압축율과 문자를 더해서 압축문자열(result)을 만듬

  6. 비교 할 문자를 구하면서 마지막 문자는 안 붙여지고 팅기므로 for 문 밖에서 마지막 문자를 붙임

  7. 이전에 구했던 압축문자열 길이와 비교해서 더 작은 길이를 저장함


결과

번호 속도
테스트 1 통과 (0.99ms, 52.4MB)
테스트 2 통과 (2.92ms, 49.9MB)
테스트 3 통과 (1.72ms, 52.7MB)
테스트 4 통과 (0.99ms, 52.1MB)
테스트 5 통과 (0.84ms, 50MB)
테스트 6 통과 (1.03ms, 52.4MB)
테스트 7 통과 (3.49ms, 50.4MB)
테스트 8 통과 (2.49ms, 50.3MB)
테스트 9 통과 (4.46ms, 52.6MB)
테스트 10 통과 (9.25ms, 52.4MB)
테스트 11 통과 (1.25ms, 50.2MB)
테스트 12 통과 (1.28ms, 52.3MB)
테스트 13 통과 (9.63ms, 51.8MB)
테스트 14 통과 (4.35ms, 52.2MB)
테스트 15 통과 (1.31ms, 52.1MB)
테스트 16 통과 (0.85ms, 50.2MB)
테스트 17 통과 (8.91ms, 52.6MB)
테스트 18 통과 (7.70ms, 52.6MB)
테스트 19 통과 (8.63ms, 54.9MB)
테스트 20 통과 (7.89ms, 52.6MB)
테스트 21 통과 (9.99ms, 52.7MB)
테스트 22 통과 (10.63ms, 52.7MB)
테스트 23 통과 (9.68ms, 52.5MB)
테스트 24 통과 (7.89ms, 54.4MB)
테스트 25 통과 (10.43ms, 52.9MB)
테스트 26 통과 (8.17ms, 52.5MB)
테스트 27 통과 (15.60ms, 52.6MB)
테스트 28 통과 (0.88ms, 50.4MB)
  • result에 문자열을 더하지 말고 그냥 문자열 길이를 더하는 방식으로 하면 더 시간을 줄일 수 있음

테스트 케이스

1
2
3
4
5
6
7
8
assertEquals(7, test.solution("aabbaccc"));
assertEquals(9, test.solution("ababcdcdababcdcd"));
assertEquals(8, test.solution("abcabcdede"));
assertEquals(14, test.solution("abcabcabcabcdededededede"));
assertEquals(17, test.solution("xababcdcdababcdcd"));
assertEquals(1, test.solution("a"));
assertEquals(2, test.solution("aaaaa"));
assertEquals(3, test.solution("aaaaaaaaaa"));

참고 사이트

댓글 공유

Junggu Ji

author.bio


author.job