Posts python algorithm 1
Post
Cancel

python algorithm 1

Python



01. Python



001. 1부터 10까지의 합

1
2
3
4
5
6
7
8
9
10
# 예시 1
sum = 0
for i in range(1, 10+1):
  sum += i
  
# 예시 2
sum = sum(i for i in range(1, 10+1))

# 예시 3
sum = sum(range(1, 10+1))


002. 제네릭 프로그래밍

  • 제네릭generic이란 파라미터의 타입이 나중에 지정to-be-specified-later되게 해서 재활용성을 높일 수 있는 프로그래밍 스타일.
  • 파이썬은 원래 동적 타이핑Dynamic Tying언어이기 때문에 제네릭이 필요 없음.
  • 하지만 동적 타이핑의 장점이자 단점은 얼핏 사용하기엔 매우 편리하지만 코드의 복잡도가 높아질 수록 혼란을 가중시킴.
  • 타입을 아예 명시하지 않으면 가독성을 낮추고 버그 발생 확률이 높아짐.
1
2
3
4
5
6
7
8
9
10
# 타입 명시
from typing import TypeVar

T = TypeVar('T')
U = TypeVar('U')

def are_equar(a: T, b: U) -> bool:
  return a == b
  
are_equal(10, 10.0)


003. 배열 반복

1
2
3
foo = ['A', 'B', 'C']
for f in foo:
  print(f)


004. 구조체

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Python
from collections import namedtuple

MyStruct = namedtuple("MyStruct", "field1 field2 field3")

m = MyStruct("foo", "bar", "baz")

# Python 3.7+
from dataclasses import dataclass

@dataclass
class Product:
  weight: int = None
  price: float = None

apple = Product()
apple.price = 10


005. 파이썬의 대표적인 특징이기도 한 인덴트indent는 공식 가이드인 PEP 8에 따라 공백 4칸을 원칙으로 함.

006. 파이썬의 변수명 네이밍 컨벤션Naming Convention은 자바와 달리 각 단어를 밑줄(_)로 구분하여 표기하는 스네이크 케이스Snake Case를 따름.

007. 리스트 컴프리헨션List Comprehension

  • 파이썬은 map, filter와 같은 함수형Funtional 기능을 지원하며 다음과 같은 람다 표현식Lamda Expression도 지원함.
1
2
>>> list(map(lambda x: x + 10, [1, 2, 3]))
[11, 12, 13]
  • 리스트 컴프리헨션이란 기존 리스트를 기반으로 새로운 리스트를 만들어 내는 구문.
1
2
>>> [n * 2 for n in range(1, 10+1) if n % 2 == 1]
[2, 6, 10, 14, 18]
  • 버전 2.7 이후에는 리스트 외에도 딕셔너리 등이 가능하도록 추가.
1
2
3
4
5
a = {}
for key, value in original.items():
  a[key] = value
  
a = {key: value for key, value in original.items()}


008. 제너레이터Generator

  • 제너레이터는 루트의 반복Iteration 동작을 제어할 수 있는 루틴 형태.
    • 예를 들어 임의의 조건으로 숫자 1억 개를 만들어내 계산하는 프로그램을 작성한다고 가정.
    • 이 경우 제너레이터가 없다면 메모리 어딘가에 만들어낸 숫자 1억 개를 보관하고 있어야 함.
    • 그러나 제러네이터를 이용하면, 단순히 제러레이터만 생성해두고 필요할 때 언제든 숫자를 만들어낼 수 있음.
    • 만약에 1억 개 중 100개 정도만 쓰인다면 차이는 더욱 클 것.
  • yield 구문을 사용하면 제너레이터를 리턴할 수 있음.
    • 기존의 함수는 return 구문을 맞닥뜨리면 값을 리턴하고 모든 함수의 동작을 종료.
    • 그러나 yield는 제너레이터가 여기까지 실행 중이던 값을 내보낸다는(‘양보하다’)의미로, 중간값을 리턴한 다음 함수는 종료되지 않고 계속해서 맨 끝에 도달할 때까지 실행됨.
    • 물론 다음 코드의 경우 처럼 while True 구문은 종료 조건이 없으므로 계속해서 값을 내보낼 수 있음.
