코딩 테스트 문제를 풀기 전에는 당연히 코딩 테스트에 사용할 언어의 문법을 알아야 한다. 그러나 정작 문제를 풀다 보면 remove
와 pop
의 차이라든가, 여러가지 헷갈릴 때가 많았다.
더 자세한 내용은 추후 포스팅 할 예정이니 이 게시물에서는 기본적인 Python의 문법과 코딩 테스트에 자주 나오는 것들에 대해서만 포스팅하도록 하겠다.
빌트인 데이터 타입built-in data type
에는 언어 자체에서 제공하는 데이터 타입과 컬렉션 데이터 타입이 있다. 기본 데이터 타입으로는 정수형int
, 부동소수형float
, 문자열 타입이 있고 컬렉션 데이터 타입으로는 리스트, 튜플, 셋, 딕셔너리 등이 있다.
정수형은 양과 음의 정수, 0을 포함한다. 정수형은 사칙 연산 외에도 지수 연산**
, 나눗셈 연산의 몫만 반환하는 //
연산 등을 할 수 있다.
a = 13
b = 4
print(a + b) # 더하기 / 17
print(a - b) # 빼기 / 9
print(a * b) # 곱하기 / 52
print(a / b) # 나누기 (소수점 포함) / 3.25
print(a // b) # 나누기 (소수점 제외) / 3
print(a % b) # 모듈러 연산 (나머지) / 1
print(-a) # 부호를 바꿈 / -13
print(abs(-a)) # 절대값 / 13
print(a**b) # a의 b승 / 28561
print(a == b) # 같은 값인지 비교 / False
print(a != b) # 같지 않은 값인지 비교 / True
print(a > b) # 왼쪽 값이 더 큰지 비교 / True
print(a < b) # 왼쪽 값이 더 작은지 비교 / False
print(a >= b) # 왼쪽 값이 더 크거나 같은지 비교 / True
print(a <= b) # 왼쪽 값이 더 작거나 같은지 비교 / False
print(a & b) # AND / 4
print(a | b) # OR / 13
print(a ^ b) # XOR / 9
print(~a) # NOT / -14
print(a << 2) # 왼쪽 시프트 (a에 2^2를 곱한 것과 동일) / 52
print(a >> 1) # 오른쪽 시프트 (a를 2^1로 나눈 것과 동일) / 6
print(a and b) # 논리 연산 AND / 4
print(a or b) # 논리 연산 OR / 13
print(not a) # 논리 연산 NOT / False
부동소수형은 소수를 저장할 때 사용한다.
print(2.5 + 3.7) # 더하기 / 6.2
print(7.9 - 4.2) # 빼기 / 3.7
print(1.5 * 4.8) # 곱하기 / 7.199999999999999
print(10.0 / 3.2) # 나누기 / 3.125
print(10.0 // 3.2) # 정수형 나누기 / 3.0
print(10.0 % 3.2) # 모듈러 / 0.39999999999999947
print(2.0 ** 3.2) # 제곱 연산 / 9.18958683997628
x = 0.5
y = 1.2
z = 2.0
print(x > y and y < z) # AND 연산 / False
print(x < y or y < z) # OR 연산 / True
print(not (x > y)) # NOT 연산 / True
부동소수형 코드 실행 결과를 보면 눈에 띄는 내용이 있다. 10 % 3.2
의 연산 결과를 보면 결괏값이 0.4
가 아니라 0.39999999999999947
이다.
이런 이유는 파이썬은 부동소수형 데이터를 이진법으로 표현하기 때문이다. 표현 과정에서 오차가 발생하는 것이다. 이를 엡실론epsilon
이라고 한다.
이 내용을 언급한 이유는 코딩 테스트에서 부동소수형 데이터를 다룰 일이 생겼을 때 이 엡실론을 항상 생각하라는 이유에서이다. 부동소수형을 사용하여 코드를 작성하면 엡실론이라는 요소 때문에 일부 테스트 케이스가 통과하지 못할 수도 있으니 유의해야 한다.
마찬가지의 이유로 0.1을 3번 더한 a의 값에 0.3을 빼면 0이 아니다.
import sys
# 엡실론 출력
print(sys.float_info.epsilon) # 2.220446049250313e-16
# 부동소수점 수의 오차 검사
a = 0.1 + 0.1 + 0.1
b = 0.3
print(a - b) # 5.551115123125783e-17
if abs(a - b) < sys.float_info.epsilon:
print("a와 b는 같은 값입니다.") # 이 코드가 출력됨
else:
print("a와 b는 다른 값입니다.")
부동소수형 데이터를 활용하는 문제는 오차 허용 범위를 언급하는 경우가 많다. 문제를 분석할 때 꼭 이 부분을 체크해야 한다. 이 지점에서 정말 많은 사람이 실수한다.
컬렉션은 여러 값을 담는 데이터 타입을 말한다. 대표적으로 리스트list
, 튜플tuple
, 딕셔너리 dictionary
, 셋 set
, 문자열string
등이 있다.
그리고 이 컬렉션들은 데이터의 수정 가능 여부에 따라 변경할 수 있는 객체mutable object
와 변경할 수 없는 객체immutable object
이렇게 2가지로 구분한다. 먼저 변경할 수 있는 객체와 변경할 수 없는 객체 개념부터 설명하고 코드와 함께 컬렉션을 하나씩 설명해보도록 하겠다.
뮤터블 객체는 객체 생성 후 객체를 수정할 수 있다. 대표적인 뮤터블 객체는 리스트, 딕셔너리, 셋이다.
my_list = [1, 2, 3, 4, 5] # 리스트 객체 생성, [1, 2, 3, 4, 5]
my_list[4] = 6 # 리스트 원소 변경
print(my_list) # [1, 2, 3, 4, 6]
위 코드는 다음과 같이 동작한다. 그림에서 보듯 my_list
가 참조하고 있는 [1, 2, 3, 4, 5]
에서 5번째 위치에 있는 값을 6으로 수정한다.
이뮤터블 객체는 객체 생성 후 객체를 수정할 수 없다. 대표적인 이뮤터블 객체는 정수, 부동소수점, 문자열, 튜플이다.
뮤터블, 이뮤터블을 설명하기 위해 잠시 정수를 빌려와 예로 들었다. 정수가 컬렉션 데이터 타입에 포함되는 것이 아니므로 오해 없기 바란다.
a = 4 # ➊ a = 4
b = a # ➋ a = 4, b = 4 / b는 a가 아닌 a가 참조한 4를 참조
b += 2 # ➌ b = 6 / 기존에 참조한 객체를 수정하지 않음, 새 객체 6을 참조
print(a, b) # 4 6
➊ a가 4라는 객체를 참조한다.
➋ a가 참조한 4라는 이뮤터블 객체를 b도 똑같이 참조한다.
➌에 의해 b는 6이라는 객체를 참조한다. 주목할 점은 4는 이뮤터블 객체이므로 수정할 수 없다. 따라서 객체 6을 새로 생성하고 그 객체를 참조한다.
이처럼 뮤터블 객체와 이뮤터블 객체는 값의 수정 유무의 특징이 있으므로 동작하는 방식이 다르다. 그럼 계속해서 대표적인 컬렉션 타입인 리스트, 딕셔너리, 튜플, 문자열을 자세히 설명해보도록 하겠다.
리스트는 뮤터블 객체이다. [ ]
로 원소를 감싸는 형태로 사용한다. 다음 코드를 보면 리스트의 동작 방식을 쉽게 알 수 있을 것이다.
# 리스트 선언
my_list = [1, 2, 3, 4, 5]
my_list2 = [1, 3, 5] + [7, 9]
my_list3 = list(my_list)
print(my_list) # [1, 2, 3, 4, 5]
print(my_list2) # [1, 3, 5, 7, 9]
print(my_list3) # [1, 2, 3, 4, 5]
리스트는 흔히 ‘시퀀스(순서)가 있는 자료형이다’라고 말한다. 이런 특징으로 리스트는 인덱싱, 슬라이싱을 할 수 있다. 인덱싱과 슬라이싱을 알아보자.
인덱싱은 인덱스를 활용해서 특정 위치의 원소에 접근하는 것을 말합니다. 예를 들어 다음 코드에서 3번째 인덱스에 접근하는 my_list[2]
코드를 작성했는데, 이를 ‘인덱싱으로 원소에 접근한다’라고 표현한다.
my_list = [1, 2, 4]
# 값 추가
my_list.append(6) # ➊
print(my_list) # [1, 2, 4, 6]
# 인덱싱으로 값 삭제
del my_list[2] # ❷ del은 인덱스 위치에 있는 원소를 지우는 키워드이다.
print(my_list) # ➌ [1, 2, 6]
슬라이싱은 시퀀스 자료형의 범위를 지정해서 값들을 복사하여 가져오는 방식을 말한다. 슬라이싱은 파이썬 코드로 list_name[a:b]
와 같이 작성한다. 이렇게 작성한 코드는 인덱스 a 이상부터 b 미만에 해당하는 원소를 새 리스트에 담아 반환한다.
my_list = [1, 2, 3, 4, 5]
print(my_list[0:2]) # ➊ [1, 2]
print(my_list[1:]) # ➋ [2, 3, 4, 5]
print(my_list[3:4]) # ➌ [4]
print(my_list[-4:-2]) # ➍ [2, 3]
파이썬의 딕셔너리dictionary
는 뮤터블 객체이며, 키key
와 값value
쌍을 저장하는 해시 테이블로 구현되어 있다. 이름 그대로 키를 사용하여 값을 검색하는 자료형이라고 생각하면 된다.
my_dict = { }
# 딕셔너리 값 삽입
my_dict["apple"] = 1
my_dict["banana"] = 2
my_dict["orange"] = 3
# 딕셔너리 값 출력
print(my_dict) # {'apple': 1, 'banana': 2, 'orange': 3}
key = "apple"
if key in my_dict:
value = my_dict[key]
print(f"{key}: {value}") # apple: 1
else:
print(f"{key}는 딕셔너리에 존재하지 않습니다.")
my_dict["banana"] = 4
print(my_dict) # {'apple': 1, 'banana': 4, 'orange': 3}
del my_dict["orange"]
print(my_dict) # {'apple': 1, 'banana': 4}
# ➊ 딕셔너리 생성
my_dict = {"apple": 1, "banana": 2, "cherry": 3}
# ➋ my_dict에 없는 key로 설정
key = "kiwi"
# ➌ 키가 딕셔너리에 있는지 확인
if key in my_dict:
# ➍ 키가 딕셔너리에 있으면 해당 값 출력
print(f"값: {my_dict[key]}")
else:
# ➎ 키가 딕셔너리에 없으면 에러 메시지 출력
print(f"'{key}' 키가 딕셔너리에 없습니다.")
튜플은 이뮤터블 객체이다. 앞서 살펴본 리스트, 딕셔너리와 달리 한 번 생성하면 삽입하거나 삭제할 수 없다.
my_tuple = (1, 2, 3)
my_tuple = (1, 2, 3)
# 인덱싱
print(my_tuple[0]) # 1
print(my_tuple[1]) # 2
print(my_tuple[2]) # 3
# 슬라이싱
print(my_tuple[1:]) # (2, 3)
print(my_tuple[:2]) # (1, 2)
print(my_tuple[1:2]) # (2,)
문자열은 문자들을 집합의 형태로 구성한 이뮤터블 객체이다.
string = "Hello, World!" # 큰따옴표 사용
string2 = 'Hello, World!' # 작은따옴표 사용
문자열을 추가하고 삭제하는 동작에서 주목해야 할 점은 문자열은 이뮤터블 객체이므로 기존 객체를 수정하는 것이 아니라 새로운 객체를 반환한다는 사실이다.
string = "He" # ➊
string += "llo" # ➋
print(string) # "Hello"
➊ 문자열을 초기화한다. string
이 문자열 “he”
를 참조한다.
➋ string
이 참조하는 “He”
와 “llo”
를 합쳐 새로운 문자열을 만들고 string
은 그 문자열을 참조한다.
문자열을 수정하고 싶다면 어떻게 해야 할까? 그럴 때는 replace()
메서드를 사용하면 된다. replace( ) 메서드는 첫 번째 인수에 찾을 문자열을, 두 번째 인수에 변경할 문자열을 넣어 사용한다.
string = "Hello"
string = string.replace("l", "") # "l"을 모두 삭제
print(string) # Heo
def function_name(param1, param2, param3, ..., paramN):
# 함수의 실행 코드
# ...
# ...
return result # 반환값
def add(num1, num2):
result = num1 + num2
return result
# 함수 호출하여 결과 출력
ret = add(5, 10)
print(ret) # 15
파이썬은 람다식lambda expression
을 사용할 수 있다. 람다식은 함수를 더 간단하게 표현하는 방법이다. 또 람다식은 익명 함수anonymous function
를 만드는 데 사용한다. 익명 함수란 말 그대로 이름이 없는 함수를 말하며 코드에서 딱 한 번 실행할 목적으로 사용하거나, 다른 함수의 인수로 사용하는 데 사용한다.
lambda x, y : x + y # x와 y를 받아서 더한 값을 반환하는 람다식
람다식은 2가지 패턴으로 자주 사용한다. 첫 번째로 람다식은 변수로 참조할 수 있다. 그래서 변수에 람다식을 할당하고 람다식 실행이 필요한 경우 변수(a, b)와 같이 호출할 수 있다. 다음 코드를 봅시다.
# 람다를 이용한 간단한 함수 정의
add = lambda x, y: x + y
print(add(5, 4)) # 9
두 번째는 인수로 람다식을 넘기는 방법이다.
num = [1, 2, 3, 4, 5] # ➊ 리스트 선언
squares = list(map(lambda x: x**2, num)) # ➋ 람다식 넘기기
print(squares) # [1, 4, 9, 16, 25]
➊ 정수형 리스트를 하나 선언하고
➋ map()
함수에 람다식을 넘긴다.
이렇게 하면 map()
함수는 2번째 인수로 넘어온 리스트에 1번째 인수로 받은 람다식을 적용하여 num의 원소를 각각 제곱한 새 리스트를 반환한다.
조기 반환 early return
은 코드 실행 과정이 함수 끝까지 도달하기 전에 반환하는 기법이다. 이 방식은 코드의 가독성을 높여줄 뿐만 아니라 예외를 조금 더 깔끔하고 빠르게 처리할 수 있다.
def total_price(quantity, price):
total = quantity * price # ➊
if total > 100: # ➋ total이 100보다 크면
return total * 0.9 # ➌ 조기 반환
return total
print(total_price(4, 50))
➊ total에 quantity * price를 대입한다.
➋ total의 값이 100보다 큰 경우 ➌ total에 0.9를 곱하고 반환한다.
이렇게 하면 함수 자체를 조기에 종료할 수 있으므로 이후 예외에 대한 처리를 하지 않아도 되니 예외를 훨씬 빠르게 처리할 수 있다.
보호 구문guard clauses
은 본격적인 로직을 진행하기 전 예외 처리 코드를 추가하는 기법이다. 예를 들어 조건문을 이용하여 초기에 입력값이 유효한지 검사하고 그렇지 않으면 바로 함수를 종료하는 보호 구문을 쓸 수 있다.
def calculate_average(numbers):
if numbers is None: # ➊ 값이 없으면 종료(예외)
return None
if not isinstance(numbers, list): # ➋ numbers가 리스트가 아니면 종료(예외)
return None
if len(numbers) == 0: # ➌ numbers의 길이가 0이면 종료(예외)
return None
total = sum(numbers) # ➍
average = total / len(numbers)
return average
이렇게 구현한 코드는 보호 구문 이후 구현부에서 입력값에 대한 예외를 고려하지 않아도 되므로 보기 좋다. 추가로 이런 습관을 들이면 처음부터 예외를 고려할 수 있어 코드를 더 안전하게 작성할 수 있게 된다.
코드를 보면 ➊, ➋, ➌에서 예외 처리를 하여 함수를 종료시킨다. 여기서 예외를 잘 고려했다면 이후 코드에서는 ❹와 같이 원하는 동작 구현에만 집중하면 된다.
합성 함수composite method
는 2개 이상의 함수를 활용하여 함수를 추가로 만드는 기법입니다. 보통 합성 함수는 람다식을 활용한다.
def add_three(x): # ➊
return x + 3
def square(x): # ➋
return x * x
composed_function = lambda x: square(add_three(x)) # ➌
print(composed_function(3)) # ➍ (3 + 3)² = 36
➊과 ➋에서 2개의 함수를 정의한 다음 ➌에서 이 두 함수를 합성한다. 그 결과 ➍ 실제 호출하는 부분에서는 두 함수를 마치 하나의 함수처럼 활용하는 것을 볼 수 있다.
이렇게 코드를 구현하면 작은 기능을 분리해서 코드를 작성할 수 있으므로 관리자는 코드를 쉽게 관리할 수 있고, 함수를 사용하는 사용자는 코드를 쉽게 사용할 수 있다.
파이썬의 빌트인 데이터 타입은 기본 타입(정수형, 부동소수형, 문자열)과 컬렉션 타입(리스트, 딕셔너리, 튜플, 셋)이 있다.
파이썬의 데이터 타입은 이뮤터블(값을 변경할 수 없음) 타입과 뮤터블(값을 변경할 수 있음) 타입으로 나눌 수 있습니다. 이뮤터블 타입은 기본 타입, 문자열, 튜플이 있고 뮤터블 타입은 리스트, 딕셔너리, 셋이 있습니다.
함수는 프로그램의 기본 구성 요소로 파이썬에서는 예약어로 def로 정의할 수 있다.
람다식은 간결한 함수 표현 방법이다. 한 번만 사용하거나 인자로 함수를 넘겨야 할 경우 유용하다.
조기 반환, 보호 구문, 합성 함수 등의 기법을 활용하면 코드의 가독성과 효율성을 높일 수 있다.