[Python] 코딩 테스트를 위한 파이썬 문법
이번엔 코딩 테스트를 위한 Python 문법에 대해서 알아보고자 합니다. 코딩 테스트를 위한 파이썬 문법을 알아 보는 이유는 제가 자료구조와 알고리즘이 약해 보완을 하고자 이것이 취업을 위한 코딩 테스트다 with python
이라는 책을 이용해 공부를 하고자 하는데 한 때 대학원 때 딥러닝 관련해서 Python 을 사용했지만 취업을 한 이후로는 제가 원하던 것과는 달리 레거니 솔루션을 맡게 되어 Java 만 하다보니 Python 문법들을 까먹어 기억을 되살리고자 합니다. 인간이 사용하는 자연어도 안쓰다보면 녹슬듯이 프로그래밍 언어도 마찬가지인 것 같습니다.
특히 이번에는 책에서 가이드해 주는 코딩테스트를 위한 Python 문법에 대해서만 다뤄 보도록 하며, 추후에 Python 문법과 Python 의 역사와 개념 등에 대해서 다룰 예정입니다. 그래서 이번 포스트는 저와 같이 이직을 하고자 하는데 코딩 테스트가 약하신 분들에게 도움이 되었으며 하고, 또한 다른 무엇보다도 추후에 제가 다시 보기 위해 작성하는 포스트입니다.
그럼 시작해 보도록 하겠습니다. 이번 포스트를 코딩 테스트를 위한 Python 문법을 모두 다루므로 내용이 매우 많습니다. 내용이 너무 많아 보기기 힘들다고 판단이 된다면 내용을 나누어 여러 포스트로 다루도록 하겠습니다.
개요
이번 Python 문법은 책을 참고하였으며, 일단 이전에 Python 을 다루어 보았지만 오랜 시간 다루지 않아 까먹은 저와 같은 사람을 위한 내용을 위주로 하였습니다. 또한 코딩 테스트에서와 실제 개발에서의 좋은 코드의 기준은 다를 수 있습니다. 그래서 이번 포스트에서 설명해 주는 Python 문법은 오로지 코딩 테스트에 맞춰진 것으로 생각을 하시면서 보시면 될 것 같습니다. 따라서 실무에서 사용되는 클래스 위주보다는 단순 함수 위주로 설명을 진행하고자 합니다.
자료형
파이썬의 자료형은 C/C++, Java 와 같은 다른 언어에서 사용되는 기본 자료형을 제공할 뿐만 아니라, 사전 자료형, 집합 자료형 등 강력한 기능을 제공하는 자료형을 기본으로 내장하고 있어 매우 편리합니다.
수 자료형
수 자료형(Number) 은 코딩 테스트에서 가장 기본적인 자료형이며, 프로그래밍을 해봤다면 자연스럽게 사용해봤을 것입니다. 대부분의 프로그램에서는 일반적으로 정수와 실수가 많이 사용되고, 그 중에서도 정수를 기본으로 사용합니다. 실제로 코딩 테스트에서도 대부분의 경우 정수형을 다루는 문제가 출제되며, 실수형을 다루는 문제의 출제 빈도는 낮습니다.
정수형
정수형(Integer) 는 정수를 다루는 자료형이며 정수형에는 양의 정수, 음의 정수, 0이 있습니다. 코딩 테스트에서 출제되는 알고리즘 문제는 대부분 입력과 출력이 정수형입니다.
a = 1000
print(a)
a = -7
print(a)
a = 0
print(a)
콘솔 출력
1000
-7
0
실수형
실수형(Real Number)은 소수점 아래의 데이터를 포함하는 수 자료형으로 Python 에서는 변수에 소수점을 붙인 수를 대입하면 실수형 변수로 처리됩니다. 소수부가 0이거나, 정수부가 0인 소수는 0을 생략하고 작성할 수 있습니다.
#양의 실수
a = 157.93
print(a)
#음의 실수
a = -1837.2
print(a)
# 소수부가 0일 때 0을 생략
a = 5.
print(a)
#정수부가 0일 때 0을 생략
a = -.7
print(a)
콘솔 출력
157.93
-1837.2
5.0
-0.7
실수형 데이터를 표현하는 방식으로 파이썬에서는 e
나 E
를 이용한 지수 표현 방식을 이용할 수 있습니다. e 다음에 오는 수는 10의 지수부를 의미합니다. 예를 들어 1e9 라고 입력하게 되며, 10의 9제곱이 됩니다.
특히 지수 표현 방식은 코딩 테스트에서 많이 사용됩니다. 예를 들어 최단 경로 문제에서는 도달할 수 없는 노드에 대하여 최단 거리를 무한(INF)
로 설정하곤 합니다. 최단 경로로 가능한 최댓값이 10억 미만이라면 무한을 표현할 때 10억을 이용할 수 있습니다. 이 때 일일이 10억을 특정 변수에 대입하는 일은 번거로워 지수 표현 방식인 1e9로 표현할 수 있습니다. 또한 큰 수를 표현할 때, 0의 개수가 많아지게 되면 자릿수가 헷갈리는 경우가 많기 때문에 10억을 코드에 입력하는 것보다는 1e9로 표현하는 것이 더 실수할 확률이 적다는 장점도 있습니다.
#10억의 지수 표현 방식
a = 1e9
print(a)
#752.5
a = 75.25e1
print(a)
#3.954
a = 3954e-3
print(a)
콘솔 출력
1000000000.0
752.5
3.954
프로그래밍 언어에서 실수형에 대해 정확도 이슈가 존재합니다. 이와 관련해서 보통 컴퓨터 시스템은 수 데이터를 처리할 때 2진수를 이용하며, 실수를 처리할 때 부동 소수점(Floating-point) 방식을 이용합니다. 오늘날 가장 널리 쓰이는 IEEE754 표준에서는 실수형을 저장하기 위해 4바이트, 혹은 8바이트라는 고정된 크기의 메모리를 할당하며, 이러한 이유로 인해 현대 컴퓨터 시스템은 대체로 실수 정보를 표현하는 정확도에 한계를 가집니다.
예를 들어 10진수 체계에서는 0.3과 0.6을 더한 값이 0.9로 정확히 떨어지지만, 2진수에서는 0.9를 정확히 표현할 수 있는 방법이 없습니다. 물론 최대한 0.9와 가깝게 표현하지만 표현한 값이 정확히 0.9가 아닌 미세한 오차가 발생합니다. 일반적으로 코딩 테스트 문제를 풀기 위해서 컴퓨터의 내부 동작 방식까지 알 필요는 없으나(알면 더 좋습니다) 컴퓨터가 실수를 정확히 표현하지 못한다는 사실은 기억합시다. 이와 관련해 다음 예시를 통해 좀 더 자세히 알아보도록 하겠습니다.
a = 0.3 + 0.6
print(a)
if a == 0.9:
print(True)
else:
print(False)
콘솔 출력
0.8999999999999999
False
따라서 소수점 값을 비교하는 작업이 필요한 문제라면 실수 값을 비교하지 못해서 원하는 결과를 얻지 못할 수 있습니다. 이럴 때는 round() 함수를 이용하여 해결할 수 있습니다.
round() 함수를 호출할 때는 인자를 넣는데 첫 번째 인자는 실수형 데이터이고, 두 번째 인자는 반올림하고자 하는 위치 -1 입니다. 흔히 코딩 테스트 문제에서는 실수형 데이터를 비교할 때 소수점 다섯 번째 자리에서 반올림한 결과가 같으면 정답으로 인정하는 식으로 처리합니다. 그렇다면 좀 전에 봤던 예시에 round() 함수를 적용해 보도록 하겠습니다.
리스트 자료형
리스트는 여러 개의 데이터를 연속적으로 담아 처리하기 위해 사용할 수 있습니다. Python 의 리스트 자료형은 C 나 Java 와 같은 프로그래밍 언어의 배열 기능을 포함하고 있으며, 내부적으로 연결 리스트 자료구조를 채택하고 있어서 append(), remove() 등의 메서드를 지원합니다. Python 의 리스트는 C++ 의 STL, vector 와 유사하며, 리스트 대신에 배열 혹은 테이블이라고 부르기도 합니다.
리스트 만들기
리스트는 대괄호([]) 안에 원소를 넣어 초기화하며, 쉼표(,)로 원소를 구분합니다. 리스트의 원소에 접근할 때는 인덱스 값을 대괄호 안에 넣습니다. 이 때 인덱스는 0부터 시작합니다. 그리고 비어 있는 리스트를 선언하고자 할 때는 list() 혹은 간단히 대괄호를 이용할 수 있습니다.
1부터 9까지의 모든 정수 데이터를 담는 리스트를 만든 다음 특정한 인덱스의 원소에 접근하여 출력하는 예제를 확인해 보도록 하겠습니다.
a = [1, 2, 3, 4, 5, 6, 7, 8, 9]
print(a)
# 인덱스 4, 즉 다섯 번째 원소에 접근
print(a[4])
# 빈 리스트 선언 방법 1
a = list()
print(a)
# 빈 리스트 선언 방법 2
a = []
print(a)
콘솔 출력
[1, 2, 3, 4, 5, 6, 7, 8, 9]
5
[]
[]
코딩 테스트 문제에서는 주로 크기가 N인 1차원 리스트를 초기화해야 하는데 다음 방식으로 초기화 하면 편리합니다. 다음은 크기가 N이고, 모든 값이 0인 1차원 리스트를 초기화하는 소스코드입니다.
# 크기가 N이고, 모든 값이 0인 1차원 리스트 초기화
n = 10
a = [0] * n
print(a)
콘솔 출력
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
리스트의 인덱싱과 슬라이싱
인덱스 값을 입력하여 리스트의 특정한 원소에 접근하는 것을 인덱싱이라고 합니다. Python 의 인덱스 값은 양의 정수와 음의 정수를 모두 사용할 수 있으며, 음의 정수를 넣으면 원소를 거꾸로 탐색할 수 있습니다.
예를 들어 인덱스에 -1을 넣으면 가장 마지막 원소가 출력됩니다. 이런 성징을 이용해 인덱시을 하여 특정 원소에 접근한 뒤에 그 값을 간단하게 바꿀 수 있습니다.
a = [1, 2, 3, 4, 5, 6, 7, 8, 9]
# 뒤에서 첫 번째 원소 출력
print(a[-1])
# 뒤에서 세 번째 원소 출력
print(a[-3])
# 네 번째 원소 값 변경
a[3] = 7
print(a)
콘솔 출력
9
7
[1, 2, 3, 7, 5, 6, 7, 8, 9]
또한 리스트에서 연속적인 위치를 갖는 원소들을 가져와야 할 때는 슬라이싱을 이용할 수 있습니다. 이 때는 대괄호 안에 콜론(:)을 넣어서 시작 인덱스와 (끝 인덱스 -1) 을 설정할 수 있습니다. 예제를 이용해 보도록 하겠습니다.
a = [1, 2, 3, 4, 5, 6, 7, 8, 9]
# 두 번째 원소부터 네 번째 원소까지
print(a[1:4])
콘솔 출력
[2, 3, 4]
리스트 컴프리헨션
리스트 컴프리헨션은 리스트를 초기화하는 방법 중 하나입니다. 리스트 컴프리헨션을 이용하면 대괄호 안에 조건문과 반복문을 넣는 방식으로 리스트를 초기화할 수 있습니다. 이 경우 한 줄의 소스코드로 리스트를 초기화 할 수 있어 매우 간편합니다. 그리고 Python 의 경우 이러한 기법을 많이 이용하므로 컴프리헨션을 모를 경우 다른 사람이 짠 코드 분석이 힘들 수 있습니다. 왜냐하면 제가 Python 을 처음 공부할 때 Python 은 쉽다고 해서 아무런 공부를 하지 않고 코드를 봤다가 이해를 하지 못했던 적이 있기 때문입니다. 우선 간단한 예시로 자세히 알아보도록 하겠습니다.
# 0 부터 19까지의 수 중에서 홀수만 포함하는 리스트
array = [i for i in range(20) if i % 2 == 1]
print(array)
# 콘솔 출력
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
그리고 이러한 컴프리헨션은 코딩 테스트에서 2차원 리스트를 초기화할 때 매우 효과적으로 사용될 수 있습니다. 예를 들어 N x M 크기의 2차원 리스트를 초기화할 때는 다음과 같이 사용합니다.
# N x M 크기의 2차원 리스트 초기화
n = 3
m = 3
array = [[0] * m for _ in range(n)]
print(array)
콘솔 출력
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
문자열 자료형
문자열
문자열 변수를 초기화할 때는 큰 따옴표(“)나 작은 따옴표(‘)를 이용합니다. 다만, 우리가 소스코드를 작성할 때는 문자열 안에 큰 따옴표나 작은 따옴표가 포함되어야 하는 경우가 있습니다. 기본적으로 문자열을 큰따옴표로 구성하는 경우, 내부적으로 작은 따옴표를 포함할 수 있습니다. 반대로 문자열을 작은따옴표로 구성하는 경우 내부적으로 큰 따옴표를 이용할 수 있습니다. 혹은 백슬래쉬()를 사용하면, 큰따욤표나 작은 따옴표를 문자열에 원하는 만큼 포함시킬 수 있습니다. 예제로 한 번 살펴보도록 하겠습니다.
data = 'Hello World'
print(data)
data = "Don't you know \"Python\"?"
print(data)
콘솔 출력
Hello World
Don't you know "Python"?
문자열 연산
Python 은 문자열에 대한 연산도 지원하는데 문자열을 처리할 때 유용하게 사용할 수 있습니다. 문자열은 덧셈, 곱셈과 리스트에서 사용하던 인덱싱과 슬라이싱을 이용할 수 있습니다.
튜플 자료형
파이썬의 튜플 자료형은 리스트와 거의 비슷한데 다음과 같은 차이가 있습니다.
- 튜플은 한 번 선언된 값을 변경할 수 없다.
- 리스트는 대괄호를 이용하지만 튜플은 소괄호를 이용한다.
그리고 리스트가 있는데 튜플을 사용하는 이유로는 튜플의 특성이 값을 변경할 수 없는 것과, 리스트와 비교해서 튜플이 굉장히 가벼운 자료형이기 때문입니다. 이와 관련해서는 추후에 좀 더 자세히 다루도록 하며, 일단은 이 정도 이유가 있다는 것만 알고 계시면 될 것 같습니다. 우선 튜플이 정말로 변경이 되지 않는지 한 번 테스트를 진행해 보겠습니다.
a = (1, 2, 3, 4)
print(a)
a[2] = 7
콘솔 출력
(1, 2, 3, 4)
Traceback (most recent call last):
File "/Users/seongwooklee/PycharmProjects/PythonProject/src/PythonGrammar/data_type.py", line 4, in <module>
a[2] = 7
TypeError: 'tuple' object does not support item assignment
튜플의 값 (1, 2, 3, 4) 는 그대로 출력되는 것을 확인할 수 있습니다. 하지만 튜플의 특정한 값을 변경하려고 할 때는 오류 메시지가 출력됩니다. 오류의 내용에서는 원소의 대입이 불가능하다는 메시지가 출력되고 있습니다.
튜플 자료형은 코딩 테스트에서 그래프 알고리즘을 구현할 때 자주 사용됩니다. 예를 들어 다익스트라 최단 경로 알고리즘처럼 최단 경로를 찾아주는 알고리즘의 내부에서는 우선순위 큐를 이용하는데 해당 알고리즘에서 우선순위 큐에 한 번 드어간 값은 변경되지 않습니다. 그래서 그 우선순위 큐에 들어가는 데이터를 튜플로 구성하여 소스코드를 작성합니다. 이렇게 알고리즘을 구현하는 과정에서 일부러 튜플을 이용하게 되면 혹여나 자신이 알고리즘을 잘못 작성함으로써 변경하면 안되는 값이 변경되고 있지는 않은지 체크할 수 있습니다. 또한 튜플은 리스트에 비해 상대적으로 공간 효율적이고, 일반적으로 각 원소의 성질이 서로 다를 때 주로 사용합니다. 흔히 다익스트라 최단 경로 알고리즘에서는 비용
과 노드 번호
라는 서로 다른 성질의 데이터를 (비용, 노드번호)의 형태로 함께 튜플로 묶어서 관리하는 것이 관례입니다.
사전 자료형
사전 자료형 소개
사전 자료형은 key 와 value 의 쌍을 데이터로 가지는 자료형입니다. 앞서 다루었던 리스트나 튜플은 값을 순차적으로 저장한다는 특징이 있습니다. 하지만 사전 자료형은 키-값 쌍을 데이터로 가진다는 점에서 우리가 원하는 변경 불가능한 데이터를 키로 사용할 수 있습니다. 파이썬의 사전 자료형은 내부적으로 해시 테이블(hash table) 을 이용하므로 기본적으로 데이터의 검색 및 수정에 있어서 O(1) 의 시간에 처리할 수 있어 매우 빠르게 데이터를 조회할 수 있습니다. 예시로 사전 자료형 사용 방법에 대해서 살펴 보도록 하겠습니다.
data = dict()
data['사과'] = 'Apple'
data['바나나'] = 'Banana'
data['코코넛'] = 'Coconut'
print(data)]
콘솔 출력
{'사과': 'Apple', '바나나': 'Banana', '코코넛': 'Coconut'}
사전 자료형은 코딩 테스트에서도 자주 사용될 수 있습니다. 예를 들어 학생의 번호가 1부터 10,000,000까지로 구성되어 있는 상황엣 최대 10,000 명의 학생을 선택했다고 가정해 보겠습니다. 이후에 특정한 학생 번호가 주어졌을 때 해당 학생이 선택되었는지를 어떻게 빠르게 알 수 있을까요? 만약 리스트를 이용한다면 1부터 10,000,000까지의 각 번호가 선택 되었는지를 저장할 수 있는 리스트를 만들어야 합니다. 다시 말해 1,000만 개 데이터를 저장할 수 있는 리스트를 만들어야 하므로 많은 메모리 공간이 낭비됩니다. 이 중 999만 개 가량의 데이터는 쓰이지 않기 때문입니다.
하지만 사전 자료형을 이용하는 경우 1,000만 개의 데이터를 담을 필요가 없으며, 10,000 개의 데이터만 사전 자료구조에 들어가므로 훨씬 적은 메모리 공간을 사용할 수 있습니다.
사전 자료형에 특정한 원소가 있는지 검사할 때는 원소 in 사전
의 형태를 사용할 수 있습니다. 이는 리스트나 튜플에 대해서도 사용할 수 있는 문법입니다.
사전 자료형 관련 함수
사전 자료형과 관련하여 자주 사용하는 함수로는 사전 자료형에 있는 키값들만을 뽑아내는 keys() 와 value 값 들만 뽑아내 주는 values() 함수를 주로 사용합니다. 예시를 통해 사용방법을 알아보겠습니다.
data = dict()
data['사과'] = 'Apple'
data['바나나'] = 'Banana'
data['코코넛'] = 'Coconut'
# 키 데이터만 담은 리스트
key_list = data.keys()
# 값 데이터만 담은 리스트
value_list = data.values()
print(key_list)
print(value_list)
# 각 키에 따른 값을 하나씩 출력
for key in key_list:
print(data[key])
콘솔 출력
dict_keys(['사과', '바나나', '코코넛'])
dict_values(['Apple', 'Banana', 'Coconut'])
Apple
Banana
Coconut
집합 자료형
Python 은 집합(set)을 처리하기 위한 집합 자료형을 제공하고 있습니다. 집합은 기본적으로 리스트 혹은 문자열을 이용해 만들 수 있는데 다음과 같은 특징이 있습니다.
- 중복을 허용하지 않는다
- 순서가 없다.
이전에 다루었던 리스트나 튜플은 순서가 있기 때문에 인덱싱을 통해 자료형의 값을 얻을 수 있었습니다. 반면 사전 자료형과 집합 자료형은 순서가 없기 때문에 인덱싱으로 값을 얻을 수 없다는 특징이 있습니다. 이와 더불어 집합 자료형에서는 키가 존재하지 않고, 값 데이터만을 담게 됩니다. 특정 원소가 존재하는지를 검사하는 연산의 시간 복잡도는 사전 자료형과 마찬가지로 O(1) 입니다.
집합 자료형은 보통 중복을 허용하지 않을 경우에 자주 사용되며 특히 특정한 데이터가 이미 등장한 적이 있는지 여부
를 체크할 때 매우 효과적입니다. 집합 자료형을 초기화할 때는 set() 함수를 이용하거나, 중괄호 안에 각 원소를 콤마(,) 를 기준으로 구분해서 넣으면 됩니다. 이와 관련해서는 예시를 통해 자세히 알아보도록 하겠습니다.
# 집합 자료형 초기화 방법 1
data = set([1, 1, 2, 3, 4, 4, 5])
print(data)
# 집합 자료형 초기화 방법 2
data = {1, 1, 2, 3, 4, 4, 5}
print(data)
콘솔 출력
{1, 2, 3, 4, 5}
{1, 2, 3, 4, 5}
집합 자료형의 연산
기본적인 집합 연산으로는 합집합, 교집합, 차집합 연산이 있습니다. Python 과 같이 집합 자료형이 있는 Java 의 경우 이런 연산이 없고, 함수를 이용해야 합니다만 Python은 이러한 집합 자료형의 연산에 대해서 다루고 있습니다. 집합 자료형 데이터 사이에서 합집합을 계산할 때는 |
를 이용합니다. 또한 교집합은 &
차집합은 -
를 이용합니다. 예시를 통해 알아보도록 하겠습니다.
a = set([1, 2, 3, 4, 5])
b = set([3, 4, 5, 6, 7])
print(a|b) # 합집합
print(a&b) # 교집합
print(a-b) # 차집합
콘솔 출력
{1, 2, 3, 4, 5, 6, 7}
{3, 4, 5}
{1, 2}
집합 자료형 관련 함수
집합 자료형 또한 다른 자료형과 마찬가지로 다양한 함수가 존재합니다. 집합에 하나의 데이터를 추가하고자 할 때에는 add() 함수를 이용합니다. update() 함수는 여러 개의 값을 한꺼번에 추가하고자 할 때 사용합니다. 그리고 특정한 값을 제거할 때는 remove() 함수를 이용할 수 있습니다. 이 때 add(), remove() 함수는 모두 시간 복잡도가 O(1) 입니다. 예시를 통해 한 번 살펴보도록 하겠습니다.
data = set([1, 2, 3])
print(data)
# 새로운 원소 추가
data.add(4)
print(data)
# 새로운 원소 여러 개 추가
data.update([5, 6])
print(data)
# 특정한 값을 갖는 원소 삭제
data.remove(3)
print(data)
콘솔 출력
{1, 2, 3}
{1, 2, 3, 4}
{1, 2, 3, 4, 5, 6}
{1, 2, 4, 5, 6}
조건문
조건문은 프로그램을 작성할 때 프로그램의 흐름을 제어하는 문법입니다. 조건문을 이용하면 조건에 따라서 프로그램의 로직을 설정할 수 있습니다.
Python 에서 조건문을 작성할 때는 if ~ elif ~ else 문을 이용합니다. 또한 C 나 Java 와 같이 중괄호를 이용해서 조건문의 조건이 참일 시 실행하는 부분을 설정하지 않고 콜론(:) 과 탭(\t) 을 이용합니다.
그래서 저는 한창 Python 을 이용해서 개발을 할 때 자주 겪었던 문제로 구분을 탭으로 하다보니 같은 Python 파일이라도 내가 사용하던 vi 가 아닌 다른 vi 로 해당 파일을 열고 저장을 하게 되면 탭 단위가 맞지 않아 파일이 실행이 되지 않거나 혹은 조건문, 반복문 등 각 부분들이 모두 깨지는 문제를 겪었었습니다.
if 조건문 1:
조건문 1이 True 일 때 실행되는 코드
elif:
조건문 1에 해당하지 않고, 조건문 2가 True 일 때 실행되는 코드
else:
위의 모든 조건문이 모두 True 값이 아닐 때 실행되는 코드
비교 연산자
조건문에는 비교 연산자를 자주 사용합니다. 비교 연산은 특정한 두 값을 비교할 이용합니다.
비교 연산자 | 설명 |
---|---|
x==y | x와 y 가 서로 같을 때 True 이다. |
x!=y | x와 y 가 서로 다를 때 True 이다. |
x>y | x가 y 보다 클 때 True 이다. |
x<y | x가 y 보다 작을 때 True 이다. |
x>=y | x가 y보다 크거나 같을 때 True 이다. |
x<=y | x가 y보다 크거나 같을 때 True 이다. |
논리 연산자
논리 연산자는 2개의 논리 값 사이의 연산을 수행할 때 사용하는데 Python 에는 3가지 논리 연산자가 있습니다.
논리 연산자 | 설명 |
---|---|
x and y | x 와 y 가 모두 True 일 때 True 이다. |
x or y | x 와 y 중에 하나만 True 이어도 True 이다. |
not x | x 가 False 일 때 True 이다. |
in, not in 연산자
연산자 | 설명 |
---|---|
X in 리스트 | 리스트 안에 X 가 들어가 있을 때 True 이다. |
X not in 문자열 | 문자열 안에 X 가 들어가 있지 않을 때 True 이다. |
특히 조건부 표현식은 리스트에 있는 원소의 값을 변경해서, 또 다른 리스트를 만들고자 할 때 매우 간결하게 사용할 수 있습니다. 예시를 통해 알아보도록 하겠습니다.
일반적인 형태의 조건문을 이용하면 다음과 같이 작성됩니다.
a = [1, 2, 3, 4, 5, 5, 5]
remove_set = {3, 5}
result = []
for i in a:
if i not in remove_set:
result.append(i)
print(result)
콘솔 출력
[1, 2, 4]
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]
반복문
반복문은 특정한 소스코드를 반복적으로 실행하고자 할 때 사용할 수 있습니다. Python 에서는 while 문과 for 문이 있는데 어떤 것을 사용해도 괜찮습니다만 특정 케이스를 제외하곤 일반적으로 for 문을 많이 사용합니다. 그래서 반복문의 경우 for 문에 대해서만 알아보도록 하겠습니다.
for 문
Python 의 for 문 구조는 in 뒤에 오는 데이터에 포함되어 있는 모든 원소를 첫 번째 인덱스부터 차례대로 하나씩 방문합니다. in 뒤에 오는 데이터로는 리스트, 튜플, 문자열 등이 사용될 수 있습니다.
for 변수 in 리스트:
실행할 소스코드
함수
함수는 프로그래밍에 있어서 굉장히 중요합니다. 프로그래밍을 하다 보면 똑같은 코드가 반복적으로 사용되어야 할 때가 많습니다. 함수를 사용하지 않으면 소스코드를 매번 일일이 작성해야 하므로, 소스코드가 길어지며 이로 인해 프로그램의 크기가 비효율적으로 커지게 됩니다. 코딩 테스트에서 테스트 케이스가 입력된 뒤에 테스트 케이스 만큼 특정한 알고리즘을 수행한 결과를 반복적으로 출력하도록 요구하는 문제가 출제되는 경우가 많습니다. 이럴 때는 문제를 푸는 코드를 함수화하면 매우 효과적으로 풀 수 있습니다. 이처럼 동일한 알고리즘을 반복적으로 수행해야 할 때 함수는 중요하게 사용됩니다. Python 에서 함수의 구조는 다음과 같습니다. 삼수를 작성할 때는 함수 내부에서 사용되는 변수의 값을 전달 받기 위해 매개 변수를 정의할 수 있습니다. 이후에 함수에서 어떠한 값을 반환하고자 할 때는 return 을 이용합니다.
def 함수명(매개변수):
실행할 소스코드
return 반환 값
함수 안에서 함수 밖의 변수 데이터를 변경해야 하는 경우가 있습니다. 이때는 함수에서 global 키워드를 이용하면 됩니다. global 키워드로 변수를 지정하면, 해당 함수에서는 지역 변수를 만들지 않고, 함수 바깥에 선언된 변수를 바로 참조하게 됩니다. 예시를 통해 좀 더 자세히 알아보도록 하겠습니다.
a = 0
def func():
global a
a += 1
for i in range(10):
func()
print(a)
콘솔 출력
10
마지막으로 Python 에는 람다 표현식(Lambda Express)을 사용할 수 있습니다. 람다 표현식을 이용하면 함수를 매우 간단하게 작성하여 적용할 수 있습니다. 특정한 기능을 수행하는 함수를 한 줄에 작성할 수 있다는 점이 특징입니다. 이러한 람다식은 Python 의 정렬 라이브러리를 사용할 때, 정렬 기준(Key)을 설정할 때에도 자주 사용됩니다. 간단한 예시를 통해 알아보도록 하겠습니다.
def add(a, b):
return a+b
# 일반적인 add() 메서드 사용
print(add(3, 7))
# 람다 표현식으로 구현한 add() 메서드
print((lambda a, b: a+b)(3,7))
콘솔 출력
10
10
입출력
알고리즘 문제 풀이의 첫 번째 단계는 데이터를 입력 받는 것입니다. 그리고 실무에서도 파일 입출력을 많이 사용합니다. 알고리즘 문제의 경우 적절한 입력이 주어졌을 때, 그 입력을 받아서 적절한 알고리즘을 수행한 뒤의 결과를 출력하는 것을 요구하기 때문입니다.
Python 에서 데이터를 입력 받을 때에는 input() 을 이용합니다. input() 의 경우 한 줄의 문자열을 입력 받도록 해줍니다. 만약 파이썬에서 입력받은 데이터를 정수형 데이터로 처리하기 위해서는 문자열을 정수로 바꾸는 int 함수를 사용해야 합니다.
그리고 여러 개의 데이터를 입력 받을 때는 데이터가 공백으로 구분되는 경우가 많습니다. 그래서 입력받은 문자열을 띄어쓰기로 구분하여 각각 정수 자료형의 데이터로 저장하는 코드의 사용 빈도가 매우 높습니다. 이때는 list(map(int, input.split()))
을 이용하면 됩니다.
입력을 위한 전형적인 소스코드
콘솔 입력
5
65 90 75 34 99
# 데이터의 개수 입력
n = int(input())
# 각 데이터를 공백으로 구분하여 입력
data = list(map(int, input().split()))
data.sort(reverse = True)
print(data)
콘솔 출력
[99, 90, 75, 65, 34]
또한 문제를 풀다보면 입력을 최대한 빠르게 받아야 하는 경우가 있습니다. 흔히 정렬, 이진 탐색, 최단 경로 문제의 경우 매우 많은 수의 데이터가 연속적으로 입력이 되곤 합니다. 예를 들어 1,000 만 개가 넘는 라인이 입력되는 경우, 입력을 받는 것만으로도 시간 초과를 받을 수 있습니다. Python 의 경우 입력의 개수가 많은 경우 단순히 input() 함수를 그대로 사용하지는 않습니다. Python 의 input() 함수는 동작 속도가 느려 시간 초과로 오답 판정을 받을 수 있기 때문입니다. 이러한 경우에는 Python 의 sys 라이브러리에 정의 되어 있는 sys.stdin.readline() 함수를 이용합니다. sys 라이브러리는 다음과 같은 방식으로 사용하며 input() 함수와 같이 한 줄씩 입력 받기 위해 사용합니다.
import sys
sys.stdin.readline().rstrip()
sys 라이브러리를 사용할 때는 한 줄 입력을 받고 나서 rstrip() 함수를 꼭 호출해 주어야 합니다. readline() 으로 입력을 받으면 입력 후 엔터가 줄 바꿈 기호로 입력되는데, 이 공백 문자를 제거하려면 rstrip() 함수를 사용해야 제거할 수 있기 때문입니다. 알고리즘 문제 푸는데 자주 사용되고 짧으니 예시를 외우도록 합시다.
import sys
# 문자열 입력 받기
data = sys.stdin.readline().rstrip()
print(data)
주요 라이브러리의 문법과 유의점
코딩 테스트에서 자주 사용되는 주요 라이브러리의 문법과 유의할 점에 대해서 추가로 알아보도록 하겠습니다. 일부 Python 라이브러리는 잘못 사용하면 수행 시간이 비효율적으로 증가하므로 이 절에서 설명하는 내용을 잘 기억합시다.
표준 라이브러리 란 특정한 프로그래밍 언어에서 자주 사용되는 표준 소스코드를 미리 구현해 놓은 라이브러리를 말합니다. 코딩 테스트에서는 대부분 표준 라이브러리를 사용할 수 있도록 허용하므로 표준 라이브러리를 사용하면 소스코드 작성량에 대한 부담을 줄일 수 있습니다.
Python 에서 지원하는 표준 라이브러리는 굉장히 다양하지만, 코딩 테스트를 준비하며 반드시 알아야 하는 라이브러리는 6가지 정도입니다. 이 6가지 라이브러리 또한 각각 많은 기능을 포함하고 있어서 모든 기능을 언급할 수는 없으므로, 여기서는 각 라이브러리의 가장 중요하고 알아두어야 할 핵심 내용만 요약하여 설명하고자 합니다.
내장 함수
Python 에는 별도의 import 명령어 없이 바로 사용할 수 있는 내장 함수가 존재합니다.
sum
sum() 메서드는 리스트와 같은 iterable 객체가 입력으로 주어졌을 때, 모든 원소의 합을 반환합니다. 예시를 통해 알아보도록 하겠습니다.
list = [1,2,3,4,5]
result = sum(list)
print(result)
콘솔 출력
15
min
min() 메서드는 파라미터가 2개 이상 들어왔을 때 가장 작은 값을 반환합니다. 예시를 통해 알아보도록 하겠습니다.
result = min(7, 3, 5, 2)
print(result)
콘솔 출력
2
max
max() 메서드는 파라미터가 2개 이상 들어왔을 때 가장 큰 값을 반환합니다. 예시를 통해 알아보도록 하겠습니다.
result = max(7, 3, 5, 2)
print(result)
콘솔 출력
7
eval
eval() 메서드는 수학 수식이 문자열 형식으로 들어오면 해당 수식을 계산한 결과를 반환합니다. 예시를 통해 알아보도록 하겠습니다.
result = eval("(3+5)*7")
print(result)
콘솔 출력
56
sorted
sorted() 메서드는 iterable 객체가 들어왔을 때 정렬된 결과를 반환합니다. key 속성으로 정렬기준을 명시할 수 있으며, reverse 속성으로 정렬된 결과 리스트를 뒤집을지의 여부를 설정할 수 있습니다. 예시를 통해 알아보도록 하겠습니다.
result = sorted([9, 1, 8, 5, 4]) # 오름 차순 정렬
print(result)
result = sorted([9, 1, 8, 5, 4], reverse = True) # 내림 차순 정렬
print(result)
콘솔 출력
[1, 4, 5, 8, 9]
[9, 8, 5, 4, 1]
Python 에서는 리스트의 원소로 리스트나 튜플이 존재할 때 특정한 기준에 따라서 정렬을 수행할 수 있습니다. 정렬 기준은 key 속성을 이용해 명시할 수 있습니다. 예시를 통해 좀 더 자세히 알아보도록 하겠습니다.
list = [('홍길동', 35), ('이순신', 75), ('아무개', 50)]
result = sorted(list, key = lambda x: x[1], reverse = True)
print(result)
콘솔 출력
[('이순신', 75), ('아무개', 50), ('홍길동', 35)]
리스트와 같은 iterable 객체는 기본적으로 sort() 메서드를 내장하고 있어 굳이 sorted 함수를 사용하지 않고도 정렬이 가능합니다. sort() 를 사용할 경우 해당 객체의 내부 값이 정렬된 값으로 바로 변경이 됩니다.
itertools
itertools 는 Python 에서 반복되는 데이터를 처리하는 기능을 포함하고 있는 라이브러리입니다. 제공하는 클래스는 매우 다양하지만, 코딩 테스트에서 가장 유용하게 사용할 수 있는 클래스는 permutations, combinations 입니다.
permutations
permutations 는 리스트와 같은 iterable 객체에서 r 개의 데이터를 뽑아 일렬로 나열하는 모든 경우(순열)을 계산해줍니다. permutations 는 클래스이므로 객체 초기화 이후에는 리스트 자료형으로 변환하여 사용합니다. 예시를 통해 알아보도록 하겠습니다.
from itertools import permutations
data = ['A', 'B', 'C']
result = list(permutations(data, 3)) # 모든 순열 구하기
print(result)
콘솔 출력
[('A', 'B', 'C'), ('A', 'C', 'B'), ('B', 'A', 'C'), ('B', 'C', 'A'), ('C', 'A', 'B'), ('C', 'B', 'A')]
combinations
combinations 는 리스트와 같은 iterable 객체에서 r 개의 데이터를 뽑아 순서를 고려하지 않고 나열하는 모든 경우를 계산합니다. combinations 는 클래스이므로 객체 초기화 이후에는 리스트 자료형으로 변환하여 사용합니다. 예시를 통해 알아보도록 하겠습니다.
from itertools import combinations
data = ['A', 'B', 'C']
result = list(combinations(data, 2)) # 모든 순열 구하기
print(result)
콘솔 출력
[('A', 'B'), ('A', 'C'), ('B', 'C')]
product
product 는 permutations 와 같이 리스트와 같은 iterable 객체에서 r 개의 데이터를 뽑아 일렬로 나열하는 모든 경우를 계산합니다. 다만 중복을 허용합니다. product 객체를 초기화 할 때는 뽑고자 하는 데이ㅓㅌ의 수를 repeat 속성값으로 넣어줍니다. product 는 클래스이므로 객체 초기화 이후에는 리스트 자료형으로 변환하여 사용합니다. 예시를 통해 알아보도록 하겠습니다.
from itertools import product
data = ['A', 'B', 'C']
result = list(product(data, repeat=2)) # 2 개를 뽑는 모든 순열 구하기 (중복 허용)
print(result)
콘솔 출력
[('A', 'A'), ('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'B'), ('B', 'C'), ('C', 'A'), ('C', 'B'), ('C', 'C')]
heapq
Python 에는 힙 Heap 기능을 위해 heapq 라이브러리를 제공한다. heapq 는 다익스트라 최단 경로 알고리즘을 포함해 다양한 알고르짐에서 우선순위 큐 기능을 구현하고자 할 때 사용됩니다. heapq 외에도 PriorityQueue 라이브러리를 사용할 수 있지만, 코딩 테스트 환경에서는 보통 heapq 가 더 빠르게 동작하므로 heapq 를 이용하도록 합시다.
Python 의 힙은 최소 힙(Min Heap) 으로 구성되어 있으므로 단순히 원소를 힙에 전부 넣었다가 빼는 것만으로도 시간 복잡도 O(NlogN) 에 오름차순 정렬이 완료됩니다. 보통 최소 힙 자료구조의 최상단 원소는 항상 가장 작은
원소이기 때문입니다.
힙에 원소를 삽입할 때는 heapq.heappush() 메서드를 이용하고, 힙에서 원소를 꺼내고자 할 때는 heapq.heappop() 메서드를 이용합니다. 힙 정렬(Heap Sort) 을 heapq 로 구현한 예제를 통해 heapq 의 사용 방법에 대해서 알아보도록 하겠습니다.
import heapq
def heapsort(iterable):
h = []
result = []
# 모든 원소를 차례대로 힙에 삽입
for value in iterable:
heapq.heappush(h, value)
# 힙에 삽입된 모든 원소를 차례대로 꺼내어 담기
for i in range(len(h)):
result.append(heapq.heappop(h))
return result
result = heapsort([1, 3, 5, 7, 9, 2, 4, 6, 8, 0])
print(result)
콘솔 출력
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
그리고 Python 에서는 최대 힙(Max Heap) 을 제공하지 않습니다. 따라서 heapq 라이브러리를 이용하여 최대 힙을 구현해야 할 때는 원소의 부호를 임시로 변경하는 방식을 사용합니다. 힙에 원소를 삽입하기 전에 잠시 부호를 반대로 바꾸었다가, 힙에서 원소를 꺼낸 뒤에 다시 원소의 부호를 바꾸면 됩니다. 이러한 방식으로 최대 힙을 구현하여 오름차순 힙 정렬을 구현한 예시는 다음과 같습니다.
import heapq
def heapsort(iterable):
h = []
result = []
# 모든 원소를 차례대로 힙에 삽입
for value in iterable:
heapq.heappush(h, -value)
# 힙에 삽입된 모든 원소를 차례대로 꺼내어 담기
for i in range(len(h)):
result.append(-heapq.heappop(h))
return result
result = heapsort([1, 3, 5, 7, 9, 2, 4, 6, 8, 0])
print(result)
콘솔 출력
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
bisect
Python 에서는 이진 탐색을 쉽게 구현할 수 있도록 bisect 라이브러리를 제공합니다. bisect 라이브러리는 정렬된 배열
에서 특정한 원소를 찾아야 할 때 매우 효과적으로 사용됩니다. bisect 라이브러리에서는 bisect_left() 메서드와 bisect_right() 메서드가 가장 중요하게 사용되며, 이 두 메서드는 시간 복잡도 O(logN)에 동작합니다.
- bisect_left() 정렬된 순서를 유지하면서 리스트 a 에 데이터 x 를 삽입할 가장 왼쪽 인덱스를 찾는 메서드
- bisect_right() 정렬된 순서를 유지하면서 리스트 a 에 데이터 x 를 삽입할 가장 오른쪽 인덱스를 찾는 메서드
예를 들어 정렬된 리스트 [1, 2, 4, 4, 8]이 있을 때, 새롭게 데이터 4를 삽입하려 한다고 가정할 때 bisect_left(a, 4) 와 bisect_right(a, 4) 는 각각 인덱스 값으로 2와 4를 반환합니다. 예제 코드를 통해 자세히 알아보도록 하겠습니다.
from bisect import bisect_left, bisect_right
a = [1, 2, 4, 4, 8]
x = 4
print(bisect_left(a, 4))
print(bisect_right(a, 4))
콘솔 출력
2
4
또한 bisect_left() 메서드와 bisect_right() 메서드는 정렬된 리스트에서 값이 특정 범위에 속하는 원소의 개수를 구하고자 할 때, 효과적으로 사용될 수 있습니다. 예시를 통해 자세히 알아보도록 하겠습니다 시간 복잡도는 O(longN) 입니다.
from bisect import bisect_left, bisect_right
# 값이 [left_value, right_value] 인 데이터의 개수를 반환하는 함수
def count_by_range(a, left_value, right_value):
right_index = bisect_right(a, right_value)
left_index = bisect_left(a, left_value)
return right_index - left_index
# 리스트 선언
a = [1, 2, 3, 3, 3, 3, 4, 4, 8, 9]
# 값이 4인 데이터 개수 출력
print(count_by_range(a, 4, 4))
# 값이 [-1, 3] 범위에 있는 데이터 개수 출력
print(count_by_range(a, -1, 3))
콘솔 출력
2
6
위와 같은 결과가 나오는 이유에 대해서 잠깐 설명을 하자면 값이 4인 데이터의 개수 출력 케이스를 가지고 설명을 하겠습니다. [1, 2, 3, 3, 3, 3, 4, 4, 8, 9]
리스트에서 bisect_left() 를 이용해 4가 시작되는 왼쪽 인덱스 값을 뽑으면 6이 나오게 됩니다. 그리고 bisect_right() 를 이용해 오른쪽에서 4가 시작되는 인덱스를 뽑으면 8이 나오게 됩니다. 여기서 헷갈릴 수 있는데 bisect_left 는 리스트의 인덱스 값을 그대로 사용하고 있고, bisect_right() 는 인덱스 값에 1을 더해준 값을 사용하고 있다는 것을 생각하시면 이해가 잘 되실 것 같습니다.
collections
Python 의 collections 라이브러리는 유용한 자료구조를 제공하는 표준 라이브러리입니다. collections 라이브러리의 기능 중에서 코딩 테스트에서 유용하게 사용되는 클래스는 deque 와 Counter 입니다.
보통 Python 에서는 deque 를 사용해 큐를 구현합니다. 별도로 제공되는 Queue 라이브러리가 있지만 일반적인 큐 자료구조를 구현한 라이브러리는 아닙니다. 따라서 deque 를 이용해 큐를 구현해야 한다는 점을 기억해 주시면 되겠습니다.
deque
Python 에서 제공하는 리스트 자료형은 데이터 삽입, 삭제 등의 다양한 기능을 제공합니다. 리스트가 있을 때 중간에 특정한 원소를 삽입하는 것도 가능합니다. 하지만 리스트 자료형은 append() 메서드로 데이터를 추가하거나, pop() 메서드로 데이터를 삭제할 때 가장 뒤쪽 원소를 기준으로 수행됩니다. 따라서 앞쪽에 있는 원소를 처리할 때에는 리스트에 포함된 데이터의 개수에 따라서 많은 시간이 소요될 수 있습니다.
리스트에서 앞족에 있는 원소를 삭제하거나 앞쪽에 새 원소를 삽입할 때의 시간 복잡도는 O(N) 입니다. 하지만 deque 를 사용한다면 가장 앞쪽에 원소를 추가하거나 제거할 때에도 시간 복잡도가 O(1) 입니다. 표를 이용해 좀 더 간결하게 정리를 해보자면
동작 | 리스트 | deque |
---|---|---|
가장 앞쪽에 원소 추가 | O(N) | O(1) |
가장 뒤쪽에 원소 추가 | O(1) | O(1) |
가장 앞쪽에 있는 원소 제거 | O(N) | O(1) |
가장 뒤쪽에 있는 원소 제거 | O(N) | O(1) |
deque 에서는 리스트 자료형과 다르게 인덱싱, 슬라이싱 등의 기능은 사용할 수 없습니다. 다만, 연속적으로 나열된 데이터의 시작 부분이나 끝부분에 데이터를 삽입하거나 삭제할 때는 매우 효과적으로 사용될 수 있습니다. deque 는 스택이나 큐의 기능을 모두 포함한다고도 볼 수 있기 때문에 스택 혹은 큐 자료구조의 대용으로 사용될 수 있습니다.
deque 는 첫 번째 원소를 제거할 때 popleft() 를 사용하며, 마지막 원소를 제거할 때 pop() 을 사용합니다. 또한 첫 번째 인덱스에 원소 x 를 삽입할 때 appendleft(x) 를 사용하며, 마지막 인덱스에 원소를 삽입할 때 append(x) 를 사용합니다.
Counter
Python collections 라이브러리의 Counter 는 등장 횟수를 세는 기능을 제공합니다. 구체적으로 리스트와 같은 iterable 객체가 주어졌을 때, 해당 객체 내부의 원소가 몇 번씩 등장했는지를 알려줍니다. 따라서 원소별 등장 횟수를 세는 기능이 필요할 때 짧은 소스코드로 이를 구현할 수 있습니다. 몇몇분들은 아마 이런 생각을 하셨을 수도 있습니다. 리스트에서 특정 원소가 몇 개 있는지 세어주는 건 간단해서 직접 구현하면 되지 이런 것 까지 왜 라이브러리를 쓰는거지? 하고요 코딩 테스트를 쳐본 사람들은 공감할 것입니다. 보통 2~3 시간 정도 테스트 시간을 주지만 첫 번째 문제 혹은 문제의 내용이 가장 작은 문제를 읽었을 때 문제 내용을 다 읽고 났을 때 부터 시간이 굉장히 촉박하는 것을요 그렇다면 그 촉박한 시간에 간단하긴 하지만 그래도 직접 함수를 구현해야 하는데 또 직접 구현하다보면 실수가 발생할 수도 있고 그러다 보면 한 문제도 풀지 못하고 시간을 허비하는 경우가 많습니다. 그래서 이렇게 코딩 테스트에서 굉장히 많이 사용되는 것들은 굳이 구현하기 보다는 있는 것을 쓰는게 더 낫기 때문에 소개를 해보았습니다. 우선 예시를 통해 한 번 살펴보도록 하겠습니다.
from collections import Counter
counter = Counter(['red', 'blue', 'red', 'green', 'blue', 'blue'])
print(counter['blue']) # blue 가 등장한 횟수 출력
print(counter['green']) # green 이 등장한 횟수 출력
print(dict(counter)) # 사전 자료형으로 변환
콘솔 출력
3
1
{'red': 2, 'blue': 3, 'green': 1}
마치며
Python 을 이용한 알고리즘 공부 하기에 앞서 오랫동안 사용하지 않아 까먹었던 Python 의 기본적인 문법들에 대해 책에서 소개한 내용을 토대로 알아보고 정리를 해보았습니다.
책에 있는 내용들을 보면서 필요할 때마다 책을 펼쳐보기 싫어 제가 생각하는 중요한 내용들을 모두다 옮겨 적었으며, 때에 따라 추가적으로 제가 경험한 것들이 있을 경우에는 추가적인 내용도 넣었습니다.
제 생각엔 아마 책에서 설명한 것들이라 잘못된 내용은 없을 듯 하지만 그래도 잘못된 내용이 있다면 댓글 달아주시기 바라겠습니다.
긴 포스트 읽어주셔서 감사드리며, 오타나 궁금하신 내용이 있을 경우에도 댓글 남겨주세요!
참조
이것이 취업을 위한 코딩 테스트다 with python
저자 나동빈
Comments