supergravity

파이썬 - SEQUENCE 데이터 타입 본문

콘텐츠/파이썬-알고리즘 기초_ 2021

파이썬 - SEQUENCE 데이터 타입

supergravity 2021. 9. 23. 19:22

시작

이번 영상에서는 파이썬 SEQUENCE에 대해서 알아봅시다.

SEQUENCE 타입은 거의 모든 문제에서 사용이 되는 데이터 타입이기에 확실히 익혀야 합니다.

특히 SEQUENCE 타입 중 문자열의 경우 프로그래머스 레벨 1, 2에서 자주 사용됩니다.
문자열의 기본 연산과 함수들을 이용한 빡구현 문제가 대부분입니다.
그러면 시작해 봅시다.

SEQUENCE 타입

파이썬 SEQUENCE타입은 메모리에 연속적으로 저장되는 데이터 타입을 말합니다.

이처럼 연속적으로 메모리에 오브잭트들이 저장이 되는 특성을 효율적으로 다룰 수 있도록.

기본 기능을 제공하고 있습니다. 기본 기능은 아래와 같습니다.

 

시퀀스의 크기를 알 수 있는 len(seq)

시퀀스에 특정 오브젝트가 있는지 확인하는 obj in seq

시퀀스의 부분을 잘라오는 seq [a:b:c]

시퀀스를 반복 가능함 그래서 순차적으로 탐색 가능 loop(ex for while)

 

파이썬에서 SEQUENCE타입은 문자열, 튜플 리스트, 바이트 어레이, 바이트 등 5가지를 사용할 수 있습니다.

이들 중 프로그래머스에서 자주 사용되는 리스트 , 문자열, 튜플에 대해서 집중적으로 알아봅시다.

 

파이썬에서 리스트?

파이썬에서 리스트는 mutable sequanse data type입니다.
mutable은 immutable과는 대조적으로 메모리에 올려진 오브젝트를 변경 가능을 의미합니다. 

sequanse는 리스트의 성분이 연속적으로 메모리에 저장됨을 의미합니다.

그래서 리스트에서도 문자열의 sequance특징 때 살펴본 기능들을 사용할 수 있습니다.

 예로는 in, len, 슬라이싱, 루프가 있습니다.

그러면 문제 풀이에 필요한 리스트 지식을 알아봅시다.

리스트 생성

파이썬에서 리스트를 만들어 봅시다.
리스트 오브젝트는 브래킷과 콤마를 이용하여 생성합니다.

a = [1,2]


리스트 안의 성분은 정수형이 아닌 다른 데이터 타입도 사용이 가능합니다.

c = [1,[2,3],4] d = ["a" ,"b" ,"c" , 'D' ]

문제를 풀다 보면 성분이 없는 리스트를 생성할 때가 있습니다.
성분이 없는 리스트의 경우.

empty = []

이렇게 하면 됩니다.

기본 연산

구체적인 방법

리스트에는 +, *가 있습니다.
더하기의 경우 이와 같이 두 문자열을 더할 수 있습니다.

a = [1] 
b = a + [2] 
print(b) 
---> 출력 결과
[1, 2]

곱하기는 리스트를 반복하고 싶을 때 사용합니다.

c= b*2 
print(c) ---> [1,2,1,2]


출력되는 결과를 보면 리스트 b의 성분이 2번 반복되는 것을 볼 수 있습니다.

 

시간 복잡도

+의 경우

시퀀스의 특성인 '시퀀스의 처음 위치만 알면 나머지는 연속적으로 존재'를

이용하면 마지막 위치를 조회하는데 O(1)가 사용되고,

여기에 k개의 성분을 순차적으로 리스트에 추가하는데 O(k)가 사용됩니다.

그래서 O(1)*O(k) 가되어 O(k)입니다.

*의 경우 

각은 맥락으로 n개의 성분을 k번 곱해야 하기 때문에 O(nk)입니다.

 

리스트 인덱스, 수정: O(1), O(1)

인덱싱 구체적인 사용법

리스트는 mutable sequance type이기 때문에.
문자열에서 사용한 index기능을 사용할 수 있을 뿐만 아니라 성분을 수정할 수 있습니다.
그러면 인덱스 기능부터 살펴봅시다.

lt = [1,2,3,4,5] 

print(lt[0])
print(lt[1])
---> 출력 결과
1
2

만약 인덱스를 초과하여 접근하게 되면 에러가 생성됩니다.
프로그래머스에서 인덱스를 초과하는 경우 런타임 에러가 발생합니다.

print(lt[7]) 
-> 에러

뒤에서 접근할 때 용이성을 위해서 파이썬에서는 음수 인덱스를 제공합니다.
마지막 인덱스가 -1이고 앞으로 갈수록 -1씩 감소하여.
첫 번째 성분의 경우 인덱스가 -7이 됩니다.

print(lt[-1]) 
---> 출력 결과
5

또한 마지막 인덱스에 더하기 1을 하면 리스트의 성분 수를 얻을 수 있습니다.
성분의 수는 sequence에서 길이라고 표현하며 len()을 이용하면 한 번에 찾을 수 있습니다.

print(len(lt))

---> 출력 결과
5


아까 전에 공부한
음수 인덱스와 양수 인덱스는


길이 - 양수 인덱스 = 음수 인덱스

 

의 관계를 가지고 있습니다

인덱싱 시간복잡도

인덱스의 시간 복잡도의 경우 O(1)입니다.