1
2
3
4
5
6
7
8
>>> def get_natural_number():
      n = 0
      while True:
        n += 1
        yield n
        
>>> get_natural_number()
<generator object get_natural_number at 0x10d3139d0>
  • 다음 값을 생성하려면 next()로 추출하면 됨.
    • 예를 들어 100개의 값을 생성하고 싶다면 다음과 같이 100번 동안 next()를 수행하면 됨.
1
2
3
4
5
6
7
8
9
10
11
>>> g = get_natural_number()
>>> for _ in range(0, 100):
      print(next(g)

1
2
3
...
98
99
100
  • 제너레이터는 여러 타입의 값을 하나의 함수에서 생성하는 것도 가능.
1
2
3
4
5
6
7
8
9
10
11
>>> def generator():
      yield 1
      yield 'string'
      yield True
>>> g = generator()
>>> next(g)
1
>>> next(g)
'string'
>>> next(g)
True


009. range()

  • 제너레이터의 방식을 활용하는 대표적인 함수.
1
2
3
4
5
6
7
8
>>> list(range(5))
[0, 1, 2, 3, 4]
>>> range(5)
range(0, 5)
>>> type(range(5))
<class 'range'>
>>> for i in range(5): print(1, end=' ')
0 1 2 3 4
  • range()는 range 클래스를 리턴하며, for 문에서 사용할 경우 내부적으로는 제너레이터의 next()를 호출하듯 매번 다음 숫자를 생성.
    • 파이썬 2.x 버전까지는 range() 함수가 지금과 같은 형태가 아니었음.
    • 숫자를 미리 생성해서 리스트로 리턴하는 방식이었고(파이썬 3.x의 list(range(x)) 결과와 동일) 제너레이터를 리턴하는 방식은 xrange()라고 따로 존재.
    • 그러다가 버전 3 이후, range() 함수가 제너레이터 역할을 하는 range 클래스를 리턴하는 형태로 변경됐고 xrange() 함수는 사라짐.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 숫자 100만 개를 생성하는 2가지 방법
>>> a = [n for n in range(1000000)]
>>> b = range(1000000)

# len()으로 길이 비교를 해보면 둘 다 동일한 100만 개가 출력되며, 비교 연산자에서도 True를 리턴
>>> len(a)
1000000
>>> len(b)
1000000
>>> len(a) == len(b)
True

# 그러나 a에는 이미 생성된 값이 담겨 있고, b는 생성해야 한다는 조건만 존재
>>> b
range(0, 1000000)
>>> type(b)
<class 'range'>

# 둘 사이의 메모리 점유율 비교
>>> sys.getsizeof(a)
8697494
>>> sys.getsizeof(b)
48

# 100만 개가 아니라 1억 개라도 b 변수의 메모리 점유율은 동일. 생성 조건만 보관하고 있기 때문.
# 게다가 미리 생성하지 않은 값은 인덱스에 접근이 안될 거라 생각할 수 있으나, 
# 인덱스로 접근 시에는 바로 생성하도록 구현되어 있기 때문에 리스트와 거의 동일한 느낌으로 불편 없이 사용할 수 있음.
>>> b[999]
999


010. enumerate

  • enumerate()는 ‘열거하다’는 뜻의 함수로, 순서가 있는 자료형(list, set, tuple 등)을 인덱스를 포함한 enumerate 객체로 리턴.
1
2
3
4
5
6
7
 >>> a = [1, 2, 3, 2, 45, 2, 5]
 >>> a
 [1, 2, 3, 2, 45, 2, 5]
 >>> enumerate(a)
 <enumerate object at 0x1010f83f0>
 >>> list(enumerate(a))
 [(0, 1), (1, 2), (2, 3), (3, 2), (4, 45), (5, 2), (6, 5)]


011. //나눗셈 연산자

  • 정수형을 나눗셈할 때 동일한 정수형을 결과로 리턴하면서 내림Floor Division 연산자의 역할. 몫Quotient을 구하는 연산자.
  • 5 // 3 은 int(5 / 3)과 동일.
  • 나머지Remainder를 구하는 모듈로modulo 연산자는 %.
  • 몫과 나머지를 동시에 구하려면 divmod() 함수를 사용하면 됨.
1
2
>>> divmod(5, 3)
(1, 2)


012. print

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> print('A1', 'B2')
A1 B2
 
>>> print('A1', 'B2', sep=',')
A1,B2
 
>>> print('aa', end=' ');print('bb')
aa bb
 
>>> a = ['A', 'B']
>>> print(' '.join(a))
A B
 
>>> idx = 1
>>> fruit = "Apple"
>>> print(f'{idx + 1}: {fruit}')
2: Apple


013. locals

  • locals()는 로컬 심볼 테이블 딕셔너리를 가져오는 메소드로 업데이트 또한 가능.
  • 로컬에 선언된 모든 변수를 조회할 수 있는 강력한 명령이므로 디버깅에 많은 도움이 됨.
  • 특히 로컬 스코프에 제한해 정보를 조회할 수 있기 때문에 클래스의 특정 메소드 내부에서나 함수 내부의 로컬 정보를 조회해 잘못 선언한 부분이 없는지 확인하는 용도로 활용할 수 있음.
  • 변수명을 일일이 찾아낼 필요 없이 로컬 스코프에 정의된 모든 변수를 출력하기 때문에 편리.
1
2
import pprint # 보기 좋게 줄바꿈 처리를 해주어 가독성 높음
pprint.pprint(locals())






02. Big-O, Data Type



014. 빅오(O, big-O)란 입력값이 무한대로 향할때 함수의 상한을 설명하는 수학적 표기 방법.

  • 빅오는 점근적 실행 시간Asymtotic Running Time를 표기할 때 가장 널리 쓰이는 수학적 표기법.
  • 점근적 실행 시간이란 입력값 n이 커질 때, 즉 입력값이 무한대를 향할 때 함수의 실행 시간의 추이를 의미.
  • 점근적 실행 시간은 달리 말하면 시간 복잡도라 할 수 있음.
  • 시간 복잡도Time Complexity의 사전적 정의는 어떤 알고리즘을 수행하는 데 걸리는 시간을 설명하는 계산 복잡도Computational Complexity를 의미하며, 계산 복잡도를 표기하는 대표적인 방법이 빅오.
  • 빅오로 시간 복잡도를 표현할 때는 최고차항만을 표기하며, 상수항은 무시. 입력값에 따른 알고리즘의 실행 시간의 추이만을 살피게 됨.
    • O(1)
      • 입력값이 아무리 커도 실행 시간은 일정.
      • 최고의 알고리즘이라 할 수 있음.
      • 상수 시간에 실행된다 해도 상수값이 상상을 넘어설 정도로 매우 크다면 사실상 일정한 시간의 의미 없음.
      • 최고의 알고리즘이 될 수 있지만 그만큼 신중해야 함.
      • ex) 해시 테이블의 조회 및 삽입
    • O(logN)
      • 여기서부터 실행 시간은 입력값에 영향을 받음.
      • 그러나 로그는 매우 큰 입력값에도 크게 영향을 받지 않는 편이므로 왠만한 n의 크기에 대해서도 매우 견고.
      • ex) 이진 검색
    • O(N)
      • 입력값만큼 실행 시간에 영향을 받으며, 알고리즘을 수행하는 데 걸리는 시간은 입력값에 비례.
      • 선형 시간Linear-Time 알고리즘.
      • ex) 정렬되지 않은 리스트에서 최댓값 또는 최솟값 -> 이 값을 찾기 위해서는 모든 입력값을 적어도 한 번 이상 살펴봐야 함.
    • O(NlogN)
      • 병합 정렬을 비롯한 대부분의 효율 좋은 정렬 알고리즘이 이에 해당.
    • O(N^2)
      • 버블 정렬 같은 비요율적인 정렬 알고리즘이 이에 해당.
    • O(2^N)
      • 피보나치 수를 재귀로 계산하는 알고리즘이 이에 해당.
      • 간혹 N^2과 혼동하는 경우가 있는데 처음에는 비슷해 보이지만 2^N이 훨씬 더 큼.
    • O(N!)
      • 각 도시를 방문하고 돌아오는 가장 짧은 경로를 찾은 외판원 문제Travelling Salesman Problem(TSP)를 브루트 포스로 풀이할 때.
      • 가장 느린 알고리즘으로, 입력값이 조금만 커져도 왠만한 다항 시간 내에는 계산 어려움.
  • 빅오는 시간 복잡도 외에도 공간 복잡도를 표현하는 데에도 널리 쓰임.
  • 알고리즘은 흔히 ‘시간과 공간이 트레이드오프Space-Time Tradeoff’관계.
    • 실행 시간이 빠른 알고리즘은 공간을 많이 사용하고, 공간을 적게 차지하는 알고리즘은 실행 시간이 느림.
  • 빅오(O)는 상한Upper Bound를 의미.
  • 상한을 최악의 경우와 혼동하는 데, 빅오 표기법은 정확하게 쓰기에는 너무 길고 복잡한 함수를 ‘적당히 정확하게’ 표현하는 방법일 뿐 최악의 경우/평균적인 경우의 시간 복잡도와는 아무런 관계 없는 개념.
  • 빅오 표기법은 주어진(최선/최악/평균) 경우의 수행 시간의 상한을 나타냄.
  • 분할 상환 분석: 최악의 경우를 여러 번에 걸쳐 골고루 나눠주는 형태로 알고리즘의 시간 복잡도 계산.
  • 병렬화: GPU 각각의 코어는 CPU보다 훨씬 더 느리지만 GPU의 코어는 수천여 개로 구성되어있어, 많아 봐야 수십여 개에 불과한 CPU보다 수백 배 더 많은 연산을 동시에 수행할 수 있음.
    • CPU가 비행기로 짐을 여러 번 나르는 것이라면, GPU는 배로 수많은 짐을 한 번에 나르는 것.


