리스트 조건제시법(List Comprehension)

리스트를 쉽게 구현하는 방법을 배운다.

먼저 0부터 1억 사이의 모든 홀수를 항목으로 갖는 리스트를 구현하는 방법을 고민해보자. 가장 단순한 방법은 아래와 같이 홀수를 모두 나열하는 것이다.

[1, 3, 5, 7, 9, 11, ..., 99999999]

그런데 0부터 1억 사이의 총 5천만개의 홀수를 위와 같이 나열하는 것은 불가능하다. 1초에 하나씩 숫자를 적는다 해도 5천만 초, 약 1년 8개월이 걸린다.

이제 반복문을 이용해보자. 아래 함수는 0부터 입력된 숫자 사이의 모든 홀수를 항목으로 갖는 리스트를 리턴한다.


In [1]:
def odd_number(num):
    L=[]
    for i in range(num):
        if i%2 == 1:
            L.append(i)
    return L

1억 까지의 홀수들의 리스트를 생성하는 걸리는 시간을 확인해보자.


In [2]:
%time odd_sample1 = odd_number(100000000)


CPU times: user 11.3 s, sys: 1.96 s, total: 13.3 s
Wall time: 12.5 s

지금 사용하는 컴퓨터에서는 13초 정도 걸린다. 사람이 사실상 할 수 없는 일을 컴퓨터는 10여초 만에 해결함을 알 수 있다.

처음 20개의 홀수를 확인해보자.


In [3]:
odd_sample1[:20]


Out[3]:
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39]

이제 질문을 좀 달리하자. odd_number 함수를 좀 더 간결하게 정의할 수 없을까?

이에 대해 파이썬에서는 리스트 조건제시법이라는 기술을 제공한다. 이 기술을 모든 언어가 지원하지는 않으며, 지원하는 언어는 아래 사이트에서 확인할 수 있다.

https://en.wikipedia.org/wiki/List_comprehension

예를 들어, C# 언어는 from ... where ... select ... 가 비슷한 역할을 지원하지만 좀 다르고, Java 언어에서는 함수 인터페이스를 이용하여 비슷한 기능을 구현할 수 있다.

리스트 조건제시법 이해

리스트의 조건제시법을 이해하기 위해서는 집합 정의에 사용되는 조건제시법을 이해하면 된다.

0부터 1억 사이의 홀수들의 집합을 여러 방식으로 표현할 수 있다.

  • 원소 나열법

    {1, 2, 3, ...., 99999999}

  • 조건제시법 1

    {x | 0 <= x < 1억, 단 x는 홀수}

  • 조건제시법 2

    {2 * y + 1 | 0 <= y < 50000000}

앞서 언급했든 여기서도 원소나열법은 중략기호(...)를 사용하기 전에는 사실상 사용 불가능하다. 반면에 조건제시법 1과 2는 어느 누구가 의미를 이해할 수 있다. 리스트 조건제시법은 위에 사용된 조건제시법과 거의 모양이 비슷하다. 다만 프로그래밍 언어를 사용할 뿐이다.

먼저 조건제시법 1을 파이썬 코드로 옮기면 다음과 같다.


In [4]:
odd_number1 = [x for x in range(100000000) if x % 2 == 1]

위 코드에서 아래 부분이 핵심이다.

x for x in range(100000000) if x % 2 == 1

위 코드를 집합기호 대신 리스트 기호로 감싸기만 하면 리스트 원하는 리스트를 만들어준다.

  • for 는 집합 정의에서 왼쪽과 오른쪽을 구분하는 짝대기('|') 역할을 한다.

  • if 는 만족해야 하는 조건을 의미하는 '단' 역할을 한다.

  • x % 2 == 1는 홀수 여부를 물으며 참일 경우에만 사용된다는 것을 의미한다.

리스트 조건제시법을 사용하지 않으려면 다음고 같이 할 수 있다.

주의: 오천만개의 홀수를 나열하는 일은 피해야 한다. 너무 길다. 구현한 코드가 잘 작동하는지를 확인하고자 할 때는 작은 숫자를 사용해야 한다.