이러한 이유는 리스트의 성분들이 순차적으로 메모리에 배열이 되어 있기에.

시작 주소만 알면 시작 주소 + 인덱스 연산을 통해 위치를 바로 받아올 수 있기 때문입니다.

 

수정 구체적인 방법

리스트의 경우 mutable type이기 떄문에 수정이 가능합니다.

lt[0] = 'a' 
print(lt) 
-->출력 결과
['a',2,3,4,5]

이렇게 리스트 인덱싱을 통해 위치를 정하고 값을 정해주면 됩니다.

 

수정 시간복잡도

수정의 경우 O(1)입니다. 

이러한 이유는 먼저 인덱싱을 통해 위치를 알아내고 이 메모리가 가리키는 값을 

가른 곳을 가리키게 만들면 되기 때문입니다.

리스트 슬라이싱

구체적인 방법

이전 챕터에서 인덱스를 통해 한 가지 성분을 다루는 인덱싱에 대해서 알아보았습니다.
이번에는 부분 성분을 가져오는 슬라이싱에 대해 알아봅시다.

l = [1,2,3,4,5,6]

슬라이싱은 :를 이용하여 구현합니다.
구체적으로
리스트[시작 인덱스 : 마지막 인덱스]
이와 같이 표현됩니다.
이를 l 리스트에 적용해 봅시다.

print(l[0:3]) 
-->출력 결과
[1,2,3]

 

출력된 결과를 보면 0번 인덱스부터 2번 인덱스까지 출력된 것을 볼 수 있습니다.
이처럼 슬라이싱의 시작 인덱스는 포함되고 마지막 인덱스는 포함되지 않습니다.

 

시간복잡도

인덱싱을 통해 슬라이싱 하고 싶은 부분을 조회해야 합니다.

그래서 만약 복사하고 싶은 부분의 수가 k라면,

인덱싱을 k번 해야 합니다. O(1) *k

그래서 시간 복잡도는 O(k)가 됩니다.

루프

구체적인 방법

파이썬에서 문자열과 같은 sequence타입에 루프로 접근하는 방법은
대략적으로 3가지가 존재합니다. 예를 봐봅시다.

l = [1,2,3,4] 
for idx in range(len(test)): 
    print(idx, test[idx])
    
---> 출력 결과
0, 1
1, 2
2, 3
3, 4

 

여기서 range() 함수는 sequence 타입의 데이터를 만드는 함수입니다.
예를 보면 range() 안에 len()이 보입니다.
len()은 sequence의 길이를 가져오는 함수입니다.
이번의 경우 len(l)가 4입니다.
그래서 range(4)는 0,1,2,3을 리턴합니다.
이를 이용하면 test의 성분을 순차적으로 접근할 수 있습니다.

range(start=0, end, step=1)

range(시작=0, 끝, 스텝=1) 함수는 시작 인덱스, 끝 인덱스 그리고 스텝의 크기를 파라 매터로 받습니다.
그리고 시작인 덱스부터 끝 인덱스 전까지 스텝을 밟으며 값을 리턴합니다.
만약 시작 인덱스를 파라매터로 전달하지 않는다면 디폴트로 0이 설정됩니다.
보폭 또한 디폴트로 1이 설정되어 있습니다.

그래서 예제의 range(4)는 range(0,4,1)과 같아 0,1,2,3을 순차적으로 리턴합니다.

 

리스트도 sequence이기 때문에 for 루프를 이용하여 순차적으로 접근할 수 있습니다.
출력해 보면 1부터 5까지 순차적으로 출력되는 것을 확인할 수 있습니다.

test = ["A","B"]
for char in test: 
    print(char)
---> 출력 결과
A
B

range()와 비슷한 enumerate()를 이용하면 index와 문자를 같이 받아 올 수 있습니다.
enumerate()는 sequence를 파라매터로 받고 파라매터로 받은 sequence의 성분과 인덱스를 순차적으로 리턴합니다.

for idx, char in enumerate(test): 
    print(idx, char)
---> 출력 결과
0, A
1, B

 

루프 시간 복잡도

만약 루프를 통해 탐색해야 하는 데이터 수가 n이면.

시간복잡도는 O(n)입니다.

한줄로 list 정의 하기, 컴프리헨션

구체적인 방법

for문을 이용하여 사용할 list를 초기화 하는 경우가 많습니다.

이를 깔끔하게 한줄로 작성하게 도움을 주는 문법이 컴프리헨션입니다.

처음 접한다면 새로운 문법에 대한 거부감이 있을 수 있지만,

진짜 편하고 많이 사용합니다.

li = [ str(element) for element in range(10) ]

print(li)

---> ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']

li = [ str(element) for element in range(10) if element % 2 == 0]

print(li)

---> ['0', '2', '4', '6', '8']

이런식으로 for와 []를 이용하여 list를 만들수 있습니다.

뒤에 if를 추가하면 조건에 부합하는 것들만 첨가됩니다.

 

시간복잡도

for loop와 마찬가지로 O(n)입니다. 

여기서 n은 in 뒤에 iterable이라 불리는 반복 가능한 객체의 길이입니다.

리스트 메서드

append

구체적 방법

append 메서드는 리스트 마지막에 성분을 추가하는 함수입니다.

l = [2,3,4,5] 
l.append(6) 
print(l) ---> [2,3,4,5,6]

여기서 리스트 l 자체가 변경된 것을 확인할 수 있습니다.
이는 리스트은 mutable이기 떄문입니다.

 

