성장, 그 아름다운 향연

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

 

20920번: 영단어 암기는 괴로워

첫째 줄에는 영어 지문에 나오는 단어의 개수 $N$과 외울 단어의 길이 기준이 되는 $M$이 공백으로 구분되어 주어진다. ($1 \leq N \leq 100\,000$, $1 \leq M \leq 10$) 둘째 줄부터 $N+1$번째 줄까지 외울 단

www.acmicpc.net

문제

화은이는 이번 영어 시험에서 틀린 문제를 바탕으로 영어 단어 암기를 하려고 한다. 그 과정에서 효율적으로 영어 단어를 외우기 위해 영어 단어장을 만들려 하고 있다. 화은이가 만들고자 하는 단어장의 단어 순서는 다음과 같은 우선순위를 차례로 적용하여 만들어진다.

  1. 자주 나오는 단어일수록 앞에 배치한다.
  2. 해당 단어의 길이가 길수록 앞에 배치한다.
  3. 알파벳 사전 순으로 앞에 있는 단어일수록 앞에 배치한다

M보다 짧은 길이의 단어의 경우 읽는 것만으로도 외울 수 있기 때문에 길이가 M이상인 단어들만 외운다고 한다. 화은이가 괴로운 영단어 암기를 효율적으로 할 수 있도록 단어장을 만들어 주자.

입력

첫째 줄에는 영어 지문에 나오는 단어의 개수 N과 외울 단어의 길이 기준이 되는 M이 공백으로 구분되어 주어진다. (1≤N≤100000, 1≤M≤10)

둘째 줄부터 N+1번째 줄까지 외울 단어를 입력받는다. 이때의 입력은 알파벳 소문자로만 주어지며 단어의 길이는 10을 넘지 않는다.

단어장에 단어가 반드시 1개 이상 존재하는 입력만 주어진다.

출력

화은이의 단어장에 들어 있는 단어를 단어장의 앞에 위치한 단어부터 한 줄에 한 단어씩 순서대로 출력한다.

예제 입력

7  4
apple
ant
sand
apple
append
sand
sand

예제 출력

sand
apple
append

 


 

hashmap으로 구현하는 이유는,

 

 

문제 조건에서 자주 나오는 단어일수록 앞에 배치가 있기 때문에

<String, Int> 로 선언시켜서 정렬을 수행할 때 value 값을 비교시키면 가능하다.

 

 

이 문제는 시간 초과로 꽤 애를 먹었다.

처음에 제출한 코드는 다음과 같다.

 

fun main() = with(System.`in`.bufferedReader()) {
    val bw = System.out.bufferedWriter()
    val (n, m) = readLine().split(" ").map { it.toInt() }
    val map = hashMapOf<String, Int>()
    
    //***************** 이 부분을 주목! *****************
    for(i in 0 until n) {
        val s = readLine()
        if (s.length < m)
            continue
        if (map.contains(s)) {
            map[s] = map[s]!! + 1
        } else {
            map[s] = 0
        }
    }
     //************************************************

    val result = map.entries.sortedWith(kotlin.Comparator { o1, o2 ->
        when {
            o1.value != o2.value -> {
                o2.value.compareTo(o1.value)
            }
            o1.key.length != o2.key.length -> {
                o2.key.length - o1.key.length
            }
            else -> {
                o1.key.compareTo(o2.key)
            }
        }
    })

    result.forEach {
        bw.write("${it.key}\n")
    }
    bw.flush()
    bw.close()
}

 

아래 정렬을 수행하는 코드는 아무리 뚫어져 쳐다봐도

 

 

1. 많은 출력값을 요구하기 때문에 bufferedWriter 로 출력 버퍼에 저장한 점,

2. sortWith를 이용한 정렬한 점.

 

충분히 시간 초과를 피할 수 있는 조건 같았다.

 

 

하지만 map.contains(s) 에서 뭔가 속도를 잡아먹을진 않을까 생각했고, 구글링을 통해 map의 key 값을 찾는 여러 방법 중에 가장 원초적이고, 시간 효율이 떨어진 것을 확인했다.

 

 

실행 속도에 대해 더 궁금한 사람은 아래를 보면 좋겠다.

 

참조

http://daplus.net/java-java%EC%97%90%EC%84%9C-%EB%A7%B5-%EA%B0%92%EC%9D%84-%EC%A6%9D%EA%B0%80%EC%8B%9C%ED%82%A4%EB%8A%94-%EA%B0%80%EC%9E%A5-%ED%9A%A8%EC%9C%A8%EC%A0%81%EC%9D%B8-%EB%B0%A9%EB%B2%95/

 

[java] Java에서 맵 값을 증가시키는 가장 효율적인 방법 - 리뷰나라

이 질문이이 포럼에서 너무 기본적이지 않기를 바랍니다. 그러나 우리는 보게 될 것입니다. 여러 번 실행되는 더 나은 성능을 위해 일부 코드를 리팩터링하는 방법이 궁금합니다. Map (아마 HashMap

daplus.net

 

 

내가 찾은 방법은 Java 8에서 제공하는 Lambda Expression 을 이용하는 것이다.

 

map.merge(key, 1, Integer::sum)

 

 

hashmap에서 제공하는 map의 merge함수는 다음과 같은 기능을 제공한다.

 

  • key가 존재하지 않을 경우, value에 1을 넣는다.
  • key가 존재할 경우, 기존의 value에 1을 추가한다.

 

 

결국 위와 같은 방법을 통해 시간초과 늪에 탈출할 수 있었다.

 

전체 코드는 다음과 같다.

 

fun main() = with(System.`in`.bufferedReader()) {
    val bw = System.out.bufferedWriter()
    val (n, m) = readLine().split(" ").map { it.toInt() }
    val map = hashMapOf<String, Int>()
    for(i in 0 until n) {
        val s = readLine()
        if (s.length < m)
            continue
        map.merge(s,1, Integer::sum)
    }

    val result = map.entries.sortedWith(kotlin.Comparator { o1, o2 ->
        when {
            o1.value != o2.value -> {
                o2.value.compareTo(o1.value)
            }
            o1.key.length != o2.key.length -> {
                o2.key.length - o1.key.length
            }
            else -> {
                o1.key.compareTo(o2.key)
            }
        }
    })

    result.forEach {
        bw.write("${it.key}\n")
    }
    bw.flush()
    bw.close()
}

 

 

결론 : hashmap에 value 값을 key의 갯수만큼 가지는 로직을 구현한다면, containsKeys나 contains보다 merge 함수를 사용하자!

profile

성장, 그 아름다운 향연

@dev_minoflower

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

profile on loading

Loading...