Super Kawaii Cute Cat Kaoani

코딩테스트 [킬링캠프]

0.2 문자열

zozni 2024. 1. 2. 14:05
728x90
반응형
SMALL

 

 

문자열 처리하기

많은 분께서 파이썬으로 코딩테스트를 보는 이유 중 하나는 바로 문자열 처리의 편리함 때문입니다. 이번에는 코딩 테스트에서 편리함을 극대화해 주는 문자열 처리 관련 사용법에 대해 알아보겠습니다.

문자열 객체 (String)

파이썬에서 문자열은 텍스트 시퀀스(sequence)형으로 분류됩니다. 문자열은 작은 따옴표 '' , 큰 따옴표 "" 를 이용해서 표현합니다. 따옴표 종류에 상관없이 문자열을 생성할 수 있지만, 반드시 쌍을 맞춰서 사용해 줘야 합니다.

'morning'
"night"

문자열 선언하기

위에서 언급한 바와 같이 작은 따옴표 '' , 큰 따옴표 "" 를 사용해서 표현해줄수도 있고, 기본 문자열 형태를 str()로 초기화할 수 있습니다. 이때 생성된 빈 문자열의 길이는 0입니다.

# 빈 문자열 선언
a = ''
b = ""
c = str()

str() 의 인자로 객체를 전달합니다. 가장 큰 특징은 모든 객체를 str(obj) 를 사용해서 문자열 형태로 나타낼 수 있습니다. 그리고 화면에 보이는 문자 그대로를 저장하여, [ , ' 등의 요소들도 모두 개별적으로 문자로 처리됩니다. 아래에 변수 f에 대한 출력문이 있으니 결과창으로 확인해 보시면 좋을 것 같습니다.

a = 1      # 정수형
b = str(a) # 문자열 형태

c = [2, 7, 12, 'b', (2, 3)] # 리스트
d = str(c)                  # 문자열 형태

e = frozenset(['I', 'am', 'a', 'good', 'boy'])
f = str(e)

for i in f:
	print(i)
  • 결과
  • f r o z e n s e t ( { ' I ' , ' g o o d ' , ' a m ' , ' b o y ' , ' a ' } )

문자열 시퀀스 연산 및 인덱싱

파이썬에서 문자열은 리스트, 튜플 등과 같은 시퀀스(sequence)형에 해당합니다. 시퀀스형 데이터이기에 가능한 연산들이 존재하는데, 이것이 파이썬에서 문자열을 쉽고 효율적으로 다룰 수 있도록 만들어줍니다.

in / not in - 부분 문자열 존재 여부 확인

in 연산자는 for 문을 제외하고는 탐색 연산자로 활용됩니다. 그래서 문자열에 대해서 사용할 때도 부분 문자열이 존재하는지 안 하는지 검사하는 기능을 제공합니다. 문자열 메서드 find()와 유사한 특징을 갖고 있습니다.

a = 'aiajakalajbananajadaianaoa'

if 'banana' in a:
	print("Found")

if 'apple' not in a:
	print("No apple")
  • 결과
  • Found No apple

in의 시간복잡도

in 연산자의 시간복잡도는 in 뒤에 오는 객체 종류에 따라 달라집니다. 아래 표는 객체 종류에 따른 in 연산자의 평균 시간복잡도를 정리해 놓은 것입니다.

연산자 시간복잡도 ($n$ : 기존 문자열 길이)

리스트 (list) $O(n)$
튜플 (tuple) $O(n)$
문자열 (string) - 문자 1개 찾기 $O(n)$
문자열 (string) - 부분 문자열 찾기 $O(m n)$ ($m$ : 부분 문자열 길이)
집합 (set) $O(1)$
딕셔너리 (dictionary) $O(1)$

집합(set)과 딕셔너리(dictionary)는 최악의 경우, 해시 충돌 문제로 인해 $O(n)$의 시간복잡도를 가질 수 있습니다. 하지만 이러한 경우는 매우 드물고, 특히 코딩테스트에서는 발생하지 않을 것이기 때문에 위의 표와 같이 시간복잡도를 알아두시면 됩니다.

문자열 연결하기 (+, *)

파이썬에서는 문자열에 대한 +, *의 기능을 제공합니다. 각 연산자의 의미는 다음과 같습니다.

  • a + b : a와 b, 두 문자열을 이어 붙인다.
a = ["Park", "Kim", "Son", "Yoon"]
b = [4, 7, 2, 3]

for name, num in zip(a, b):
	result = name + " " + str(num)
	print(result)
  • 결과
  • Park 4 Kim 7 Son 2 Yoon 3
  • a * n 혹은 n * a : 문자열 a를 n번 더한다. (n번 이어 붙인다.)
s = "data "
ss = s * 10
print(ss)
  • 결과
  • data data data data data data data data data data

문자열 인덱싱(indexing)과 슬라이싱(slicing)

파이썬에서 문자열은 **인덱스(index)**로 접근할 수 있습니다. 정수 형태의 인덱스를 지원하여, 0은 첫 문자에 대한 인덱스이며, -1은 마지막 문자에 대한 인덱스입니다. 이외의 문자는 양수 혹은 음수 인덱스로 접근할 수 있습니다.

d = "From Oh to Lee"
print(d[0], d[5] ,d[11])
print(d[-1], d[-9], d[-14])
  • 결과
  • F O L e O F

그리고 이것과 더불어 **슬라이싱(slicing)**도 가능하여, 문자열을 손쉽게 자르고 연결하여 조작이 매우 편합니다.

s = "Python is wonderful!"

# 양수 인덱스로 슬라이싱
print(s[3:5])
print(s[:10])
print(s[8:])
print(s[3:11:2])

# 음수 인덱스로 슬라이싱
print(s[-5:-1])
print(s[-1:-12:-1])
print(s[::-1]) # 문자열 뒤집기!
  • 결과
  • # 양수 인덱스로 슬라이싱 ho Python is s wonderful! hni # 음수 인덱스로 슬라이싱 rful !lufrednow !lufrednow si nohtyP

<aside> 🚧 슬라이싱(slicing) 주의 사항

주의 사항 1) 슬라이싱 범위 인지하기 일반 슬라이싱과 똑같이 a[x:y] 라고 한다면, 인덱스 x부터 인덱스 y-1까지의 문자가 해당한다는 것을 인지하고 있어야 합니다.