015. 자료형

img

  • 파이썬은 모든 것이 객체.
  • 파이썬에서 변수를 할당하는 작업은 해당 객체에 대한 참조를 한다는 의미.
  • 파이썬 자료형의 불변 객체 여부
클래스설명불변 객체
bool부울o
int정수o
float실수o
list리스트x
tuple리스트와 튜블의 차이는 불변 여부이며 이외에는 거의 동일
튜플은 불변이므로 생성할 때 설정한 값 변경할 수 없음
o
str문자o
set중복된 값을 갖지 않는 집합 자료형x
dict딕셔너리x
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 불변 객체
>>> 10
>>> a = 10
>>> b = a
>>> id(10), id(a), id(b)
(4393858752, 4393858752, 4393858752)
# 만약 10이 11이 된다면 a 변수와 b 변수 모두 값이 11로 바뀌게 될 것. 
# 그러나 그런 일은 벌어지지 않음.
# 숫자와 문자는 모두 불변 객체이기 때문.

>>> b = 7
# b에 새로운 값을 할당하게 되면 더 이상 b 변수는 a 변수를 참조하기 않음.
# 이제 b 변수는 7이라는 새로운 객체를 참조.
# 따라서 b는 다른 ID가 되며, a 변수의 값은 기존 10에서 변경되지 않고 그대로 유지.
# 숫자나 문자와 같은 불변 객체는 값의 변경이 불가능하기 때문에 이처럼 참조 변수에서 값을 조작할 수 있는 방법이 없음.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 가변 객체
>>> a = [1, 2, 3, 4, 5]
>>> b = a
>>> b
[1, 2, 3, 4, 5]
>>> a[2] = 4
>>> a
[1, 2, 4, 4, 5]
>>> b
[1, 2, 4, 4, 5]

