리스트는 번호가 붙은 칸이 한 줄로 늘어선 사물함이다. 번호만 알면 바로 꺼낼 수 있고, 맨 끝에 넣고 빼는 것도 빠르다. 대신 맨 앞이나 가운데에 넣고 빼면 뒤의 칸을 모두 한 칸씩 밀어야 해서 느리다. 무엇이 들어 있는지 찾을 때도 처음부터 다 봐야 한다. 문자열은 글자로 된 줄인데, 한 번 만들면 고칠 수 없어서 바꿀 때마다 새로 만든다.
예시로 보기
a = [10, 20, 30, 40, 50]을 칸으로 그리면 이렇다.
값 10 20 30 40 50
앞 번호 0 1 2 3 4
뒤 번호 -5 -4 -3 -2 -1
- 칸 번호(인덱스)는 0부터 센다.
a[0]은 10,a[4]는 50이다. - 음수 번호는 뒤에서 센다.
a[-1]은 맨 끝의 50이다. - 칸 수는
len(a)로 알며 5다. 쓸 수 있는 번호는 0~4, 또는 −5~−1이다.a[5]를 꺼내면IndexError가 난다.
자르기 (슬라이싱)
a[i:j]는 i번부터 j번 바로 앞까지 잘라 새 리스트로 만든다. j번 칸은 들어가지 않는다.
| 코드 | 결과 | 풀어 말하면 |
|---|---|---|
a[1:4] |
[20, 30, 40] |
1, 2, 3번 칸 |
a[:2] |
[10, 20] |
처음부터 2번 앞까지 |
a[3:] |
[40, 50] |
3번부터 끝까지 |
a[::2] |
[10, 30, 50] |
한 칸씩 건너뛰며 |
a[::-1] |
[50, 40, 30, 20, 10] |
거꾸로 |
잘라 낸 것은 복사본이다. b = a[1:3]로 만든 b를 고쳐도 a는 그대로다. 대신 복사하는 데 자른 길이만큼 시간이 든다.
넣고 빼기와 비용
| 코드 | 하는 일 | 비용1 |
|---|---|---|
a[i], a[i] = x |
i번 칸 읽기, 바꾸기 | \(O(1)\) |
a.append(x) |
맨 끝에 넣기 | \(O(1)\) (평균) |
a.pop() |
맨 끝을 빼서 돌려주기 | \(O(1)\) |
a.pop(0), a.insert(0, x) |
맨 앞에서 빼기, 넣기 | \(O(n)\). 뒤 칸을 모두 민다 |
x in a, a.index(x), a.count(x), a.remove(x) |
찾기, 세기, 지우기 | \(O(n)\). 처음부터 본다 |
a[i:j] |
잘라 복사하기 | \(O(j - i)\) |
a.sort(), sorted(a) |
정렬 | \(O(n \log n)\) |
비용 표기는 시간 복잡도로 방법 고르기를 따른다. 맨 앞에서 자주 빼야 하면 덱(deque)을, 들어 있는지 자주 물어야 하면 집합을 쓴다.
한 줄로 만들기 (컴프리헨션)
squares = [x * x for x in range(5)] # [0, 1, 4, 9, 16]
evens = [x for x in a if x % 20 == 0] # [20, 40]
[만들 값 for 이름 in 모음 if 조건]은 아래 반복문을 한 줄로 쓴 것이다.
evens = []
for x in a:
if x % 20 == 0:
evens.append(x)
2차원 리스트 (표)
격자나 표는 리스트 안에 리스트를 넣어 만든다. grid[r][c]는 r번째 줄의 c번째 칸이다.
n, m = 3, 4
grid = [[0] * m for _ in range(n)] # 3줄 4칸, 줄마다 따로 만든다
grid[1][2] = 7
[[0] * m] * n으로 만들면 안 된다. 이렇게 하면 같은 줄 하나를 n번 가리키는 표가 된다. 그래서 한 칸을 바꾸면 모든 줄의 같은 칸이 함께 바뀐다.
문자열
문자열도 번호로 꺼내고 자를 수 있다. s = "hello"이면 s[0]은 "h", s[1:3]은 "el"이다. 하지만 s[0] = "H"처럼 칸을 바꿀 수는 없다. 바꾸려면 새 문자열을 만든다: "H" + s[1:].
| 코드 | 결과 |
|---|---|
"a b c".split() |
['a', 'b', 'c'] (빈칸 여러 개도 하나로 본다) |
"2021.05.02".split(".") |
['2021', '05', '02'] |
"-".join(["a", "b", "c"]) |
'a-b-c' |
"banana".replace("an", "_") |
'b__a' |
"banana".find("na"), "banana".count("a") |
2, 3 |
"abc123".isdigit(), "123".isdigit() |
False, True |
"Hi".lower(), "Hi".upper() |
'hi', 'HI' |
ord("a"), chr(98) |
97, 'b' |
"na" in "banana" |
True (이어진 부분이 있는지) |
글자를 여러 번 붙여 긴 문자열을 만들 때는 조각을 리스트에 모았다가 마지막에 "".join(조각들)로 한 번에 붙인다. 문자열은 고칠 수 없어서 붙일 때마다 새로 만들어지기 때문이다.
[[0]*m]*n에서 줄이 함께 바뀌는 것, 컴프리헨션과 반복문의 결과가 같다는 것을 실행해 확인했다. 원소 10만 개에서 pop(0)이 pop()보다 몇십 배 이상 느리다는 것도 실험으로 확인했다 — 03_list-string_verify.py활용
- 거의 모든 문제의 입력이 리스트나 문자열로 온다.
- 자주 하는 실수:
- 번호를 1부터 세는 문제(“i번째부터 j번째까지”)를 0부터 세는 파이썬으로 옮길 때 하나씩 어긋난다. 1부터 센 i~j번째는
a[i - 1:j]다. - 반복문 안에서
a.pop(0)이나x in a를 쓰면 전체가 \(O(n^2)\)이 된다. - 순회하는 리스트를 도는 중에 지우면 원소를 건너뛴다. 새 리스트를 만든다.
- 번호를 1부터 세는 문제(“i번째부터 j번째까지”)를 0부터 세는 파이썬으로 옮길 때 하나씩 어긋난다. 1부터 센 i~j번째는
연결
- 선수: 파이썬 기본 문법
- 다음 개념: 딕셔너리와 집합(빠른 찾기), 문자열을 규칙대로 자르는 방법은 문자열 파싱 문서에서 다룬다
append가 평균 \(O(1)\)인 까닭은 꽉 차면 칸 수를 일정한 비율로 늘리기 때문이다. 그러면 복사 횟수의 합이 등비급수가 되어 넣은 개수의 상수배를 넘지 않는다.a[i:j]는 j − i칸이다(0 ≤ i ≤ j ≤ len(a)일 때). 양 끝을 모두 넣는 “i번째부터 j번째까지”는 j − i + 1개로, 수열과 합의 기호의 항 개수 공식 n − m + 1과 같은 셈이다.- 컴프리헨션
[x * x for x in a if 조건]은 집합의 조건제시법 \(\{x^2 : x \in A,\ \text{조건}\}\)(\(\in\)은 “~에 속한다”)과 모양이 같다. 다른 점은 리스트가 순서와 중복을 남긴다는 것이다. 중괄호로 쓴{x * x for x in a if 조건}은 결과가 집합이라 중복이 사라진다. - 연습: 평균 구하기, 숫자 문자열과 영단어
확인 문제
C1 다음 코드가 끝난 뒤 a와 b는?
a = [3, 1, 4, 1, 5]
a.append(9)
a.pop(0)
b = a[1:3]
b[0] = 7
답: a = [1, 4, 1, 5, 9], b = [7, 1]. append로 9가 끝에 붙고, pop(0)으로 맨 앞의 3이 빠진다. a[1:3]은 1, 2번 칸 [4, 1]의 복사본이라, b[0]을 바꿔도 a는 그대로다.
흔한 오답: b = [4, 1, 5]처럼 3번 칸까지 넣는 것. 슬라이스의 끝 번호는 들어가지 않는다.
C2 grid = [[0] * 3] * 2 다음에 grid[0][0] = 1을 하면 grid는 [[1, 0, 0], [1, 0, 0]]이 된다. 왜 두 줄이 다 바뀌는가?
답: 바깥의 * 2는 줄을 새로 만드는 게 아니라, 같은 줄 하나를 두 번 가리킨다. 그래서 grid[0]과 grid[1]이 같은 줄이다. 줄마다 따로 만들려면 [[0] * 3 for _ in range(2)]처럼 반복해서 만든다.
C3 result = [s.upper() for s in words if len(s) >= 3]을 컴프리헨션 없이 반복문으로 바꿔 써라.
답:
result = []
for s in words:
if len(s) >= 3:
result.append(s.upper())
대괄호 맨 앞의 식이 append할 값, for 부분이 반복, if 부분이 거르는 조건이 된다.
-
Python Wiki, “TimeComplexity” (wiki.python.org/moin/TimeComplexity)의 list 항목. 문법과 메서드는 Python 3 공식 튜토리얼 3.1.2 “Text”, 3.1.3 “Lists”, 5.1 “More on Lists”와 표준 라이브러리 문서의 “Built-in Types” 중 Sequence Types, Text Sequence Type (str). ↩