Algorithm/백준 알고리즘 풀이

파이썬 리스트 컴프리헨션 사용하기. (feat. 백준 10816번)

최동훈1 2023. 10. 28. 01:51
print(" ".join(str(dict[i]) if i in dict else '0' for i in list_nums))

 

위의 코드를 보고 설명을 하겠다. 나도 처음에 이런 종류의 코드를 래퍼런스를 찾다가 발견했을때 상당히 당황했지만, list comprehension의 규칙을 안다면 쉽게 해석 할 수 있다. 

 

결론부터 말하자면 이 코드는 파이썬의 리스트 컴프리헨션(list comprehension)과 조건부 표현식(conditional expression)을 사용한 것이다.

 

1. 리스트 컴프리헨션(list comprehension) : 리스트 컴프리헨션은 리스트를 생성하는 짧고 간결한 방법이다. 일반적인 for-loop보다 코드가 짧고 읽기 쉬우며, 때로는 실행 속도도 빠르다. `for i in list_nums` 부분이 이에 해당한다. `list_nums`의 각 요소에 대해 어떤 연산을 수행하겠다는 의미이다.

 

2. 조건부 표현식(conditional expression) : 조건부 표현식은 일반적인 if-else 문을 한 줄로 표현한 것이다. `str(dict[i]) if i in dict else '0'` 부분이 이에 해당한다. 이는 "만약 `i`가 `dict`에 있으면 `str(dict[i])`를, 그렇지 않으면 `'0'`을 반환하라"는 의미이다.

따라서 이 코드 전체의 동작은 다음과 같다:

1. 먼저 `list_nums`의 각 요소 `i`에 대해 순회한다.

2. 각 `i`에 대해, `i`가 `dict`에 존재하면 `dict[i]`를 문자열로 변환하여 리스트에 추가하고, 그렇지 않으면 문자열 `'0'`을 리스트에 추가한다.

 

3. 이렇게 생성된 리스트를 `" ".join()`을 사용하여 공백으로 이어붙여 문자열로 만든다. 이 방식을 사용하면 여러 줄의 코드를 한 줄로 간결하게 표현할 수 있다. 

이 문장을 이해가 잘 안된다면 아래와 같은 긴 원문으로 보면 쉽다.

for num in list_nums:
    if num in dict:
        print(dict[num],end=" ")
    else:
        print(0,end=" ")

또한 의문점이 들수 있는게 아래의 코드는 왜 문법적으로 말이 안되는지 이해가 안됬다. 특히, if 뒤에 else가 반드시 와야한다는 점이 의문이 든다. 이 경우도 리스트 컴프리핸션의 조건문 역할을 이해한다면 바로 이해할 수 있다.

print(" ".join(str(dict[i]) if i in dict for i in list_nums))

파이썬의 리스트 컴프리헨션에서 조건문(if)을 사용할 때, `if`만 단독으로 사용하는 경우와 `if-else`를 같이 사용하는 경우가 있다.

1. `if`만 사용하는 경우: 이 경우 `if`문은 필터링 역할을 한다. 즉, `if`문의 조건을 만족하는 요소들만 리스트에 포함시킨다. 이 경우 `if`문은 `for`문 뒤에 위치한다. 예를 들어

list= [i for i in range(10) if i%2==0]

은 0부터 9까지의 짝수만을 리스트에 포함시켜서 리스트를 생성한다.

 

 

2. `if-else`를 사용하는 경우: 이 경우 `if-else`문은 각 요소의 값을 결정하는 역할을 합니다. 즉, 모든 요소들이 리스트에 포함되지만, 그 값이 `if-else`문에 따라 달라진다. 이 경우 `if-else`문은 `for`문 앞에 위치한다. 예를 들어

list=[i if i%2==0 else -1 for i in range(10)]

은 0부터 9까지의 수 중 짝수는 그대로, 홀수는 -1로 값을 변경하여 리스트에 포함시킵니다. 따라서, `print(" ".join(str(dict[i])) if i in dict for i in list_nums)`  이런 코드는 문법적으로 올바르지 않다. 여기서는 `if`문이 각 요소의 값을 결정하는 역할을 하려고 하지만, `else`문이 없어서 그 값을 어떻게 결정해야 할지 모호하기 때문이다. 그래서 아래와 같이 `else`문을 추가해주면 문제가 해결된다:

 print(" ".join(str(dict[i]) if i in dict else '0' for i in list_nums))