시간 복잡도

리스트 마지막에 추가하는 일은

인덱싱을 통해 마지막 자리를 탐색하고 , O(1)

마지막 자리 다음에 새로운 자리를 만들고 값을 추가, O(1)

하면 되기에 O(1)입니다.

pop

구체적인 방법

pop(index)은 index를 파라매터로 받습니다.

pop을 실행하면 index로 받은 성분을 제거합니다.

만약 index를 파라매터로 제공하지 않으면 디폴트로 -1이 사용됩니다.

l = [2,3,4,5] 
print(l.pop()) 

---> 출력 결과
5 

print(l) 

---> 출력 결과
[2,3,4]

l.pop(0)
print(l)

---> 출력 결과
[3,4]

시간복잡도

pop(k)의 경우

k번째 성분을 제거하고,O(1)

연속적인 데이터를 만들기 위해 k이후의 성분을 1칸씩 앞으로 당깁니다. O(k)

그래서 O(k)입니다.

pop(-1)의 경우

len을 통해 마지막 성분의 위치를 확인하고,  O(1)

그 값을 버리면 됩니다. O(1) *O(1)

그래서 O(1)입니다.

revserse

구체적인 방법

리스트를 역순으로 만듭니다.

l = [2,3,4,5] 

l.reverse() 

print(l) 

---> [5,4,3,2]

시간복잡도

뒤에서부터 인덱싱으로 접근하고, O(1)

모든 성분을 조회해야 하기 때문에, O(1) *n

O(n)입니다.

remove

index가 아닌 값을 파라매터로 받습니다.

받은 값중 가장 앞에 존재하는 값을 제거합니다.

test = [7,7,2,2,7]

test.remove(7)

print(test)


---> 출력 결과

[7, 2, 2, 7]

 

시간복잡도

가운데 성분을 제거하고 메모리 상에 연속적으로 연결 시켜야 합니다.

k번째를 삭제한다고 하면, k번째 성분에 접근하고 삭제하는 데 O(1)가 사용되고,

k이후의 성분을 앞으로 당기는데 O(n-k)가 사용됩니다.

sort

구체적인 방법

sort는 리스트의 성분을 정렬하는 메서드입니다.
숫자의 경우 크기순으로 정렬되고. 문자열의 경우 알파벳 순으로 정렬이 됩니다.

a = [2,3,4,5,6,2,2,2] 

b = ['a','b','a','b','c'] 

a.sort() 

print(a) 

---> 

[2, 2, 2, 2, 3, 4, 5, 6] 

b.sort() 

print(b) 

---> 

['a', 'a', 'b', 'b', 'c'] 

a.sort(reverse=True) 

print(a) 

---> 

[6, 5, 4, 3, 2, 2, 2, 2]

reverse=True를 파라매터로 제공하면 역순으로 정렬할 수 있습니다.
a.sort()의 리턴 값은 None이고 리스트를 직접 변환시킵니다.

 

시간복잡도 

O(nlog2 n)입니다.

정렬은 나중에 구체적으로 해야 합니다.

그러니 일단 넘어갑시다.

count

구체적인 방법

count메서드는 타깃을 파라 매터로 받고 리스트의 성분 중 타겟과 같은 값이 몇 개 있는 지를 리턴합니다.

a = [1,2,3,1,1,1] 

print(count(1))

---> 4

시간복잡도

모든 성분을 탐색하며, O(n)

값이 타깃 값과 같은지 확인, O(n)*O(1)

해야 합니다.

그래서 O(n)입니다.

Index

구체적인 방법

인댁스 메서드는 타깃을 파라매터로 받고.
타겟이 리스트의 인덱스가 몇인지 리턴합니다.
만약 리스트에 없다면 에러를 생성합니다.

b = ['a','b','a','b','c'] 

print(b.index('c')) 

---> 4 

b.index('d') 

---> error

시간복잡도

모든 성분을 탐색하고, O(n)

타겟과 같은지 확인해야 합니다. O(n)*O(1)

그래서 O(n)입니다.

문제 풀기

출처: 프로그래머스 코딩 테스트 연습, https://programmers.co.kr/learn/challenges

 

코딩테스트 연습

기초부터 차근차근, 직접 코드를 작성해 보세요.

programmers.co.kr

배운 지식을 이용하여 문제를 풀어 봅시다.
문제를 푸는 프로세스는
첫째 구라코드를 작성한다.
여기서 구라코드란 수도코드의 은어입니다.
수도코드는 코드 구현전 어떻게 작성할지 생각한 내용을 스케치한 것을 말합니다.
둘째 여러 구라코드중 가독성과 효율성을 고려하여 선택한다.
셋째 구현한다.
넷째 다른 사람의 풀이와 비교한다.

현재 레벨에서 둘째에 표현된 가독성과 효율성을 판단하는 기준은
"파이썬에서 기본으로 제공하는 것을 이용한다"라고 생각을 합시다.

이 프로세스에서 구현 파트가 가장 시간이 적게 들어야 합니다.
구라 코드 작성과 다른 사람의 풀이 비교가 문제 풀이 실력을 향상하는 핵심이기 때문에.
이 둘에 집중하며 문제를 풀어 봅시다.

3.1 음양 더하기

https://programmers.co.kr/learn/courses/30/lessons/76501

 

코딩테스트 연습 - 음양 더하기

