별로 어렵지 않은 문제였다.

 

다만 코틀린스럽게 풀지는 못했다...

내가 제출한 코드

class Solution {
    fun solution(numbers: IntArray): IntArray {
        var answer: IntArray = intArrayOf()
        var intset = mutableSetOf<Int>()
        
        for(i in 0 until numbers.size - 1) {
            for(j in i+1 until numbers.size) {
                intset.add(numbers[i] + numbers[j])
            }
        }
        answer = intset.sorted().toIntArray()
        return answer
    }
}

 

이중for문을 이용해서 더해주었고 원소가 중복되면 안되기에 Set를 사용했다.

 

내 생각 코틀린스럽게 푼 사람 (사실 코틀린스럽다는 말을 정확히 몰라 틀릴 수 있음,,)

뭔가 코틀린 함수 쓴게 코틀린스러운건가,,ㅎㅎ

다른 분의 코드

class Solution {
    fun solution(numbers: IntArray): IntArray {
        val list = numbers.toList()
        return list.withIndex().flatMap { i -> list.withIndex().map { j -> i to j } }
            .filter { it.first.index != it.second.index }
            .map { it.first.value + it.second.value }
            .toSortedSet()
            .toIntArray()
    }
}

 

분석해보면 공부에 도움이 될거 같아 분석한번 해보겠습니다.

 

분석에 앞서 필요한 확장함수를 조금 알아보고 가자.

.withIndex() -> 객체의 원소를 (index, value) 형태로 접근할 수 있게 해준다.

예) list = listOf("a", "b", "c")

      indexedList = list.withIndex() 

      print(indexedList)

[IndexedValue(index=0, value=a), **IndexedValue(index=1, value=b)**, IndexedValue(index=2, value=c)]

 

이런식으로 출력된다.

 

.flatMap -> 컬렉션 안에 컬렉션이 있을 경우 내부 컬렉션을 펼쳐주는 연산이며 .map과 다르게 1대1매핑이 아닌 1대 多 매핑이 가능하다. 즉 모든 요소들의 생성값을 다룰 수 있다. 

 

.filter -> 간단히 필터 조건문안에 조건을 만족하는 원소들만 받아서 가져오겠다는거다.

 

이제 코드를 하나하나 뜯어보자. (너무 깊숙이 뜯진 못한다,,)

 

val list = numbers.toList()

numbers를 리스트 형태로 바꾸어 list에 담아준다.

 

list.withIndex().flatMap { i -> list.withIndex().map { j -> i to j } }

 

이 부분이 이해하기 굉장히 어려웠던 부분이었다. 사실 완벽하게 이해한 것인지는 모르나 틀렸을 경우 댓글부탁드립니다.

우선 문제의 첫 번째 입출력 예인 [2,1,3,4,1] 을 입력값으로 해서 예시로 사용해보겠다.

 

withIndex() 를 통해 [(0,2) (1,1) (2,3) (3,4) (4,1)] 이런식의 원소의 인덱스와 실제 value가 포함된리스트가 반환된다.

 

flatMap() 을 통해 이 안에 원소들을 1대 다 매핑이 가능하게 만들어줌과 동시에 마지막엔 싱글리스트로 반환해준다.

.map{} 안에 "i to j"는 i와 j를 pair시켜주는 역할이다. 

 

[(0,2) (1,1) (2,3) (3,4) (4,1)] 이 리스트의 i로 돌아가는 반복문과 list.withIndex().map의 j 인덱스로 돌아가는 반복문으로 

j가 i를 기준으로 각 원소를 순회하며 i to j를 실행한다. 가독성을 위해 많은 띄어쓰기를 넣었습니다...

 

[(0,2) (0,2)    ,     (0,2) (1,1)    ,    (0,2) (2,3)   ,   (0,2) (3,4)    ,  (0,2) (4,1)]

 

마찬가지로 다음 i 원소인 (1,1)을 기준으로 실행

[ (1,1) (0,2)   ,     (1,1)  (1,1)    ,   (1,1) (2,3)   ,   (1,1) (3,4)    ,    (1,1)  (4,1)]

 

이런식으로 하면 반복하면 