주의 사항 2) 슬라이싱 시작점과 마지막점 이 부분은 step 수를 음수로 했을 때 가장 주의해야 합니다. step 수가 음수라면, 높은 값의 시작점에서 낮은 값의 마지막점을 지정해 줘야 해당 범위의 문자열이 순서가 역전된 형태로 출력됩니다. 위의 예시를 참고하시면 됩니다.

</aside>

문자열이 속한 시퀀스형은 iterable 객체에 해당하기 때문에 문자열의 경우, for문에서 문자별 접근을 가능하게 합니다. 인덱스로 접근하고 싶다면, range(len(str)) 을 이용하면 됩니다.

s = "This is a sentence"

# for문에 문자열을 이용하기
for i in s:
	print(i, end=" ") # print의 마지막을 개행이 아닌 띄어쓰기로 대체합니다.
print()

# for문에서 인덱스로 문자열 접근하기
for j in range(len(s)):
	print(s[j], end=" ")
print()
  • 결과
  • T h i s i s a s e n t e n c e T h i s i s a s e n t e n c e

문자열 비교

파이썬에서는 문자열에 대해 비교 연산자를 지원합니다. 우선 길이에 따라 문자열을 비교하고, 같은 길이의 문자열에 대해서는 첫 문자부터 비교를 수행합니다. 비교의 기준은 아스키(ASCII)코드⁽¹⁾입니다. 따라서 시퀀스 연산인 min() 과 max() 또한 같은 기준으로 문자열 간 혹은 문자 간 비교를 수행합니다.

# 길이가 다른 경우
print('abc' > 'anbodsnk')

# 길이가 같은 경우
print('abc' > 'ABC')

# 길이가 같은 경우 - 2번째 문자 간 비교
print('abc' > 'azd')

# min, max - 문자 간 비교
s = 'amzpomasoimwel;km./,mas;dnnjasd'
print(min(s), max(s))

# min, max - 문자열 간 비교
sl = ['add', 'substitute', 'multiply', 'divide']
print(min(sl), max(sl))

  • 결과
  • False True False , z add substitute

문자열 주요 메서드

이번 섹션에서는 코딩테스트에서 문자열을 다룰 때 자주 사용하는 주요 메서드를 모아놨습니다. 위에서 설명한 시퀀스 연산만으로는 못했던 문자열 처리, 가령 부분 문자열 바꾸기, 문자열 쪼개기 등을 해당 섹션에서 소개되는 메서드를 통해 수행할 수 있습니다.

  • s.find(substring) - 부분 문자열 존재 여부 확인

