코딩 공부를 계속 하고 있는데, 공부하는 내용을 블로그에 정리해보려 합니다. 그 첫 걸음으로, 오늘은 제가 직접 보기위해 파이썬 문법 정리하는 글을 올리고자 합니다. 너무 길어지면 추가로 게시글을 올릴 수도 있고, 내용이 추가되거나 수정될 수 있습니다.
주로 실질적으로 코딩에서 사용되는 문법 위주로 정리를 하려 하니, 두서가 없을 수 있습니다. 혹시 틀린 부분이 있거나 알려주고 싶은 꿀팁이 있으시면 댓글로 알려주세요.
최근엔 나동빈 님의 '이것이 취업을 위한 코딩 테스트다 with 파이썬' 이라는 책으로 공부를 하고 있는데, 앞으로의 코딩 공부 게시글은 주로 이 책을 참고하여 작성하게 될 것 같습니다.
파이썬에서는 변수에 값을 넣어주면, 자동으로 자료형이 결정됩니다. C 언어에서 처럼 변수의 자료형을 처음에 선정해주지 않아도 됩니다.
최단 경로 문제에서, 도달할 수 없는 노드에 대하여 최단거리를 무한(INF)로 설정하곤 합니다. 경로 값이 10억 미만의 범위로 항상 존재할 때, 무한(INF)을 표현할 때 10억을 이용할 수 있습니다.
a = 1e9 print(a)
1000000000.0
round( )합수의 첫 번째 인자는 실수형 데이터이고, 두 번째 인자는 '반올림하고자 하는 위치 -1'입니다. round(0.346, 2) 라고 작성하면 결과는 0.35가 됩니다. 두 번째 인자 없이 round( ) 함수를 호출하면 결과는 소수점 첫째 자리에서 반올림됩니다.
print(round(32.1461,3)) print(round(32.1461))
32.146 32
나눗셈 관련 다양한 연산자들이 있는데, 은근히 다양하게 응용할 수 있으나 헷갈리는 경우가 많습니다. 먼저 가장 기본적인 나누기 연산자(/)는 파이썬에서 결과를 실수형으로 반환합니다. 나머지 연산자(%)는 나눗셈의 결과에서 몫 이외의 나머지 값을 반환하고, 코딩 테스트에서 자주 사용됩니다. 나눈 결과에서 몫 값을 얻으려면 몫 연산자(//)를 사용합니다.
추가로 거듭제곱 연산자(**)를 사용하면 x**y의 결과는 x의 y제곱 값과 같은 결과를 냅니다.
a = 5 b = 3 print(a/b) print(a//b) print(a%b) print(a**b)
1.6666666666666667 1 2 125
리스트는 여러 개의 변수들을 연속적으로 담아 처리할 수 있습니다. 파이썬의 리스트 자료형은 내부적으로 배열(Array)를 채택하고, 연결 리스트 자료구조 기능을 포함하고 있어서 append( ), remove( ) 등의 메소드를 지원합니다.
비어 있는 리스트를 선언하고자 할 때는 list( ) 혹은 간단히 대괄호( [ ] )를 이용하면 됩니다. 대괄호( [ ] ) 안에 원소를 넣어 초기화 할 수 있고, 쉼표(,)로 원소를 구분합니다.
리스트를 지칭하는 변수의 뒤에서 원하는 원소의 인덱스(index)를 대괄호 속에 이어 붙여, 원하는 원소의 값에 접근할 수 있습니다. 리스트의 맨 첫 번째 원소는 인덱스가 0번 째 입니다. 예를 들어, a[2]의 경우, a라는 리스트의 세 번째 원소를 지칭합니다.
a = [1, 2, 3, 4, 5] print(a) print(a[3]) b = list() c = [] print(b) print(c)
[1, 2, 3, 4, 5] 4 [] []
코딩 문제를 풀 때, 크기가 N인 1차원 리스트를 초기화 할 때, 다음 방식으로 편리하게 진행할 수 있다.
n = 5 a = [0] * n print(a)
[0, 0, 0, 0, 0]
인덱스 값을 이용하여 해당 자리의 원소 값에 접근하는 것을 인덱싱이라고 한다. 인덱스는 양의 정수와 음의 정수를 전부 사용할 수 있다. 인덱싱을 할 때 양의 정수는, '리스트의 크기-1' 까지 사용할 수 있고, 음의 정수를 넣으면 원소를 거꾸로 탐색하게 된다. 예를 들어 인덱스에 -1을 넣으면 가장 마지막 원소가 출력된다. 리스트의 크기를 모르거나 크기가 변할 때, 마지막 원소나 뒤에서 n 번째 원소 등에 접근할 때 유용하다.
인덱싱을 하여 해당 인덱스의 위치에 접근한 후, 리스트의 값을 변경할 수 있다. 이와 대조되어 튜플(tuple)이라는 자료형은 인덱싱을 통한 원소 값 변경이 불가능하다는 특징이 있다.
a = [3, 5, 7, 9, 11] print(a[-1]) print(a[-2]) a[3] = 1 print(a)
11 9 [3, 5, 7, 1, 11]
a = [1, 3, 5] print(a[3]) # 원소가 0, 1, 2번 째 인덱스위치밖에 없는데, # 3 이상의 양의 정수를 입력 시 오류가 발생한다.
IndexError: list index out of range
리스트에서 연속적인 위치를 갖는 원소들을 가져와야 할 때는 슬라이싱(slicing)을 이용할 수 있다. 이때는 대괄호 안에 콜론(:)을 넣어서 시작 인덱스와 끝 인덱스를 설정할 수 있다. 콜론의 왼쪽에 값을 입력 시, 해당 인덱스의 원소부터 시작된다. 값을 입력하지 않으면, 0번 째 원소부터 시작되다. 콜론의 오른쪽에 값을 입력 시, 해당 인덱스로 지칭되는 원소의 직전까지 범위를 잡게 된다.
예를 들어, a = [1, 2, 3, 4, 5] 라는 리스트에 대해 a[1:3]는 인덱스가 1인 원소부터 3인 원소 직전까지인 인덱스가 2인 원소까지를 범위로 잡겠다는 의미이다. 추가로, 슬라이싱 할 때 두 번째 입력값에는 리스트의 크기 이상의 양의 정수를 넣으면 범위를 그냥 끝까지로 잡게 된다.
슬라이싱 시 콜론을 두 개를 써서, 세 번째 입력값을 설정할 수 있다. 이는 범위 내에서 원소를 뽑아낼 간격을 의미하며, 설정하지 않으면 기본적으로 1을 의미해서 범위 내의 원소들을 연속적으로 가져온다.
a = [1, 3, 5, 7, 9, 11, 13] print(a) print(a[:]) print(a[:3]) print(a[3:]) print(a[3:6]) print(a[:10]) print(a[-4:-1]) print(a[::2])
[1, 3, 5, 7, 9, 11, 13] # print(a) [1, 3, 5, 7, 9, 11, 13] # print(a[:]) [1, 3, 5] # print(a[:3]) [7, 9, 11, 13] # print(a[3:]) [7, 9, 11] # print(a[3:6]) [1, 3, 5, 7, 9, 11, 13] # print(a[:10]) [7, 9, 11] # print(a[-4:-1]) [1, 5, 9, 13] # print(a[::2])
리스트 컴프리헨션은 대괄호 내에서 조건문과 반복문을 사용하여 리스트를 초기화하는 방법이다. 원소가 될 계산식으로 대괄호 안을 시작하고, for 구절을 적어준다. 원하는 변수로 조건을 주고 싶으면 이후 if 문을 추가하여 설정해줄 수 있다. 일반적인 for문이나 if문으로 작성하는 것보다 간결하게 코드를 작성할 수 있다.
# 0부터 19까지의 수 중에서 홀수만 포함하는 리스트 a = [i for i in range(20) if i % 2 == 1] print(a) # 1부터 9까지의 수의 제곱 값을 포함하는 리스트. b = [ i * i for i in range(1, 10)] # range(k)는 0부터 k-1까지, range(k,l)은 k부터 l-1까지의 자연수 리스트이다. print(b)
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19] [1, 4, 9, 16, 25, 36, 49, 64, 81]
리스트 컴프리헨션은 2차원 리스트를 초기화 할 때 매우 효과적이다. N x M 크기의 이차원 리스트를 초기화 할 때 리스트 컴프리 헨션을 사용하지 않고 아래 두 번째 방법으로 리스트에 정수를 곱해 진행한다면, 내부적인 문제가 발생한다. 2차원 리스트에서 인덱싱을 이용해 값을 변경 시 내부적으로 동일한 주소로 인식하여, 여러 개의 값이 바뀌는 문제가 생길 수 있다. 따라서, 정해진 크기의 2차원 리스트의 초기화에는 리스트 컴프리헨션을 기억하여 사용하자.
#리스트 컴프리헨션 사용 n = 3 m = 4 a = [ [0] * m for _ in range(n) ] print(a) a[1][1] = 5 print(a) print() # 빈 줄 출력 #잘못된 방법으로 2차원 리스트 초기화 b = [ [0] * m] * n print(b) b[1][1] = 5 print(b)
[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]] [[0, 0, 0, 0], [0, 5, 0, 0], [0, 0, 0, 0]] [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]] [[0, 5, 0, 0], [0, 5, 0, 0], [0, 5, 0, 0]] # 동시에 여러 원소의 값이 변경되는 문제 발생
다음은 리스트 관련 주요 메서드이다.
메서드명 | 사용법 | 설명 | 시간 복잡도 |
---|---|---|---|
append( ) | 변수명.append( ) | 리스트의 마지막에 원소 하나를 삽입 | O(1) |
sort( ) | 변수명.sort( ) | 기본 정렬 기능으로, 오름차순으로 정렬 | O(NlogN) |
sort( ) | 변수명.sort(reverse=True) | 내림차순으로 정렬 | O(NlogN) |
reverse( ) | 변수명.reverse( ) | 리스트의 원소의 순서를 모두 뒤집어 놓음 | O(N) |
insert( ) | 변수명.insert(삽입할 위치 인덱스, 삽입할 값) | 특정한 인덱스 위치에 원소를 삽입할 때 사용 | O(N) |
count( ) | 변수명.(특정 값) | 리스트에서 특정한값을 가지는 데이터의 개수를 셀 때 사용 | O(N) |
remove( ) | 변수명.(특정 값) | 특정한 값을 갖는 원소를 제거하는데, 값을 가진 원소가 여러 개면 가장 앞의 인덱스의 원소만 제거 | O(N) |
이 중에서 insert(), append(), 그리고 remove() 함수를 더 살펴보자. 원소의 개수가 N인 리스트에 대해 insert() 메소드를 사용하면 시간 복잡도가 O(N)이다. append() 메소드가 O(1)인 것에 비해 시간이 더 걸린다는 것인데, 코드를 작성할 때, insert() 함수를 많이 사용하면 제한 시간이 있는 경우 문제가 생길 수 있다. remove() 함수도 마찬가지로 사용에 주의하여야 한다. remove() 함수는 특정 원소가 여러개 존재할 경우 앞 쪽의 한 개만 지워지게 되는데, 원하는 원소를 전부 지우고 싶을 때는 다음의 방법을 이용하면 가능하다.
a = [1, 2, 3, 4, 5, 5, 5] remove_set = {3, 5} result = [ i for i in a if i not in remove_set ] print(result)
[1, 2, 4]
파이썬이라고는 스타크래프트 밖에 몰랐던 제가 이 글을 읽고 파이썬에 눈을 떴어요! 감사합니다!!