어떤 정수들이 있습니다. 이 정수들의 절댓값을 차례대로 담은 정수 배열 absolutes와 이 정수들의 부호를 차례대로 담은 불리언 배열 signs가 매개변수로 주어집니다. 실제 정수들의 합을 구하여 re

programmers.co.kr

숫자가 담긴 리스트와 부화가 담긴 리스트를 가공하고.

연산 결과를 제출하는 문제입니다.

인풋의 길이가 1천번 이기떄문에.

자유롭게 리스트의 기능을 이용하여 구현하면 좋습니다.

구라코드

1.두 개의 리스트를 한 번에 돌려야 하기 때문에 range()를 사용한다.
2.if sign의 참이면
  정답에 ansolute를 더한다.
  else
  정답에 뺀다.
3.답을 제출한다.

 

이번 문제의 경우 저는 1가지 방법만 생각이 되기 때문에 이를 구현해 봅시다.

def solution(absolutes, signs):
    answer = 0
    for i in range(len(signs)):
        if signs[i]:
            answer += absolutes[i]
        else:
            answer -= absolutes[i]
    return answer

다른 사람 풀이 중 range() 함수 대신 zip() 함수를 이용한 경우를 찾아볼 수 있습니다.
zip() 함수를 이용하니 코드가 깔끔하고 좋아요가 많은 것을 볼 수 있습니다.
이렇게 좋은 방법이 있으면 공부하고 다음에 써먹을 수 있도록 정리합시다.

파이썬에서 문자열

파이썬 문자열은 immutable sequence data type입니다.
문자열의 Immutable 속성은 메모리 위에 올라간 파이썬 오브젝트는 변경이 불가능함을 의미합니다.

아래에서 볼 수 있듯 리스트의 경우 mutable 타입이기 때문에 리스트의 성분을 수정할 수 있었지만 문자열의 경우 오류가 발생합니다.

st = 'ABCDEFG' 
st[0] = 'a' 
-->오류 

lt = [1,2,3,4,5] 
lt[0] = 6 
---> [6,2,3,4,5]

 

sequence는 문자열의 문자가 연속적으로 저장됨을 의미합니다.

그래서 문자열의 문자는 위치를 나타내는 인덱스가 존재합니다.

문자의 위치를 나타내는 인덱스가 존재함으로 인하여 문자열의 길이 자르기 등이 기본 함수로 구현되어 있습니다.
말이 좀 어려운데 이해가 안 되더라도 스트레스받지 않으셔도 됩니다
뒤에 차근차근 설명할 거예요
러면 본격적으로 파이썬 문자열을 구체화해 봅시다.

문자열 생성

파이썬에서 문자열을 만들어 봅시다.
문자열 오브젝트는 작은따옴표로 감싸서 만들 수 있습니다.

a = '안녕'

 

작은따옴표 말고도 아래와 같이 문자열 오브젝트를 만들 수 있습니다.

c = "hellow" 
d = '''문제에서 잘 사용안함.''' 
e = """이것도 별로. """

이들 4가지 중 변수 a, c에 해당하는 방법을 자주 사용하여 문제를 풀게 됩니다.
빈 문자열을 만드는 방법은

empty = "" 
empty = ''

이렇게 하면 됩니다.

기본 연산

구체적인 방법

문자열에서는 +, *가 있습니다.
더하기의 경우 이와 같이 두 문자열을 더할 수 있습니다.

a = '안' 
b = a + '녕'

곱하기는 같은 문자열을 반복하고 싶을 때 사용합니다.

c= b*10

이런 식으로 사용하면 됩니다.
출력되는 결과를 보면 '안녕'이 10번 반복되는 것을 볼 수 있습니다.

 

시간 복잡도

리스트와 같은 맥락입니다.

문자열은 시퀀스 타입이기 때문에 처음 데이터의 위치만 알면 연속적으로 저장이 되어있습니다.

그래서 마지막 위치를 조회하는데 O(1), 마지막 다음에 k번 문자 추가, O(k) 이기 때문에.

더하기의 경우 O(k)입니다.

곱하기는 마지막 위치를 조회 O(1), 마지막 다음에 문자의 수를 k번 추가, O(nk)이기 때문에.

O(nk)입니다.

문자열 인덱스, 대입과 불변

구체적인 방법

문자열은 sequence타입이면서 immutable입니다.
이 특성에 대해서 구체적으로 알아봅시다.
문자열은 sequence이기 때문에 index기능을 파이썬에서 제공합니다.
인덱스 기능을 예제를 통해 알아봅시다.

st = 'ABCDEFG' st[0] st[1] print(st[0]) print(st[1])

 

스트링의 첫 번째에 위치한 문자 A는 0번 인덱스를 가지고 있습니다.
그 바로 뒤에 등장하는 B는 1번 인덱스를 가지고 있습니다.
이러한 인덱스를 가지고 문자열의 문자에 접근할 수 있는데.
접근할 때는 브래킷을 이용하여 접근합니다.
이런 식으로 접근한 값을 프린트해보면 A와 B가 등장하는 것을 볼 수 있습니다.

 

만약 인덱스를 초과하여 접근하게 되면 에러가 생성됩니다.
프로그래머스에서 인덱스를 초과하는 경우 런타임 에러가 발생합니다.

print(st[7]) -> 에러

문자열을 뒤에서 접근할 때 용이성을 위해서 파이썬에서는 음수 인덱스를 제공합니다.
문자열 마지막 문자의 인덱스가 -1이고 앞으로 갈수록 -1씩 감소하여,
첫 번째 A의 경우 -7이 됩니다.

