파이썬에서 리스트 변수는 리스트 객체를 직접 저장하고 있지 않다. 리스트 변수에 저장되는 것은 리스트 객체의 참조값(reference)이다. 참조값이란 메모리에서 리스트 객체의 주소값이다.
평상시에는 이런 사소한 것에 신경을 쓰지 않아도 괜찮지만 리스트를 복사할 때는 주의가 필요하다.
예를 들어 한 프로그래머가 리스트를 복사하기 위해 다음과 같은 코드를 작성했다고 하자.
scores = [10,20,30,40,50]
test = scores
결론적으로 복사가 되지 않는다. test는 scores가 가리키는 리스트의 참조값을 얻게 된다. 즉 test와 scores는 '동일한 객체 리스트'를 '가리킬'뿐이다. 이를 얕은 복사(shallow copy)라고 한다. 만일 아래의 코드와 같이 test에 인덱스를 이용하여 값을 변경시킨다고 가정하자.
그리고 이를 scores을 통해 확인하자.
scores = [ 10, 20, 30, 40, 50 ]
test = scores
test[2] = 99
for element in scores:
print(element, end=" ")
출력 결과:
10 20 99 40 50
그러면 결론적으로 같은 참조값을 가지기에 scores을 출력할 때 요소가 변경됨을 확인할 수 있다.
아예 새로운 객체를 만드는 복사를 깊은 복사(deep copy) 라고한다.
scores = [ 10, 20, 30, 40, 50 ]
values = list(scores)
values [2]=99
print(scores)
print(values)
출력 결과:
[10, 20, 30, 40, 50]
[10, 20, 99, 40, 50]
# copy 모듈의 deepcopy
from copy import deepcopy
scores = [ 10, 20, 30, 40, 150 ]
values1 = deepcopy(scores)
# copy 모듈의 copy
from copy import copy
values2 = copy(scores)
print(values1)
print(values2)
출력 결과:
[ 10, 20, 30, 40, 150 ]
[ 10, 20, 30, 40, 150 ]
scores = [ 10, 20, 30, 40, 50 ]
values = scores[:]
values[2]=99
print(scores)
print(values)
출력 결과:
[10, 20, 30, 40, 50]
[10, 20, 99, 40, 50]
앞에서 비교 연산자(==)를 통해 리스트 요소들의 값을 비교하였다. is 키워드를 통해 참조값(주소값)을 비교할 수 있다. 이에 얕은 복사를 하면 True를 깊은 복사를 하면 False를 출력한다.
함수로 인자를 전달하는 방식에는 2가지가 있다.
def func1(x) :
print( "x=", x," id=",id(x))
x=42 # 새로운 객체 생성
print( "x=", x," id=",id(x))
y = 10
print( "y=",y," id=",id(y))
func1(y)
print( "y=",y," id=",id(y))
출력 결과:
y= 10 id= 1640249248
x= 10 id= 1640249248
X= 42 id= 1640249760
y= 10 id= 1640249248
def func2(list):
list[0] = 99
values = [0, 1, 1, 2, 3, 5, 8]
print(values)
func2(values)
print(values)
출력 결과:
[0, 1, 1, 2, 3, 5, 8]
[99, 1, 1, 2, 3, 5, 8]
s = [x**2 for x in range(10)]
print(s)
출력 결과:
[ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81 ]
리스트 함축은 문법은 다음과 같다.
new_list = []
for i in old list:
if filter(i):
new_list.append(i)
위의 그림을 다르게 표현하면 위의 코드와 같다. 굳이 리스트 함축을 사용하는 이유는 아주 간결하게 리스트를 생성할 수 있는 장점 때문이다.
만일 list의 요소들에 *2를 하여 새로운 리스트를 만드는 코드는 다음과 같다.
list = [3,4,5]
result = [x*2 for x in list]
print(result)
출력 결과:
[6,8,10]
리스트 함축에는 if를 사용하여 조건을 추가할 수 있다. 예를 들어 0에서 9까지의 정수 중 짝수의 집합을 리스트 함축으로 표현하면 다음과 같다.
list = [x for x in range(10) if x%2 == 0]
print(list)
출력 결과:
[0,2,4,6,8]
리스트 함축은 정수에만 제한되지 않고 모든 자료형에 대해 적용이 가능하다.
다음은 문장을 공백으로 구분지어 단어의 리스트를 만들고 그 단어의 길이와 첫 글자를 출력하는 프로그램이다.
word_list = "Lorem ipsum dolor sit amet consectetur adipiscing .".split()
count_word = [len(x) for x in word_list]
first_letter = [word[0] for word in word_list]
print(count_word)
print(first_letter)
출력 결과:
[5, 5, 5, 3, 4, 11, 10, 1]
['L', 'i', 'd', 's', 'a', 'c', 'a', '.']
리스트 함축은 2개의 집합의 상호곱 형태로도 표현할 수 있다.
예를 들어서 색상의 집합과 자동차의 집합을 상호 곱하여 새로운 리스트를 생성할 수 있다. 아래 코드는 colors 리스트의 원소와 cars 리스트의 원소가 하나씩 짝지어져서 튜플이 되고 이 튜플이 모여서 리스트가 된다.
상호 곱의 경우 오른쪽에서 왼쪽으로 읽어진다.
colors = [ "white", "silver", "black" ]
cars = [ "bmw5", "sonata", "malibu", "sm6" ]
colored_cars = [ (x, y) for x in colors for y in cars ]
## for y in cars 를 읽고 for x in colors르 읽는다.
print(colored_cars)
# 출력식에 소괄호()와 ,를 넣었기에 출력 값에서도 그것들이 포함된다.
# 제거를 한다면 그대로 제거한 상태로 출력된다.
# 출력 결과에서 확인 할 수 있드시 x="white" 상태에서 cars를 순회하고 있다.
# 상호 곱의 형태에서는 오른쪽에서 왼쪽으로 읽어드린다.
출력 결과:
[('white', 'bmw5'), ('white', 'sonata'), ('white', 'malibu'),
('white', 'sm6'), ('silver', 'bmw5'), ('silver', 'sonata'),
('silver', 'malibu'), ('silver', 'sm6'), ('black', 'bmw5'),
('black', 'sonata'), ('black', 'malibu'), ('black', 'sm6')]
min()와 max()함수를 통해 숫자들이 들어있는 리스트에서 최대, 최소값을 구할 수 있다. 하지만 이 함수들의 알고리즘을 알고 있어야 리스트에 숫자가 아닌 다른 데이터가 들어있어도 대응을 할 수 있다. 이는 실제 프로그래밍에서도 상당히 많이 등장 하기에 정확히 알고 있어야 한다.
만일 우리가 휴대폰을 최저가로 구입하고, 여러 인터넷 사이트들의 휴대폰 가격이 1차원 리스트[ ]에 저장되어 있다고 가정하자. 리스트 요소에서 최소값을 구하는 알고리즘은 다음과 같다.
최소값을 구할 때는 먼저 리스트의 첫 번째 요소를 최소값으로 가정한다. 그리고 이 요소를 두 번째 요소부터 마지막 요소까지 순차적으로 비교한다. 만약 어떤 요소가 현재의 최소값 보다 작다면 그 요소를 최소값으로 지정하고 다음으로 넘어간다. 이런 식으로 모든 요소의 검사가 종료되면 최소값을 찾을 수 있다.
# 첫 번째 요소를 최소값, minimum이라고 가정한다.
minimum = s[0]
for i in range(1, 리스트의 크기) :
if s[i] < minimum :
minimum = s[i]
# 반복이 종료되면 minimum에 최소값이 저장된다.
다시 예로 돌아가서 휴대폰 가격 리스트중 최소값을 알고자 한다면 다음과 같이 프로그램을 작성할 수 있다.
lowPrice = prices[0]
for x in range(1, len(prices)):
if prices[i] < lowPrice :
lowPrice = prices[i]
print("가장 싼 가격은 ", lowPrice)
탐색의 대상이 되는 데이터는 보통 리스트에 저장되어 있다고 가정하자. 탐색이란 리스트에서 특정한 값을 찾는 걸 말한다. 리스트의 탐색에서 index()메서드를 사용할 수 있다. 이는 리스트에서 조건이 부합하는 데이터의 인덱스를 반환한다.
list1 = [ "white", "silver", "blue", "red", "black" ]
print(list1.index ("red"))
출력 결과:
3
경우에 따라서는 프로그래머가 탐색을 구현해야하는 경우가 있으므로 기본적인 탐색의 알고리즘 정도는 알고 있어야 한다. 가장 간단하고 직접적인 알고리즘으로 순차 탐색(sequential search)가 있다.
순차 탐색이란 리스트의 요소를 순서대로 하나씩 꺼내서 탐색키와 비교하여 원하는 값을 찾아가는 방법이다. 순차 탐색은 일치하는 찾을 때, 혹은 마지막 요소에 도달할 때 까지 진행된다.
순차 탐색을 구현하고 테스트하는 코드는 다음과 같다.
def linear_Search(aList, key):
for i in range(len(aList)): # 리스트의 길이만큼 반복한다.
if key == alist[i]: # 키가 발견되면 i를 반환하고 종료한다.
return i
return -1 # 키가 발견되지 않고 반복이 종료되었으면 -1을 반환한다.
numbers = [ 10, 20, 30, 40, 50, 60, 70, 80, 90 ]
position = linear_Search(numbers, 80)
if position != -1:
print("탐색 성공 위치 = “, position)
else:
print("탐색 실패 ")
출력 결과:
탐색 성공 위치 = 7
위의 코드에서는 리스트의 요소중에서 조건에 부합하는 요소 하나만을 찾았다. 만약 해당하는 요소들을 모두 찾으려면 공백 리스트를 만들고 부합하는 요소들을 차례로 저장하면 된다.
numbers = [ 10, 20, 30, 40, 50, 60, 70, 80, 90 ]
result = []
for value in numbers:
if value > 50:
result.append(value))
print(result)
출력 결과:
[60, 70, 80, 90]
파일에서 데이터를 읽어서 리스트에 저장하는 작업은 아주 많이 등장한다.
다음은 폴더 안의 파일을 읽어들이는 문장이다.
data = []
# 파일의 경로를 지정하고 읽기 모드로 문자셋 UTF-8로 설정해서 해당 파일을 열고
# 메모리에 로딩된 파일의 주소를 반환한다
fp = open("C:\\Temp\\test.txt",mode="r",encoding="UTF-8")
# print(type(fp))
# readlines() 메서드는 파일의 저장된 내용을 한번에 다 읽는다.
for line in fp.readlines():
# strip()메서드는 원래 문자열의 양쪽 공백을 제거하는 역할을 하지만
# 파일을 읽어들일때는 엔터키를 제거를 해준다.
data.append(line.strip())
# 프로그램에서 리소스를 다 사용했으면 반드시 close()메서드를 호출해야한다.
print("파일에서 읽은 내용")
print(data)
fp.close()
위의 open()메서드의 mode가 w일 경우 파일의 내용을 덮을 수 있다.
# 파일에 내용을 쓰는 방법
# 파일의 모드가 w인 경우 에는 기존파일의 내용을 덮어써버린다.
fp = open("C:\\Temp\\test.txt",mode="w",encoding="UTF-8")
fp.write("우리는 파이썬을 공부합니다.")
fp.write("저희도 파이썬을 공부합니다.")
print("쓰기 완료")
fp.close()
이에 mode=a으로 한다면 기존 파일에서 내용을 추가로 덧붙일 수 있다.
# 기존 파일의 내용에 추가를 해준다.
fp = open("C:\\Temp\\test.txt",mode="a",encoding="UTF-8")
fp.write("11.우리는 파이썬을 공부합니다.")
fp.write("22.저희도 파이썬을 공부합니다.")
print("추가 완료")
fp.close()
정렬이란 리스트 안에 저장된 값을 특정한 순서에 따라서 나열하는 것이다. 리스트에서 정렬은 sort() 메소드를 이용하여 쉽게 수행할 수 있다. 매개변수 reverse를 이용해 순서 방향을 지정할 수 있다.
a = [ 3, 2, 1, 5, 4 ]
a.sort()
print(a)
출력 결과:
[1, 2, 3, 4, 5]
리스트를 정렬하는 경우도 프로그래머가 정렬을 구현해야할 경우가 있다. 이에 가장 이해하기 쉬운 정렬 방법으로 '선택 정렬(selection sort)'이 있다.(선택 정렬 말고도 버블 정렬, 삽입정렬, 병합정렬, 퀵정렬, 힙정렬이 있다.)
두 개의 리스트가 있다고 가정하자. 왼쪽에 정렬된 리스트가, 오른쪽에는 정렬되지 않은 리스트이다. 정렬이 되지 않은 리스트가 오른쪽 리스트에 있다. 선택 정렬은 오른쪽 리스트를 탐색하여 가장 작은 수를 찾아 왼쪽 리스트로 옮긴다. 다음에 다시 오른쪽 리스트에서 가장 작은 수를 찾아 왼쪽을 옮긴다. 이 과정을 오른쪽 리스트가 공백이 될 때까지 반복한다.
위의 표를 구현하기 위해서, 또 메모리를 절약하기 위하여 입력 리스트 외에 추가적인 공간을 사용하지 않는 선택 정렬 알고리즘을 표현할 수 있다. 이렇게 추가적인 메모리를 요구하지 않는 정렬 방법을 제자리 정렬(in-place sort)라고 한다.
아래의 그림과 소스 코드는 하나의 값을 변수에 넣어두고 리스트를 순회하면서 차례로 비교한다. 이 과정에서 변수의 값은 바뀔 수도 있고 그렇지 않을 수 있다. 리스트에서 최소값이 발견되면 0번째 인덱스와 최소값의 인덱스를 바꾼다. 이런 작업을 끝까지 하면 비로소 전체 리스트가 오름차순으로 변경된다.
def selectionSort(aList):
i, least, leastValue = 0
for i in range(len(aList)): # 리스트의 모든 요소에 대하여 반복
least = i # i요소를 최소값이라고 가정
leastValue = aList[i]
for k in range(i+1, len(aList )):
if aList[k] < leastValue: #k번째 요소가 현재의 최소값보다 작으면..
least = k # k번째 요소를 최소값으로 한다.
leastValue = aList[k]
tmp = aList[i] #i번째 요소와 최소값을 교환한다.
aList[i] = aList[least]
aList[least] = tmp
list1 = [ 7, 9, 5, 1, 8 ]
selectionSort(list1)
print(list1)
버블 정렬은 인접한 요소 2개를 비교하여 조건에 맞게 정렬시키는 알고리즘이다.
버블 정렬은 첫 번째 요소와 두 번째 요소를 비교 후 정렬 시키고 다음으로 두 번째 요소와 세 번째 요소를 비교하여 정렬시킨다. 이런 식으로 (마지막-1) 번째 요소와 마지막 요소까지 비교 후 정렬한다.
중첩 반복문을 통해 버블 정렬을 구현하는데 1회전이 끝나면 조건에 부합하는 가장 큰 요소가 맨 뒤에 위치한다. 그리고 2회전이 시작 될 때는 마지막의 요소를 제외나고 나머지 요소들 끼리 정렬이 이루어진다. 이런식으로 (요소 개수 -1) 회의 반복이 끝나면 비로소 정렬이 완료된다.
다음은 리스트를 오름차 순으로 정렬한 문장이다.
def bubble_sort(li):
num = 0
list_length = len(li)
for i in range(list_length-1):
for j in range(list_length-i-1):
if li[j] > li[j+1]:
li[j], li[j+1] = li[j+1], li[j]
num+=1
print(num,li)
list = [4,6,1,10,7,-7,-100,14,30,15] # len = 10
print("변경 전",list)
bubble_sort(list)
print("변경 후",list)
출력 결과:
변경 전 [4, 6, 1, 10, 7, -7, -100, 14, 30, 15]
1 [4, 1, 6, 7, -7, -100, 10, 14, 15, 30]
2 [1, 4, 6, -7, -100, 7, 10, 14, 15, 30]
3 [1, 4, -7, -100, 6, 7, 10, 14, 15, 30]
4 [1, -7, -100, 4, 6, 7, 10, 14, 15, 30]
5 [-7, -100, 1, 4, 6, 7, 10, 14, 15, 30]
6 [-100, -7, 1, 4, 6, 7, 10, 14, 15, 30]
7 [-100, -7, 1, 4, 6, 7, 10, 14, 15, 30]
8 [-100, -7, 1, 4, 6, 7, 10, 14, 15, 30]
9 [-100, -7, 1, 4, 6, 7, 10, 14, 15, 30]
변경 후 [-100, -7, 1, 4, 6, 7, 10, 14, 15, 30]
버블 정렬의 특징으로는 구현이 매우 간단하는 장점이 있지만 이동의 횟수가 많기 때문에 데이터가 많을 수록 속도가 느려진다는 단점이 있다.
2차원 테이블을 이용하여 많은 일들을 처리한다. 예를 들어서 아래와 같이 학생의 과목별 성적을 2차원을 표현할 수 있다.
파이썬은 C언어의 2차원 행렬과 유사하게 2차원 리스트를 만들 수 있다.
2차원 리스트 또한 객체이기에 함수로 call-by-reference 통해 전달될 수 있다. 따라서 당연하게도 함수 내에서 값을 바꾼다면 원본 리스트도 변경된다.
s = [ [ 1, 2, 3, 4, 5], [ 6, 7, 8, 9, 10 ], [11, 12, 13, 14, 15 ] ]
print(s)
출력 결과:
[ [1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15] ]
s 은 리스트 객체의 [0][0]자리의 참조값을 가진다. 또한 [0],[1],[2] 은 '행(row)'이며 각각은 다섯 개의 '열(cols)'을 가진다.
위의 2차원 리스트는 초기값이 미리 결정되어 있어서 정적으로 생성되었다. 실제로는 동적으로 2차원 리스트를 생성하는 경우가 더 많다. 리스트의 크기가 매우 큰 경우에도 동적으로 생성하여야 한다. 보통 for문과 리스트 함축을 이용해 생성한다.
# 동적으로 2차원 리스트를 생성한다.
## 첫 번째 방법: for문 반복
rows = 3
cols = 5
s = []
for row in range(rows):
s += [ [0]*cols ]
print("s =", s)
## 두 번째 방법: 리스트 함축
rows = 3
cols = 5
s = [ ([0] * cols) for row in range(rows) ] # 리스트 함축
print("s =", s)
출력 결과:
s = [ [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0] ]
s = [ [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0] ]
2차원 리스트의 요소에 접근하려면 인덱스를 두 개를 써야한다. 첫 번째 번호가 행을 의미하며 두 번째 번호가 열을 의미한다. 예를 들어서 2행 1열의 요소는 list[1][0](인덱스의 시작은 0)이다. 만일 인덱스의 모든 요소에 접근하여 출력을 한다면 아래와 같다.
s = [ [ 1, 2, 3, 4, 5 ], [ 6, 7, 8, 9, 10 ], [11, 12, 13, 14, 15] ]
# 행과 열의 개수를 구한다.
rows = len(s)
cols = len(s[0])
for r in range (rows):
for c in range(cols):
print(s[r][c], end=“\t")
print()
출력 결과:
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
리스트 안에 리스트를 주어서 2차원 리스트를 만드는 것도 가능하다.
a = ['a', 'b', 'c']
n = [1, 2, 3]
x = [a, n] #리스트 x 안에 리스트 a와 n이 들어 있다.
print(x)
출력 결과:
[['a', 'b', 'c'], [1, 2, 3]]
2차원 연산에서 가장 기본적인 연산은 행의 합계나 행의 합계이다. 만일 0번째의 행의 요소들의 합을 구하고자 한다면 다음과 같다.
s = [ [ 1, 2, 3, 4, 5 ], [ 6, 7, 8, 9, 10 ], [11, 12, 13, 14, 15] ]
cols = len(s[0])
sum = 0
for c in range(cols): # 0번째 행의 합을 계산한다.
sum = sum + s[0][c]
print(sum)
출력 결과:
15
2차원 리스트는 이미지를 처리할 때도 사용된다. 이미지도 본질적으로 각 화소(픽셀)의 값을 2차원적으로 나열한 것이기 때문이다. 만일 하나의 리스트의 요소의 인덱스를 [r][c]라고 한다면 주변의 인덱스는 다음과 같다.
다음의 프로그램은 10*10 크기의 2차원 리스트에 0과 1을 번갈아 가며 채워 출력한다.
table = []
# 2차원 리스트를 화면에 출력한다.
def printList(mylist):
for row in range(len(mylist)):
for col in range(len(mylist[0])):
print(mylist[row][col], end=" ")
print()
# 2차원 리스트를 체커보드 형태로 초기화한다.
def init(mylist):
for row in range(len(mylist)):
for col in range(len(mylist[0])):
if (row+col)%2 == 0: # (row+col)이 짝수이면 1을 저장
table[row][col] =1
if __name__ == "__main__":
for row in range(10): # 0으로 초기화된 2차원 리스트를 생성한다.
table += [ [0] * 10]
init(table)
printList(table)
출력 결과:
1 0 1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1 0 1
1 0 1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1 0 1
1 0 1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1 0 1
1 0 1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1 0 1
1 0 1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1 0 1