In [5]:
odd_number1 = []

for x in range(100000000):
    if x % 2 == 1:
        odd_number1.append(x)
    else:
        pass

odd_number1[:20]


Out[5]:
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39]

이제 조건제시법 2를 구현해보자.


In [6]:
odd_number2 = [2 * y + 1 for y in range(50000000)]

이 방식은 좀 더 쉬워 보인다. if 문이 없기 때문이다. 이렇게 기존의 리스트를 이용하여 새로운 리스트를 새롭게 만들 수 있다.

조건제시법을 사용하지 않는 방법은 아래와 같다.


In [7]:
odd_number2 = []

for x in range(50000000):
    odd_number2.append(2*x+1)

odd_number2[:20]


Out[7]:
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39]

예제

math 모듈의 exp 함수들의 값으로 이루어진 리스트 만들기.

예를 들어

[exp(0), exp(1), exp(2), ..., exp(10)]

In [8]:
import math

In [9]:
[math.exp(n) for n in range(11)]


Out[9]:
[1.0,
 2.718281828459045,
 7.38905609893065,
 20.085536923187668,
 54.598150033144236,
 148.4131591025766,
 403.4287934927351,
 1096.6331584284585,
 2980.9579870417283,
 8103.083927575384,
 22026.465794806718]

좀 더 복잡하게 사용해되 된다.


In [10]:
[math.exp(3*n) for n in range(11)]


Out[10]:
[1.0,
 20.085536923187668,
 403.4287934927351,
 8103.083927575384,
 162754.79141900392,
 3269017.3724721107,
 65659969.13733051,
 1318815734.4832146,
 26489122129.84347,
 532048240601.79865,
 10686474581524.463]

예제

단어들의 리스트인 words 를 살펴보자.


In [11]:
words = 'The quick brown for jumps \
over the lazy dog'.split()
words


Out[11]:
['The', 'quick', 'brown', 'for', 'jumps', 'over', 'the', 'lazy', 'dog']

words 리스트의 각 항목의 문자열들을 모두 대문자와 소문자로 바꾼 단어와 그리고 해당 항목의 문자열의 길이를 항목으로 갖는 리스트들의 리스트를 작성하고자 한다.

[['THE', 'the', 3], ['QUICK', 'quick', 5], ....]


반복문을 이용하여 아래와 같이 작성할 수 있다.


In [12]:
L =[]
for x in words:
    L.append([x.upper(), x.lower(), len(x)])
    
L


Out[12]:
[['THE', 'the', 3],
 ['QUICK', 'quick', 5],
 ['BROWN', 'brown', 5],
 ['FOR', 'for', 3],
 ['JUMPS', 'jumps', 5],
 ['OVER', 'over', 4],
 ['THE', 'the', 3],
 ['LAZY', 'lazy', 4],
 ['DOG', 'dog', 3]]

리스트 조건제시법으로는 아래와 같이 보다 간결하게 구현할 수 있다.


In [13]:
[[x.upper(), x.lower(), len(x)] for x in words]


Out[13]:
[['THE', 'the', 3],
 ['QUICK', 'quick', 5],
 ['BROWN', 'brown', 5],
 ['FOR', 'for', 3],
 ['JUMPS', 'jumps', 5],
 ['OVER', 'over', 4],
 ['THE', 'the', 3],
 ['LAZY', 'lazy', 4],
 ['DOG', 'dog', 3]]

처음 두 단어만 다루고자 할 경우에는 아래처럼 하면 된다.


In [14]:
[[x.upper(), x.lower(), len(x)] for x in words[:2]]


Out[14]:
[['THE', 'the', 3], ['QUICK', 'quick', 5]]

아래처럼 인덱스에 제한을 가하는 방식도 가능하다. 즉, if 문을 추가로 활용한다.


In [15]:
[[words[n].upper(), words[n].lower(), len(words[n])] \
 for n in range(len(words)) if n < 2]


Out[15]:
[['THE', 'the', 3], ['QUICK', 'quick', 5]]