st[-1]

 

또한 마지막 문자의 양수 인덱스에 더하기 1을 하면 문자열의 개수를 얻을 수 있습니다.
문자열 개수는 sequence에서 문자열 길이라고 표현하며 len()을 이용하면 한 번에 찾을 수 있습니다.

len(st)

눈치가 빠르신 분을 알아차리셨겠지만
음수 인덱스와 양수 인덱스는

길이 - 양수 인덱스 = 음수 인덱스

의 관계를 가지고 있습니다

문자열은 index를 이용하여 접근할 수 있지만,
수정할 수 없습니다.
이는 immutable타입으로 설정되어 있기 때문입니다.
만약 수정을 시도하면 오류가 발생하게 됩니다.

st[0] = 'a' -->오류

 

시간 복잡도

리스트와 마찬가지로 시퀀스 타입이기에 익덱싱과 len은 O(1)입니다.

문자열 슬라이싱

구체적인 방법

아까 전에 인덱스를 통해 한 가지 문자를 가져오는 인덱싱에 대해서 알아보았습니다.
이번에는 문자열에서 여러 문자를 가져오는 슬라이싱에 대해 알아봅시다.

test = "ABCDEF"

슬라이싱은 :를 이용하여 구현합니다.
구체적으로
문자열[시작 인덱스 : 마지막 인덱스]
이와 같이 표현됩니다.
이를 test 문자열에 적용해 봅시다.

print(test[0:3])
--->출력 결과
"ABC"

 

출력된 결과를 보면 0번 인덱스부터 2번 인덱스까지 출력된 것을 볼 수 있습니다.
이처럼 문자열 슬라이싱의 시작 인덱스는 포함되고 마지막 인덱스는 포함되지 않습니다.

 

시간 복잡도

성분을 k번 조회하고, O(k)

성분을 복사하여 저장, O(const)

그래서 O(k)*O(const) = O(k)입니다.

루프

파이썬에서 문자열과 같은 sequence타입에 루프로 접근하는 방법은
대략적으로 3가지가 존재합니다. 예를 봅시다.

test = 'ABCD' 
for idx in range(len(test)): 
    print(idx, test[idx])
    
---> 출력 결과
0, A
1, B
2, C
3, D

 

여기서 range() 함수는 sequence 타입의 데이터를 만드는 함수입니다.
예를 보면 range() 안에 len()이 보입니다.
len()은 sequence의 길이를 가져오는 함수입니다.
이번의 경우 len(test)가 4입니다.
그래서 range(4)는 0,1,2,3을 리턴합니다.
이를 이용하면 test의 성분을 순차적으로 접근할 수 있습니다.

 

range(start=0, end, step=1)

range() 함수는 시작 인덱스, 끝 인덱스 그리고 스텝의 크기를 파라 매터로 받습니다.
그리고 시작인 덱스부터 끝 인덱스 전까지 스텝을 밟으며 값을 리턴합니다.
만약 시작 인덱스를 파라매터로 전달하지 않는다면 디폴트로 0이 설정됩니다.
보폭 또한 디폴트로 1이 설정되어 있습니다.
그래서 예제의 range(4)는 range(0,4,1)과 같아 0,1,2,3을 순차적으로 리턴합니다.

 

문자열도 sequence이기 때문에 for 루프를 이용하여 순차적으로 접근할 수 있습니다.
출력해 보면 A부터 D까지 순차적으로 출력되는 것을 확인할 수 있습니다.

for char in test: 
    print(char)
---> 출력 결과
'A'
'B'
'C'
'D'

 

ange()와 비슷한 enumerate()를 이용하면 index와 문자를 같이 받아 올 수 있습니다.
enumerate()는 sequence를 파라매터로 받고 파라매터로 받은 sequence의 성분과
인덱스를 순차적으로 리턴합니다.

for idx, char in enumerate(test): 
    print(idx, char)

https://docs.python.org/ko/3/tutorial/introduction.html#lists

 

시간복잡도

n번 탐색하기 떄문에 O(n)입니다.

문자열 메서드

이번 챕터는 문제풀이에 유용한 문자열 메서드를 알아봅시다.
여기서 메서드란 객체 지향 언어에서 쓰이는 전문용어입니다.
만약 메서드의 용어가 익숙하지 않으시다면 '함수'라고 생각하면 됩니다.

이번 챕터는 7개의 메서드들을 나열하며 진행이 되는데,
모두 암기할 필요도 없고,
어디에 필요할지 몰라 재미없다고 스킵할 필요도 없습니다.
그냥 한번 쭉 욱 보고 나중에 문제를 풀 때 메서드가 존재했다는 사실만 기억하면 됩니다.
메서드들은 파이썬 공식문서에서 찾아볼 수 있습니다.
그러면 문자열 메서드를 알아봅시다.

find

구체적인 방법

find가이 어떤 함수인지 예를 통해 알아봅시다.
먼저 123456789를 test에 저장하고 find() 메서드의 파라메터로 '34'와 '36'을 프린트해봅시다.

test = '123456789' 
print(test.find('34')) 
print(test.find('36')) 
---> 2 
---> -1

'34'의 경우 test에 존재하고 index 2부터 시작합니다.
그래서 리턴되는 값은 2입니다.
'36'의 경우 test에 존재하지 않기 때문에 -1을 리턴합니다.