>> b[0] = 5
>>> a
[5, 2, 4, 4, 5]
# 리스트와 같은 가변 객체를 참조하고 있다면 마찬가지로 참조 변수에서 값을 조작하는 경우 참조 대상의 변수도 변경할 수 있게 됨.
# 다만, 이 경우에도 = 연산자로 재할당을 하게 되면 다른 ID가 되므로 주의 필요.


016. is와 ==

  • is는 id() 값을 비교하는 함수.
    • None은 널(null)로서 값 자체가 정의되어 있지 않으므로 ==로 비교 불가능. is로만 비교 가능.
  • ==는 값을 비교하는 연산자.


017. 자료구조(Data Structure), 자료형(Data Type)

  • 자료구조: 데이터에 효율적으로 접근하고 조작하기 위한 데이터의 조직, 관리, 저장 구조.
  • 자료형: 컴파일러 또는 인터프리터에게 프로그래머가 데이터를 어떻게 사용하는지 알려주는 일종의 데이터 속성(Attribute).
  • 자료형은 자료구조에 비해 훨씬 더 구체적이며, 특정 언어에서 자료형이라 함은 정수, 실수, 문자열 등 해당 언어에서 지원하는 원시 자료형까지 포함하는 모든 자료의 유형을 말함.
  • 자료구조는 일반적으로 원시 자료형을 기반으로 하는 배열, 연결 리스트, 객체(Object) 등을 말하며, 자료형의 관점에서 보자면 여러 원시 자료형을 조합한 자료구조는 복합 자료형(Composite Data Type)이 됨.