이 코드에서 `'0'`은 문자열이며, 리스트 컴프리헨션의 결과 리스트의 각 요소는 문자열이어야 한다. 왜냐하면 `join()` 메소드는 문자열의 리스트를 결합하기 때문이다. 따라서 `i`가 `dict`에 없을 때는 문자열 `'0'`을 리스트에 추가하게 된다.

 

여기까지 왔으면 파이썬의 리스트 컴프리핸션 규칙을 다 이해한 것이다. 근데 마지막 관문이 남아있다. 아래의 질문을 생각해 보자.


아니 아직도 이해가 안되는데 그럼 if i in dict else '0' for i in list_nums 이 코드가 리스트를 생성하는것인가 ?
그런데 이것이 join의 dict[i]와 무슨 관련이 있어서 문자열이 완성되는거지 ?

정확히는

i if i in dict else '0' for i in list_nums

이 함수가 리스트를 생성하는 것이다. 즉, 조건문을 통과한 변수 i 에 들어가 있는 값이 나 문자 '0' 이 요소로 포함된 배열이 생성되는 것이다.

 str(dict[i]) if i in dict else '0' for i in list_nums

이 코드는 리스트 컴프리헨션을 사용하여 새로운 리스트를 생성하는 코드이다. 이 리스트는 `list_nums`의 각 요소 `i`에 대해, `i`가 `dict`에 존재하면 `dict[i]`의 값을 문자열로 변환하여 추가하고, `i`가 `dict`에 존재하지 않으면 문자열 '0'을 추가하게 된다. 따라서 이 코드의 결과는 문자열의 리스트이다. 예를 들어, `list_nums = [1, 2, 3]`, `dict = {1: 'a', 2: 'b'}`라면, 이 코드의 결과는 `['a', 'b', '0']`이 된다.

그럼 " ".join() 함수는 뭐지 ?

 

`join()` 메소드는 문자열의 리스트를 하나의 문자열로 결합하는 역할을 한다. `" ".join(['a', 'b', '0'])`를 실행하면, 리스트의 각 요소 사이에 공백을 삽입하여 하나의 문자열을 만든다. 따라서 결과는 `'a b 0'`이 된다. 그래서 `" ".join(str(dict[i]) if i in dict else '0' for i in list_nums)`를 실행하면, `list_nums`의 각 요소 `i`에 대해 `i`가 `dict`에 존재하면 `dict[i]`의 값을 문자열로 변환하고, 그렇지 않으면 '0'을 추가하여 이들을 공백으로 이어붙인 하나의 문자열을 생성하게 된다.

 

즉 결론은,

 str(dict[i]) if i in dict else '0' for i in list_nums

위 코드를 실행 하면 요소가 문자열인 리스트가 생성된다. 이 리스트의 각 문자 요소들을 하나의 문자열로 합쳐주는 것이다.

 

자바로 알고리즘 푸는 것에 익숙하다 보니 파이썬에 간결한 코드 를 보고 놀랍다. 조금더 열심히 해서 익숙해 져야 겠다.

그리고 요즘 하루에 백준 1문제씩 풀고 깃에 올리는 1일 1백준을 실행하고 있다. 뭔가 잔디가 열심히 쌓이는 거 같아서 좋다. 

 

아래는 실제 백준 10816번을 해결하는 과정에서 리스트 컴프리핸션을 사용하여 코드를 리팩토링한 기록이다. 혹시 정확한 코드를 보고 싶다면 아래의 코드를 참고 바란다.

 

1. 리스트 컴프리핸션 적용 전 풀이

[백준] : 10816번 딕셔너리를 이용해 검색 시간 최적화 O(1) · ulsandonghun/PS@b03005c (github.com)

 

[백준] : 10816번 딕셔너리를 이용해 검색 시간 최적화 O(1) · ulsandonghun/PS@b03005c

ulsandonghun committed Oct 27, 2023

github.com

2. 리스트 컴프리핸션 적용 후 풀이

[refactor] : list 컴프리헨션을 이용한 코드 리펙토링 · ulsandonghun/PS@0142e58 (github.com)

 

[ërefactor] : list 컴프리헨션을 이용한 코드 리펙토링 · ulsandonghun/PS@0142e58

ulsandonghun committed Oct 27, 2023

github.com