[(0,2) (0,2)    ,     (0,2) (1,1)    ,    (0,2) (2,3)   ,   (0,2) (3,4)    ,  (0,2) (4,1)

(1,1) (0,2)   ,      (1,1)  (1,1)   ,    (1,1) (2,3)   ,   (1,1) (3,4)    ,  (1,1)  (4,1)

(2,3) (0,2)   ,      (2,3)   (1,1)   ,   (2,3)  (2,3)  ,   (2,3) (3,4)    ,  (2,3)   (4,1)

(3,4) (0,2)   ,      (3,4)   (1,1)   ,   (3,4)  (2,3)   ,  (3,4) (3,4)    ,  (3,4)   (4,1)

(4,1) (0,2)   ,      (4,1)   (1,1)   ,   (4,1)  (2,3)   ,   (4,1) (3,4)    ,  (4,1)   (4,1)]

 

이런식의 리스트가 반환됩니다.

 

.filter { it.first.index != it.second.index }

 

서로 다른 인덱스의 있는 두 수를 더해주기 위해서는 이 리스트에서 filter를 통해 (0,2)(0,2) , (1,1)(1,1) , (2,3)(2,3) ,  (3,4) (3,4)  , (4,1)(4,1) 과 같은 인덱스 값이 같은 원소들을 제외하고 인덱스 값이 다른 원소들만 걸러줍니다.

 

[                  ,     (0,2) (1,1)    ,    (0,2) (2,3)   ,   (0,2) (3,4)    ,  (0,2) (4,1)

(1,1) (0,2)   ,                           ,    (1,1) (2,3)   ,   (1,1) (3,4)    ,  (1,1)  (4,1)

(2,3) (0,2)   ,      (2,3)   (1,1)   ,                        ,   (2,3) (3,4)    ,  (2,3)   (4,1)

(3,4) (0,2)   ,      (3,4)   (1,1)   ,   (3,4)  (2,3)   ,                        ,  (3,4)   (4,1)

(4,1) (0,2)   ,      (4,1)   (1,1)   ,   (4,1)  (2,3)   ,   (4,1) (3,4)    ,                      ]

 

.map { it.first.value + it.second.value }

 

원소들의 value 를 합해준 리스트를 반환합니다.

[3, 5, 6, 3, 

3, 4, 5, 2,

5, 4, 7, 4,

6, 5, 7, 5,

3, 2, 4, 5] 

 

.toSortedSet()

 

Set 컬렉션의 가장 큰 특징인게 중복된 원소가 없는 없다는것입니다.

toSortedSet() 으로 set 컬렉션형태로 바꾸고 오름차순으로 정렬해줍니다.

 

그리고 마지막은 반환형태에 맞게 toIntArray()로 맞춰줍니다.

 

끄읕

 

c 코드로 짜면 이중 for문이 들어가게 짤 텐데.. 코틀린은,,

class Solution {
    fun solution(array: IntArray, commands: Array<IntArray>): IntArray {
        var answer = intArrayOf()
        answer = commands.map{ array.slice(it[0]-1 until it[1]).sorted()[it[2]-1] }.toIntArray()
        return answer
    }
}

 

한 줄로 코틀린스럽게,,,

 

우선 map으로 원소 하나 하나에 접근을 해주고 slice로 배열의 원하는 만큼 짤라서 정렬시켜주고 그 배열의 특정 인덱스 값 원소를 가져와서 IntArray로 바꿔서 리턴해준다.

 

 

 

처음 이 문제를 생각했을 땐 굉장히 복잡하게 느껴졌다.

 

mapIndexed로 접근해서 인덱스 값으로 접근을 해볼까 이런식으로,,

정렬을 보면 sort를 떠올려야하는데 아직 많이 부족하다..

 

한참을 헤매다가 결국 검색을 했다.. 

 

검색결과 다중 정렬이라는 아주 멋지고 간단한 방법을 발견했다.

 

하나 이상의 기준을 가지고 정렬을 해야할 때 사용하면 정말 쉽게 구현가능하다.

 

바로 sortedWith() 함수와 compareBy를 comparator로 사용하는 것이다.

 

코드를 먼저 보자.

 

class Solution {
    fun solution(strings: Array<String>, n: Int): Array<String> {
        var answer = arrayOf<String>()
        
        answer = strings.sortedWith(compareBy({it[n]}, {it})).toTypedArray()
        
        return answer
    }
}

 

우선 sortedWith 함수는 원본 리스트를 바꾸지 않고 인자로 comparator를 받아 여러개의 정렬 기준으로 새로운 리스트를 반환한다.

 