03. List, Dictionary

018. 리스트List는 순서대로 저장하는 시퀀즈이자 변경 가능한 목록Mutable List. 입력 순서가 유지되며, 내부적으로는 동적 배열로 구현.

019. 리스트의 주요 연산 시간 복잡도

연산시간 복잡도설명
len(a)O(1)전체 요소의 개수 리턴
a[i]O(1)인덱스 i의 요소를 가져옴
a[i:j]O(k)i부터 j까지 슬라이스의 길이만큼 k개의 요소를 가져옴
이 경우 객체 k개에 대한 조회가 필요함
elem in aO(n)elem요소가 존재하는지 확인
처음부터 순차 탐색하므로 n만큼의 시간 소요
a.count(elem)O(n)elem의 개수를 리턴
a.index(elem)O(n)elem요소의 인덱스를 리턴
a.append(elem)O(1)리스트의 마지막에 elem요소를 추가
a.pop()O(1)리스트 마지막 요소를 추출
스택의 연산
a.pop(0)O(n)리스트의 첫번째 요소를 추출
큐의 연산
이 경우 전체 복사가 필요하므로 O(n)
큐의 연산을 주로 사용한다면 리스트보다는 O(1)에 가능한 데크(deque)를 권장
del a[i]O(n)i에 따라 다름
최악의 경우 O(n)
a.sort()O(nlogn)정렬
팀소트Timsort를 사용하며, 최선의 경우 O(n)에도 실행될 수 있음
min(a), max(a)O(n)최솟값/최댓값을 계산하기 위해서는 전체를 선형 탐색해야 함
a.reverse()O(n)뒤집음
리스트는 입력 순서가 유지되므로 뒤집게 되면 입력 순서가 반대로 됨


020. 리스트의 활용 방법

  • insert(): 특정 위치에 인덱스를 지정해 요소를 추가할 수 있음.
1
2
3
4
5
# 3번째 인덱스에 5를 삽입
>>> a = [1, 2, 3, 4]
>>> a.insert(3, 5)
>>> a
[1, 2, 3, 5, 6]
  • 슬라이싱slicing
1
2
3
4
5
6
7
8
9
10
# 인덱스 1, 2, 3의 값
>>> a[1:4]
[2, 3, 5]
# 인덱스 1, 3의 값
>>> a[1:4:2]
[2, 5]