find()는 문자열 메서드이고 점을 이용해여 접근 가능합니다.(.find())
찾고자 하는 타깃 문자열을 파라매터로 받고 문자열에 없으면 -1을 리턴합니다.
만약 타겟 문자열이 문자열에 존재한다면 시작 인덱스를 리턴합니다.

 

시간복잡도

스트링의 경우 시간 복잡도는 어렵고 자주 변합니다.

그래서 필요할 때마다 구글링 해보거나 직접 실험을 해보면 좋습니다.

구글에 python string find time complexity로 검색을 해보면

https://stackoverflow.com/questions/35220418/runtime-of-pythons-if-substring-in-string

O(nm)이라고 되어있습니다.

여기서 n은 큰 문자열 길이 m은 작은 문자열의 길이입니다.

index

구체적인 방법

index 메서드는 find 메서드와 비슷하지만 조금 다릅니다.
공통점과 차이점을 비교하기 위해서 find와 같은 예시를 통해 알아봅시다.

test = '123456789' 
print(test.find('34')) 
print(test.find('36'))
---> 2 
---> -1 
print(test.index('34')) 
print(test.index('3')) 
print(test.index('0')) 
---> 2 
---> 2 
---> 
ValueError: substring not found

index 메서드는 find와 동일하게 타깃 문자열을 파라메터로 받습니다.
하지만 타겟 문자열이 문자열에 존재하지 않는다면 error을 출력합니다.

 

시간복잡도

find와 같은 맥락으로 O(nm)입니다.

replace

구체적인 방법

replace( )는 문자열의 특정 문자를 변환시키고 문자열을 리턴합니다.
하지만! 기존 문자열은 변화되지 않습니다.
그럼 확인해 봅시다.

test = '1111111' 
print(test.replace('1','0')) 
print(test) 
#테스트 문자열에서 1을 0으로 모두 변환시켜라! 
print(test.replace('1','0',3)) 
print(test) 
#테스트 문자열에서 1을 0으로 앞에서 부터 3개 변환시켜라! 
print(test.replace('2','0')) 
#테스트 문자열에서 2을 0으로 모두 변환시켜라!

replace(old, new, count=모두)는 old, new 문자열과 count 정수형을 파라매터로 받습니다.
old문자열을 new문자열로 바꾸고 앞에서부터 count 만큼 변화시키고 리턴합니다.
만약 count가 파라매터로 전달되지 않으면 기본적으로 모든 old문자를 new문자로 변환시킵니다.

 

시간복잡도

구체적으로는 O(old*(count + new/count))인데

count가 최악의 경우 n이기에 O(n^2)입니다.

궁금하면 아래를 참고해 주세요.

https://stackoverflow.com/questions/66163149/does-str-replace-have-a-time-complexity-on2

upper

구체적인 방법

upper메서드는 문자열에서 소문자를 모두 대문자로 변환시킨 문자열을 리턴합니다.

test = '1a1b1c1' 
print(test.upper()) 
print(test) 
---> 1A1B1C1 
---> 1a1b1c1

이런 식으로 소문자가 아닌 문자들은 변환되지 않고 고대로 있습니다.
immutable 타입 문자열인 test에 저장된 데이터는 변하지 않습니다.

 

시간복잡도

모든 성분을 탐색하며, O(n)

만약 소문자이면 대문자로 변형해야 하기 때문에, O(const)

O(n)*O(cont) = O(n)입니다.

lower

구체적인 방법

lower 메서드는 소문자로 변환시켜 줍니다.

test = '1A1B1c1' 
print(test.lower()) 
print(test) 
---> 1a1b1c1 
---> 1A1B1c1

시간복잡도

upper와 같은 맥랑으로 O(n)입니다.

split, join

구체적인 방법

split는 문자열을 특정 기준으로 나누고 리스트에 저장 후 리턴합니다.
join의 경우 문자열의 메서드이며 sequence를 파라 매터로 받고 sequence의 성분과 문자열을 합친 결과를 리턴합니다.

test = "1<>2<>3<>4<>5" 
arr = test.split("<>")
print(arr) 
---> ['1', '2', '3', '4', '5'] 
print(",".join(arr)) 
print(''.join(arr))
---> 1,2,3,4,5 ---> 12345


이름 arr에 '<>'를 기준으로 나눈 리스트 저장합니다.
', '. join(arr)은 성분에 ", "를 추가하여 합한 문자열을 리턴합니다.
그래서 이번 예제에서는 1,2,3,4,5가 출력되는 것을 볼 수 있습니다.
', '. join(arr)의 경우 sequence의 성분과 빈 문자열이 합해져 출력이 됩니다.
출력된 결과로 12345가 되는 것을 확인할 수 있습니다.

 

시간복잡도

만약 test.split(파라 매터)의 파라매터가 길이가 1인 문자열이라면.

test를 순회하면서, O(n)

파라매터와 같은지 확인, O(const)

그래서 대략적으로 O(n)입니다.

대부분의 문제에서 파라매터가 긴 경우가 없기 때문에 O(n) 정도라고 생각합시다.

 

2.8 eval

문자열 숫자 처럼 계산해주는 좋은 것!
https://docs.python.org/ko/3/library/stdtypes.html#string-methods

 

문자열 문제 풀어보기

출처: 프로그래머스 코딩 테스트 연습, https://programmers.co.kr/learn/challenges

 

코딩테스트 연습

기초부터 차근차근, 직접 코드를 작성해 보세요.

programmers.co.kr