부분 문자열이 존재하는지 확인할 때 사용하는 메서드입니다. 부분 문자열이 존재하는 경우, 현재 문자열에서 부분 문자열의 1번째 인덱스, 즉 부분 문자열 중 가장 왼쪽에 있는 문자의 인덱스를 반환합니다. 만약 다수의 부분 문자열이 존재한다면, 가장 앞에 있는 부분 문자열의 1번째 인덱스를 반환합니다. 부분 문자열이 존재하지 않으면 -1을 반환합니다.

s = 'Good morning Good afternoon Good evening'

# 부분 문자열 존재하는 경우
print(a.find("Good"))

# 부분 문자열 존재하지 않는 경우
print(a.find("Bad"))
  • 결과
  • 0 -1
  • s.index(substring) - 부분 문자열 존재 여부 확인

s.find와 비슷한 기능을 제공하지만, 부분 문자열이 존재하지 않으면, ValueError가 발생합니다.

a = 'aiajakalajbananajadaibananaoa'

# 부분 문자열 존재하는 경우
print(a.index("banana"))

# 부분 문자열이 존재하지 않는 경우
print(a.index("apple"))
  • 결과
  • 10 ValueError: substring not found
  • s.count(substring) - 부분 문자열 존재 여부 및 개수 확인
  • 해당 연산은 현재 문자열 s에서 존재하는 부분 문자열 개수를 반환합니다. 존재하지 않는 부분 문자열의 개수를 반환해달라고 하면, s.index와 달리 ValueError을 발생시키지 않고 0을 반환합니다.
a = 'aiajakalajbananajadaibananaoa'

# 부분 문자열 존재하는 경우
print(a.count("banana"))

# 부분 문자열이 존재하지 않는 경우
print(a.count("apple"))
  • 결과
  • 2 0

s.count의 경우, 부분 문자열 존재 여부와 그 개수를 판단하기 위해 문자열 전체를 확인해 봐야 하므로 $O(mn)$의 시간복잡도를 가집니다. 이때 $m$은 부분 문자열의 길이, $n$은 기존 문자열의 길이를 의미합니다.

  • s.format() - 출력문 만들기
  • 해당 메서드는 출력문을 작성할 때 주로 사용합니다. 문자열 내에 변수 혹은 어떤 연산 결과를 포함할 수 있도록 해줍니다. format의 인자로 들어가는 변수 혹은 연산 결과는 각자의 순서를 가집니다. {} 안에 0 이상의 값을 지정할 수 있는데, 이것을 통해 format의 인자를 출력할 위치를 지정할 수 있습니다. {0}이면 1번째 인자, {1}는 2번째 인자, 그리고 {2}는 3번째 인자로 취급합니다. 아래의 예시 코드를 통해 확인해 보시기 바랍니다.
# 출력 위치 지정 안한 경우
a = 3
b = 2

print("{} + {} = {}".format(a, b, a+b))

# 출력 위치 지정한 경우
p1 = "Tom"
p2 = "David"
p3 = "Sally"

print("{0} hates {1}, but loves {2}".format(p1, p2, p3)) # 출력 위치 지정하기
print("{1} hates {2}, but loves {0}".format(p1, p2, p3)) # 출력 위치 바꾸기!
  • 결과
  • 3 + 2 = 5 Tom hates David, but loves Sally David hates Sally, but loves Tom
  • s.join() - 문자열 이어 붙이기 (자주 사용!)
  • 자주 사용하는 문자열 메서드 중 하나입니다. 인자로 여러 문자열이 담긴 iterable 객체(주로 리스트)를 받습니다. 그리고 현재 문자열 s를 통해 주어진 문자열들을 이어 붙여서 새로운 문자열을 반환합니다. 보통 s.split() 함수와 함께 사용됩니다.
# " "(공백 문자)를 이용해서 문장 만들기
words = ["Hi,", "my", "name", "is", "Nossi!"]

sentence = " ".join(words)
print(sentence)

# "-"를 이용해서 날짜 표현하기
ymd = ["2024", "01", "01"]
today = "-".join(ymd)
print(today)

# ":"를 이용해서 시간 표현하기
hms = ["23", "59", "59"]
now = ":".join(hms)
print(now)
  • 결과
  • Hi, my name is Nossi! 2024-01-01 23:59:59
  • s.split - 문자열 쪼개기 (자주 사용!)