# 원래 [1:4]는 인덱스 1, 2, 3의 값을 가져오지만 이처럼 세 번째 파라미터를 부여하면 단계Step의 의미로,
# [1:4:2]와 같이 단계를 2로 지정할 경우 두 칸씩 건너뛰게 됨.
# 즉 인덱스 1부터 시작해 1에서 두 칸 건너뛴 3까지의 값을 가져오게 됨.
  • del(): 인덱스의 위치에 있는 요소를 삭제
  • remove(): 값에 해당하는 요소를 삭제


021. 파이썬은 모든 것이 객체며, 파이썬의 리스트는 이들 객체에 대한 포인토 목록을 관리하는 형태로 구현되어 있음. 사실상 연결 리스트에 대한 포인터 목록을 배열 형태로 관리하고 있으며, 그 덕분에 파이썬의 리스트는 배열과 연결 리스트를 함친 듯이 강력한 기능 자랑.

022. dict(): 딕셔너리

  • 키/값 구조로 이루어짐.
  • 파이썬 3.7+에서는 입력 순서가 유지되며, 내부적으로는 해시 테이블Hash Table로 구현되어 있음.
  • 파이썬의 딕셔너리는 해시할 수 있다면 숫자뿐만 아니라, 불변 객체를 모두 키로 사용할 수 있음.


023. 딕셔너리의 주요 연산 시간 복잡도

연산시간 복잡도설명
len(a)O(1)요소의 개수를 리턴
a[key]O(1)키를 조회하여 값을 리턴
a[key] = valueO(1)키/값을 삽입
key in aO(1)딕셔너리에 키가 존재하는지 확인
  • 해시 테이블은 최악의 경우 O(n)이 될 수 있으나 대부분의 경우 훨씬 더 빨리 실행되며, 분활 상환 분석Amortized Analysis에 따른 시간 복잡도는 O(1)임.


024. 딕셔너리 활용 방법

  • 선언
1
2
>>> a = dict()
>>> a = {}
  • key1, key2는 초기값으로 지정해 선언하거나 key3처럼 나중에 별도로 선언하여 value3라는 값을 할당할 수 있음.
1
2
3
4
5
6
>>> a = {'key1':'value1', 'key2':'value2'}
>>> a
{'key1': 'value1', 'key2':'value2'}
>>> a['key3'] = 'value3'
>>> a
{'key1': 'value1', 'key2':'value2', 'key3':'value3'}
  • 딕셔너리의 키를 지정하면 값을 조회할 수 있음. 존재하지 않은 키를 조회할 경우 에러 발생.
1
2
3
4
5
6
>>> a['key1']
'value1'
>>> a['key4']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'key4' # del할 때도 발생
  • try 구문으로 예회 처리를 하게 되면, 존재하지 않는 키의 경우 별도 작업을 할 수 있음. 또한 키가 존재하는지 미리 확인해 이후 작업 진행할 수도 있음.
1
2
3
4
5
6
7
8
>>> 'key4' in a
False
>>> if 'key4' in a:
      print('존재하는 키')
    else
      print('존재하지 않는 키')

존재하지 않는 키
  • 딕셔너리에 있는 키/값은 for 반목문으로도 조회 가능. 딕셔너리의 items() 메소드를 사용하면 키와 값을 각각 꺼내올 수 있음.
1
2
3
4
5
6
>>> for k, v in a.items():
      print(k, v)

key1 value1
key2 value2
key3 value3