프로그래머스의 1단계 문제 중 문자열 문제를 풀어봅시다.
1단계의 경우 기본 함수와 프로그래밍에 익숙해지는 단계입니다.
그래서 초급 단계라면 귀찮더라도 많은 문제를 접해보고 구현해 보는 것이 좋습니다.
문제를 풀 때는 최대한 기본 함수를 이용하여 문제 풀이를 시도하는 것이 좋습니다.
문제를 다 풀고 정답을 제출하면 다른 사람들이 구현한 코드를 볼 수 있습니다.
프로그래밍은 협업하여 프로젝트를 진행하는 일이 많은데,
다른 사람들이 구현한 코드를 이해하려고 노력하고 분석하다 보면,
가독성이 좋은 코드를 구현할 수 있게 됩니다.
그러니 문제의 정답을 제출하였더라도 꼭 다른 사람의 풀이를 봐야 합니다.

3.1 핸드폰 번호 가리기

https://programmers.co.kr/learn/courses/30/lessons/12948

 

코딩테스트 연습 - 핸드폰 번호 가리기

프로그래머스 모바일은 개인정보 보호를 위해 고지서를 보낼 때 고객들의 전화번호의 일부를 가립니다. 전화번호가 문자열 phone_number로 주어졌을 때, 전화번호의 뒷 4자리를 제외한 나머지 숫자

programmers.co.kr

인풋으로 문자열 데이터 타입의 핸드폰 번호가 들어옵니다.
우리는 인풋 데이터를 뒷 번호 4자리를 제외하고 "*"로 변환하고 리턴해야 합니다.

그러면 우리는 어떻게 해야 할까요?
풀이 방법을 생각해 봅시다.

1.필요한 "*"의 수를 계산하고 저장한다.
  a = 필요한 "*" 수
  *, a 그리고 인풋 데이터를 이용하여 제출한다.

2.for 루프를 돈다. enumerate를 사용하여 인덱스와 값을 같이 가져온다.
  if 인풋 스트링의 길이 - 인덱스가 1,2,3,4 이면.
  값을 정답에 더한다.
  else
  *을 정답에 더한다.
  
3.결과를 제출한다.


이처럼 코드로 구현하기 전에 어떻게 작성할지 스케치하는 것을 수도 코드라고 합니다.
수도라는 단어에 거짓말이라는 뜻이 있어 저는 구라 코드라고 부르기도 합니다.
어찌 되었건 간에 코딩하기 전 구라코드를 작성하는 일은 필수입니다.
구라코드를 작성하게 되면 어렵고 복잡한 문제를 만나더라도 뇌정지 없이 해결할 수 있습니다.

이번 문제의 경우 저는 3가지의 구라코드가 떠올랐습니다.
모두 정답을 제출할 수 있지만.
가독성과 효율성을 생각한다면 파이썬 기본 함수 및 연산을 사용하는 것이 좋습니다.
그래서 1번을 선택하고 이를 코드로 구현해 봅시다.

def solution(phone_number): 
    lng = len(phone_number)-4 
    return "*"*lng+phone_number[-4:]

파이썬에서 튜플

파이썬에서 튜플은 immutable sequanse data type입니다.
문자열과 마찬가지로 immutable 타입인 튜플은 오브젝트를 변경이 불가능을 의미합니다.

또한 시퀀스 타입이기에 sequanse 타입에 기본적으로 제공되는 4가지 기능인,

in, len, 슬라이싱, 루프를 사용할 수 있습니다.

튜플 생성

튜플은  괄호와( )와 쉼표,를  이용하여 생성할 수 있습니다.

a = 1,2

print(a)

b = (3, 4)

print(b)


튜플 안의 성분은 정수형이 아닌 다른 데이터 타입도 사용이 가능합니다.

 d = ("a" ,"b" ,"c" , 'D', 1312313213 )

문제 풀이에 잘 사용되지는 않지만 성분이 없는 튜플을 생성할 수 있습니다.

empty = ()

기본 연산

다른 시퀀스 타입과 동일하게 튜플에는 +, *가 있습니다.

더하기의 경우 이와 같이 두 문자열을 더할 수 있습니다.

a = (1)
b = a + (2) 

print(b) 

---> (1, 2)

곱하기는 리스트를 반복하고 싶을 때 사용합니다.

c= b*2 

print(c) ---> (1,2,1,2)


출력되는 결과를 보면 리스트 b의 성분이 2번 반복되는 것을 볼 수 있습니다.

튜플 인덱스, 대입

튜플은 시퀀스 데이터 타입이기에 인덱싱 기능을 가지고 있습니다.

하지만 변경이 불가능한 immutable타입 이기에.

수정이 불가능합니다.

lt = (1,2,3,4,5) 
print(lt[0]) 
print(lt[1])

만약 인덱스를 초과하여 접근하게 되면 에러가 생성됩니다.
프로그래머스에서 인덱스를 초과하는 경우 런타임 에러가 발생합니다.

print(lt[7]) -> 에러

다른 시퀀스와 마찬가지로 음수 인덱스를 사용할 수 있습니다.

마지막 인덱스가 -1이고 앞으로 갈수록 -1씩 감소하여.
첫 번째 성분의 경우 인덱스가 -7이 됩니다.

lt[-1] ---> 5

또한 시퀀스의 기본 기능인 len을 이용하면 튜플의 길이를 가져올 수 있습니다.

len(lt)

 

양수 인덱스와 음수 인덱스 그리고 길이는 

음수 인덱스 - 튜플의 길이 = 양수 인덱스