입력값 형식이 주어지는 코딩테스트 문제에 대해서는 필수적으로 사용되는 메서드가 split 메서드입니다. 인자로 분리할 구분자 문자열을 입력하면, 해당 구분자에 따라 나눈 **문자열들로 이루어진 리스트(list)**를 반환합니다. 인자 maxsplit을 설정해서 문자열을 나누는 횟수를 지정할 수 있습니다. 메서드의 특징은 다음과 같습니다.

  1. 구분자 사이에 어떠한 문자도 없다면, 빈 문자열 '' 를 반환하는 리스트에 포함합니다.
  2. 구분 인자를 지정해 주지 않으면, 공백 " " 을 기준으로 구분하는 것으로 간주합니다.
  3. 공백으로 문자열을 나눌 경우, 문자열 앞 혹은 뒤에 있는 큰 공백을 제거하여 표현된 문자열만을 요소로 삼는 리스트를 반환합니다.
# 기본 형태
nums = "1, 2, 4, 5, 7, 10"
num_list = nums.split(",")
print("num_list :", num_list)

partial_list = nums.split(",", maxsplit=2)
print("partial_list :", partial_list)

# 특징 1번 예시
scores = "96, 95, 92, 80,, 20, 32"
score_list = scores.split(",")
print("score_list :", score_list)

# 특징 2번
result = "He is the winner of this game"
words = result.split()
print("words :", words)

# 특징 3번
ns = "                3            4          9             10             "
n_list = ns.split()
print("n_list :", n_list)
  • 결과
  • num_list : ['1', ' 2', ' 4', ' 5', ' 7', ' 10'] partial_list : ['1', ' 2', ' 4, 5, 7, 10'] score_list : ['96', ' 95', ' 92', ' 80', '', ' 20', ' 32'] words : ['He', 'is', 'the', 'winner', 'of', 'this', 'game'] n_list : ['3', '4', '9', '10']
  • s.replace() - 부분 문자열 교체하기 (자주 사용!)

파이썬에서는 s[5] = "b"와 같이 인덱스로 접근해서 특정 문자로 변경하는 것이 불가능합니다. 대신 replace 메서드를 이용해서 부분 문자열을 수정할 수 있습니다. 현재 문자열에 있는 바꿀 부분 문자열을 1번째 인자로, 새롭게 바꾸고 싶은 부분 문자열을 2번째 인자로 전달하면, 부분 문자열을 교체한 문자열 복사본을 반환합니다. 대상이 되는 모든 부분 문자열을 교체한다는 점을 주의하셔야 합니다. 아래 예시 코드로 확인해 보시기 바랍니다.

# 기본 형태
person_view = "He is very kind"

new_view = person_view.replace("kind", "selfish")

print(new_view)

# 다수의 부분 문자열 존재
commands = "Do your homework, Do your laundary, Eat breakfast, Wash your hands"

new_commands = commands.replace("your", "my")

print(new_commands)
  • 결과
  • He is very selfish Do my homework, Do my laundary, Eat breakfast, Wash my hands

f-string 사용하기 (디버깅에 활용하기)

f-string은 파이썬의 고유한 문자열 포매팅 방식입니다. f-string은 아래 예시와 같이 따옴표 '' 혹은 "" 앞에 f 혹은 F를 붙여서 사용하면 됩니다. 가장 큰 특징은 문자열 사이에 중괄호 {} 를 표시해서 치환 필드를 포함할 수 있는 구조가 되어있습니다. 1개의 필드에는 1개의 파이썬 표현식을 사용할 수 있습니다. 단순한 변수, 객체 치환 뿐만 아니라 객체의 메서드, 함수 등을 호출할 수도 있어서 매우 활용도가 높습니다.

from datetime import date

# 변수 치환하기
a = 3829201
b = 37
q = a // b
r = a % b
print(f"{a} = {q} * {b} + {r}")

# 내장 함수 이용하기
words = ["house", "door", "window", "toilet", "living room"]
print(f"There are {len(words)} in the list 'words' {words}")

# 객체 치환하기
print(f"Today is {date.today()}")
  • 결과
  • 3829201 = 103491 * 37 + 34 There are 5 in the list 'words' ['house', 'door', 'window', 'toilet', 'living room'] Today is 2024-01-01

**1. 문자열 비교 기준 - 유니코드(Unicode)**

유니코드(Unicode)는 전 세계의 모든 문자를 위한 고유한 코드 값을 제공하는 문자 인코딩 표준으로, 각 문자는 유니코드 포인트(Unicode Point)라는 고유한 숫자로 매핑됩니다. 영문 알파벳과 숫자 등의 ASCII 문자들은 유니코드에서 동일한 값을 지니며, 코딩 테스트에서 다루는 문자들은 보통 ASCII 문자 내에 존재하므로, ASCII 코드 값을 통해 문자열을 비교한다고 봐도 무방합니다.

728x90
반응형
LIST