025. 딕셔너리 추가 모듈

  • collections.OrderedDict(): 항상 입력 순서가 유지됨.
  • collections.defaultdict(): 조회 시 항상 디폴트 값을 생성해 키 오류를 방지.
    • 존재하지 않는 키를 조회할 경우, 에러 메세지를 출력하는 대신 디폴트 값을 기준으로 해당 키에 대한 딕셔너리 아이템을 생성해줌.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    >>> a = collections.defaultdict(int)
    >>> a['A'] = 5
    >>> a['B'] = 4
    >>> a
    defaultdict(<class 'int'>, {'A': 5, 'B': 4})
    >>> a['C'] += 1
    >>> a
    defaultdict(<class 'int'>, {'A': 5, 'B': 4, 'C': 1})
    # 이 경우 디폴트인 0을 기준으로 자동으로 생성한 후 여기에 1을 더해 최종적으로 1이 만들어짐.
    
  • collections.Counter(): 요소의 값을 키로 하고 개수를 값 형태로 만들어 카운팅.

    1
    2
    3
    4
    5
    6
    
    >>> a = [1, 2, 3, 4, 5, 5, 5, 6, 6]
    >>> b = collections.Counter(a)
    >>> b
    Counter({5: 3, 6: 2, 1: 1, 2: 1, 3: 1, 4: 1})
    >>> type(b)
    <class 'collections.Counter'>
    
    • 가장 빈도 수가 높은 요소
    1
    2
    
    >>> b.most_common(2)
    [(5, 3), (6, 2)]
    






04. String Manipulation



026. 유효한 팰린드롬Palindrome(앞뒤가 똑같은 단어나 문장으로, 뒤집어도 같은 말이 되는 단어 또는 문장)

주어진 문자열이 팰린드롬인지 확인하라. 대소문자를 구분하지 않으며, 영문자와 숫자만을 대상으로 한다.

  • 슬라이싱 사용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import re

input1 = "A man, a plan, a canal: Panama" # True
input2 = "race a car" # False

def is_palindrome(s):
  s = s.lower()
  # 정규식으로 불필요한 문자 필터링
  s = re.sub('[^a-z0-9]', '', s)

  return s == s[::-1] # 슬라이싱

print(is_palindrome(input1))
print(is_palindrome(input2))
  • 대부분의 문자열 작업은 슬라이싱으로 처리하는 편이 가장 빠름.
    • 슬라이싱을 기준으로 한 파이썬 문자열 처리 실행 시간

      알고리즘실행 시간슬라이싱을 1로 했을 때의 비율
      슬라이싱0.499마이크로초1
      리스트 reverse()2.46마이크로초5
      reversed() + join()2.49마이크로초6
      for 반복5.5마이크로초12
      while 반복9.4마이크로초21
      재귀24.3마이크로초54
    • S = ‘안녕하세요’ 문자열을 통한 슬라이싱의 활용 방법

      • S[:] == 안녕하세요 : 둘 다 생략하면 사본을 리턴. 참조가 아닌 값을 복사하기 위해 사용.
      • S[::1] == 안녕하세요 : 1은 기본값으로 동일.
      • S[::-1] == 요세하녕안 : 뒤집는다.
      • S[::2] == 안하요 : 2칸씩 앞으로 이동.


027. 문자열 뒤집기

문자열을 뒤집은 함수를 작성. 입력값은 문자 배열. 리턴 없이 리스트 내부를 직접 조작.

  • 파이썬다운 방식
1
2
3
4
5
6
7
input = ["h", "e", "l", "l", "o"]

def reverse_string(s):
  s.reverse() # reverse()는 리스트에만 제공됨. 만약 입력값이 문자열이라면 문자 슬라이싱 사용. 슬라이싱은 리스트에도 사용할 수 있음. s[:] = s[::-1]

reverse_string(input)
print(input)


028. 로그 파일 재정렬

로그를 재정렬. 기준은 다음과 같음.

  1. 로그의 가장 앞 부분은 식별자.
  2. 문자로 구성된 로그가 숫자 로그보다 앞에 옴.
  3. 식별자는 순서에 영향을 끼치지 않지만, 문자가 동일할 경우 식별자 순.
  4. 숫자 로그는 입력 순서대로 함.
  • 람다와 +연산자를 이용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
logs = ["dig1 8 1 5 1", "let1 art can", "dig2 3 6", "let2 own kit dig", "leg3 art zero"]

def reorder_logfiles(logs):
  letters, digits = [], []
  for log in logs:
    if log.split()[1].isdigit():
      digits.append(log)
    else:
      letters.append(log)
  
  # 2개의 키를 람다 표현식으로 정렬
  letters.sort(key=lambda x: (x.split()[1:], x.split()[0]))
  return letters + digits