관계를 가지고 있습니다.

이 관계를 알아두면 유용하니 기억해 둡시다.

 

튜플 슬라이싱

시퀀스 타입의 또 다른 특징인 슬라이싱에 대해서 알아봅시다

l = (1,2,3,4,5,6)

슬라이싱은 :를 이용하여 구현합니다.
구체적으로
튜플[시작 인덱스 : 마지막 인덱스]
이와 같이 표현됩니다.
이를 적용해 봅시다.

print(l[0:3]) 
--> (1,2,3)


출력된 결과를 보면 0번 인덱스부터 2번 인덱스까지 출력된 것을 볼 수 있습니다.
이처럼 슬라이싱의 시작 인덱스는 포함되고 마지막 인덱스는 포함되지 않습니다.

언팩킹

파이썬에서 반복 가능한 오브잭트들은 언팩킹 기능을 사용할수 있습니다.

특히 언팩킹 기능은 튜플 데이터 타입에 많이 이용됩니다.

왜 그런지 알아봅시다.

 

아래에 정의된 test함수는 실행이 되면 "a" , "b",  1을 리턴하는 함수입니다.

def test():
    return "a", "b", 1

print(type(test()))

리턴이 되는 값의 타입을 확인해 보면 튜플인 것을 알 수 있습니다.

튜플을 정의할 때 ( )와 ,를 사용하여 정의할 수도 있었지만.

( )를 생략하고 정의할 수 있었기에.

결괏값이 튜플인 것은 당연합니다.

 

만약에 결괏값은 이름 a, b에 저장하여 사용하고자 한다고 가정을 해봅시다.

그러기 위해서는 결괏값을 dummy 이름에 저장하고 이를 인덱싱 기능을 이용하여 나누어야 합니다.

def test():
    return "a", "b", 1

dummy = test()

a = dummy[1]
b = dummy[2]

하지만 언팩킹을 이용하면 아래와 같이 간단하게 작성하면 됩니다.

def test():
    return "a", "b", 1

a, b, _ = test()
print(b)

---> b

a, *b = test()

print(b)

--->[b, 1]

여기서 _는 의미 없는 것을 제외할 때 사용하는 문법입니다.

아래의 *는 *위치부터 뒤로의 모든 데이터를 리스트에 저장하는 문법입니다.

 

이와 같이 언팩킹을 함수의 리턴값을 정리할 때 좋을 뿐만 아니라.

이름의 변수를 바꿀 때도 유용합니다.

a = 1
b = 2

a, b = b ,a
print(a,b)

---> 2, 1

먼저 b, a를 이용하여 튜플을 만들고 이를 a, b순으로 언팩킹하면.

a와 b의 값이 바뀌는 것을 확인할 수 있습니다.

루프

파이썬에서 문자열과 같은 sequence타입에 루프로 접근하는 방법은
대략적으로 3가지가 존재합니다.

여기서 range() 함수는 sequence 타입의 데이터를 만드는 함수입니다.
예를 보면 range() 안에 len()이 보입니다.
len()은 sequence의 길이를 가져오는 함수입니다.
이번의 경우 len(l)가 4입니다.
그래서 range(4)는 0,1,2,3을 리턴합니다.
이를 이용하면 test의 성분을 순차적으로 접근할 수 있습니다.

l = (1,2,3,4) 

for idx in range(len(test)): print(idx, test[idx])

range() 함수는
시작 인덱스, 끝 인덱스 그리고 스텝의 크기를 파라 매터로 받습니다.
그리고 시작인 덱스부터 끝 인덱스 전까지 스텝을 밟으며 값을 리턴합니다.
만약 시작 인덱스를 파라매터로 전달하지 않는다면 디폴트로 0이 설정됩니다.
보폭 또한 디폴트로 1이 설정되어 있습니다.
그래서 예제의 range(4)는 range(0,4,1)과 같아 0,1,2,3을 순차적으로 리턴합니다.

range(start=0, end, step=1)

리스트도 sequence이기 때문에 for 루프를 이용하여 순차적으로 접근할 수 있습니다.
출력해 보면 1부터 5까지 순차적으로 출력되는 것을 확인할 수 있습니다.

for char in test: print(char)

range()와 비슷한 enumerate()를 이용하면 index와 문자를 같이 받아 올 수 있습니다.
enumerate()는 sequence를 파라매터로 받고 파라매터로 받은 sequence의 성분과
인덱스를 순차적으로 리턴합니다.

for idx, char in enumerate(test): print(idx, char)

https://docs.python.org/ko/3/tutorial/introduction.html#lists

튜플 메서드

count

구체적인 방법

카운트 함수를 이용하면 성분의 수를 얻을 수 있습니다.

test = 1,1,1,1,2,3,4,5,6
print(t.count(1))

---> 4

시간복잡도

모든 성분을 확인해야 합니다.

그래서 O(n)입니다.

index 

구체적인 방법

인덱스 함수는 성분의 위치를 리턴합니다.

test = 1,1,1,1,2,3,4,5,6
print(t.index(6))

---> 8

시간복잡도

모든 성분이 나올떄 까지 확은을 해야합니다.

그래서 O(k)입니다.

문제 풀기

튜플의 경우 메인으로 사용되는 문제가 별로 없습니다.

주로 데이터를 정리할 때 자주 사용됩니다.


이전글 - 처음 시작하는 알고리즘
다음글 - 파이썬 컬렉션 자료구조

Comments