여기서는 compareBy라는 comparator를 사용하여 두 개의 정렬 기준에서 우선 순위를 부여하여 첫번째로 it[n]을 기준으로 정렬하고 그 후에 그 기준을 적용시킨 채로 두 번째 정렬 기준인 기본 오름차순 정렬을 해준것이다.

(tip: sortedBy는 정렬 기준이 하나 일 때 사용^^)

 

compareBy는 기본적으로 오름차순 정렬이며 내림차순으로 하고 싶을 때는 compareByDescending()을 써주면 된다.

 

 

처음에는 map으로 처리를 할까 하였으나 인덱스 값을 따져가며 하는게 비효율적이라 생각되어 고민을 좀 했다.

 

문자열 안에서 숫자는 냅두고 영단어만 숫자로 바꿀 순 없을까 하다가 찾은게 replace()

replace(바꿀녀석, 원하는결과) 이런식으로 쓰면 된다.

 

우선 코드를 보자.

 

class Solution {
    fun solution(s: String): Int {
        var answer : Int = 0
        val numArr = arrayOf("zero", "one", "two", "three", "four", "five", "six", "seven","eight", "nine")
        var temp = s
        for (i in 0 until numArr.size) {
            temp = temp.replace(numArr[i], i.toString())
        }
        answer = temp.toInt()
        return answer
    }
}

 

 

영단어를 배열에 넣어 반복문으로 해당하는 영단어가 있으면 그 영단어가 있었던 인덱스 값을 문자열값으로 넣어준다.

 

여기서 신경써줘야 할건 숫자를 배열에 순서대로 넣어준다는 것! one zero four two nine --- 이런식으로 넣으면 안된다. 또한 반복문의 조건식도 신경써서 넣어주어야한다.

 

배열을 잘 쓰면 까다로울 수 있는 문제도 쉽게 해결 가능하다는 걸 느낀 문제다.

 

 

시저 암호 문제

 

 

아스키 코드를 알아야 z 에서 a로 다시 돌아올 수 있을 것 같다.

 

아스키 코드

아스키 코드는 요래 생겼다.

 

A - Z 는 65 부터 90 a -z 는 97 부터 122

 

class Solution {
    fun solution(s: String, n: Int): String {
        var answer = ""
        //[a, B, z] , 4
        s.map {
            if(it == ' '){
                answer += it
            }
            else if(it.isUpperCase()){
                if((it + n).toInt() > 90){
                    answer += (it + n) - 26
                }
                else {
                    answer += it + n
                }
            }
            else {
                if((it + n).toInt() > 122){
                    answer += (it + n) - 26
                }
                else{
                    answer += it + n
                }
            }
        }
        
        return answer
    }
}

 

.map 함수를 사용하여 문자열을 리스트에 담아 원소 하나하나를 다뤄준다. 활용도가 매우 좋은 듯^^

예시를 위해 "abcd" 문자열을  .map으로 ["a", "b", "c", "d"] 이런 형식으로 바꿔줄 수 있다.

 

공백은 그대로 문자열에 넣고 대문자와 소문자를 1차적으로 구별한다. 

a 부터 z 가 순환해야 하기 때문에 아스키값 범위를 넘어가면 다시금 처음으로 돌아오도록 한다.

 

이해를 돕기 위해 예시를 들자면, 만약  X(아스키값->88) 를 4 만큼 밀었을때 아스키 값 90을 초과하므로 초과한 만큼의 값을 얻기위해 X + 4 에서 90을 빼준다. 예시에서는 88 + 4 - 90 이므로 2가 남게 된다. A에서 2만큼 더 밀어줘야하는데 

A는 아스키값으로 65 이므로 2에 64를 더해주면 된다. 이 과정은 동일하게 순환하게 해주는 값 -90+64 이므로 위에서는 간단하게 -26으로 해주었다. 

 

이보다 훨씬 간단한 코드가 있어 공유한다.

 

class Solution {
    fun solution(s: String, n: Int): String =
        s.map {
            when {
                it.isLowerCase() -> 'a' + (it + n - 'a') % 26
                it.isUpperCase() -> 'A' + (it + n - 'A') % 26
                else -> ' '
            }
        }.joinToString("")
}

 

내가 작성한 코드보다 확실히 간결하고 이쁘다,,

 

 

잘못된 내용 있으면 댓글 부탁드립니다.

+ Recent posts