print(reorder_logfiles(logs))
  • 람다 표현식: 식별자 없이 실행 가능한 함수. 함수 서언 없이도 하나의 식으로 함수를 단순하게 표현할 수 있음.


029. 가장 흔한 단어

금지된 단어를 제외한 가장 흔하게 등장하는 단어를 출력. 대소문자 구분하지 않고, 구두점(마침표, 쉼표 등) 또한 무시.

  • 리스트 컴프리헨션, Counter 객체 사용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import collections
import re

paragraph = "Bob hit a ball, the hit BALL flew far after it was hit."
banned = ["hit"]

def most_common_word(paragraph, banned):
  words = [word for word in re.sub(r'[^\w]', ' ', paragraph).lower().split()
                if word not in banned]
                
  counts = collections.Counter(words)
  # 가장 흔하게 등장하는 단어의 첫 번째 인덱스 리턴
  return counts.most_common(1)[0][0]

print(most_common_word(paragraph, banned)) # ball


030. 그룹 애너그램

문자열 배열을 받아 애너그램(문자를 재배열하여 다른 뜻을 가진 단어로 바꾸는 것) 단위로 그룹핑.

  • 정렬하여 딕셔너리에 추가
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import collections

strs = ["eat", "tea", "tan", "ate", "nat", "bat"]

def group_anagrams(strs):
  anagrams = collections.defaultdict(list)

  for word in strs:
    anagrams[''.join(sorted(word))].append(word)
  return anagrams.values()

print(group_anagrams(strs))
---------------------------
dict_values([['eat', 'tea', 'ate'], ['tan', 'nat'], ['bat']])
  • 여러가지 정렬 방법
    • sorted() 함수를 이용한 정렬

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      
      >>> a = [2, 5, 1, 9, 7]
      >>> sorted(a)
      [1, 2, 5, 7, 9]
          
      >>> b = 'zbdaf'
      >>> sorted(b)
      ['a', 'b', 'd', 'f', 'z']
          
      >>> b = 'zbdaf'
      >>> "".join(sorted(b))
      'abdfz'
      
      • key= 옵션을 지정해 정렬을 위한 키 또는 함수를 별도로 지정할 수 있음.

        1
        2
        3
        
        >>> c = ['ccc', 'aaaa', 'd', 'bb']
        >>> sorted(c, key=len)
        ['d', 'bb', 'ccc', 'aaaa']
        
    • 리스트 자료형은 sort() 메소드를 제공

      • 리스트 자체를 정렬(제자리 정렬In-place Sort): 입력을 출력으로 덮어쓰기 때문에 별도의 추가 공간이 필요하지 않으며, 리턴 값이 없음.
      • 정렬 결과를 별도로 리턴하는 sorted()와 다르므로 주의 필요
      1
      2
      3
      
      alist.sort() # sort()는 리스트 자체를 제자리 정렬
      alist = blist.sort() # 잘못된 구문
                           # sort()함수는 None을 리턴하므로 주의 필요
      


031. 가장 긴 팰린드롬 부분 문자열

가장 긴 팰린드롬 부분 문자열을 출력.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
s = "babad" # "bab" or "aba"

def longest_palindrome(s):
  # 팰린드롬 판별 및 투 포인터 확장
  def expand(left, right):
    while left >= 0 and right <= len(s) and s[left] == s[right - 1]:
      left -= 1
      right += 1
    return s[left + 1: right - 1]

  # 해당 사항이 없을 때 빠르게 리턴
  if len(s) < 2 or s == s[::-1]:
    return s

  result = ''
  # 슬라이딩 윈도우 우측으로 이동
  for i in range(len(s) - 1):
    result = max(result, 
                 expand(i, i + 1),
                 expand(i, i + 2),
                 key=len)
  return result

print(longest_palindrome(s))
This post is licensed under CC BY 4.0 by the author.