파이썬에서는 배열을 리스트와 튜플로 구현할 수 있다.
리스트
- 원소를 변경할 수 있는 뮤터블 list형 객체
- 연산자 [ ] 안에 원소를 쉼표(,)로 구분하여 표기하여 생성
- 원소 없이 [ ] 만 사용하면 빈 리스트를 생성
list01 = [] # [] 빈 리스트
list02 = [1, 2, 3] # [1, 2, 3]
list03 = ['A', 'B', 'C', ] # ['A', 'B', 'C'] 맨 마지막 원소에 쉼표를 써도 됨
list04 = list() # [] 빈 리스트
list05 = list('ABC') # ['A', 'B', 'C'] 문자열의 각 문자로부터 원소를 생성
list06 = list([1, 2, 3]) # [1, 2, 3] 리스트로부터 원소를 생성
list07 = list((1, 2, 3)) # [1, 2, 3] 튜플로부터 원소를 생성
list08 = list({1, 2, 3}) # [1, 2, 3] 집합으로부터 원소를 생성
list09 = list(range(7)) # [0, 1, 2, 3, 4, 5, 6]
list10 = list(range(3, 8)) # [3, 4, 5, 6, 7]
list11 = list(range(3, 13, 2)) # [3, 5, 7, 9, 11]
list12 = [None] * 5 # [None, None, None, None, None]
튜플
- 원소에 순서를 매겨 결합한 것으로 원소를 변경할 수 없는 이뮤터블 자료형
- 원소를 쉼표(,)로 구분하여 나열한 뒤 결합 연산자 ( )로 둘러싸는 방식으로 생성
- 리스트와 마찬가지로 맨 마지막 원소 뒤에 쉼표를 써도 되며, ( )만 사용하면 빈 튜플을 생성
- 리스트와 다르게 결합 연산자 ( ) 를 생략할 수 있음
- 아래 tuple02, tuple03처럼 원소가 1개인 경우 반드시 원소 뒤에 쉼표를 입력해야 함(쉼표가 없으면 변수로 인식)
tuple01 = () # ( ) 빈 튜플
tuple02 = 1, # (1,)
tuple03 = (1,) # (1,)
tuple04 = 1, 2, 3 # (1, 2, 3)
tuple05 = 1, 2, 3, # (1, 2, 3)
tuple06 = (1, 2, 3) # (1, 2, 3)
tuple07 = (1, 2, 3, ) # (1, 2, 3)
tuple08 = 'A', 'B', 'C', # ('A', 'B', 'C')
tuple09 = tuple() # ( ) 빈 튜플
tuple10 = tuple('ABC') # ('A', 'B', 'C') 문자열의 각 문자로부터 원소를 생성
tuple11 = tuple([1, 2, 3]) # (1, 2, 3) 리스트로부터 원소를 생성
tuple12 = tuple({1, 2, 3}) # (1, 2, 3) 집합으로부터 원소를 생성
tuple13 = tuple(range(7)) # (0, 1, 2, 3, 4, 5, 6)
tuple14 = tuple(range(3, 8)) # (3, 4, 5, 6, 7)
tuple15 = tuple(range(3, 13, 2)) # (3, 5, 7, 9, 11)
패킹
- 한 변수에 여러 개의 데이터를 할당하여 여러 개의 객체를 하나의 객체로 합쳐주는 것
# 리스트
list01 = [1, 2, 3] # list = [1, 2, 3]
print(list01) # [1, 2, 3]
# 튜플
tuple01 = ('홍길동', 20240610) # tuple01 = ('홍길동', 20240610)
print(tuple01) # ('홍길동', 20240610)
언패킹
- 패킹한 변수를 다시 여러 개의 데이터로 대입하는 것
# 리스트
list01 = [1, 2, 3] # list = [1, 2, 3]
a, b, c = list01 # a, b, c = 1, 2, 3
print(a, b, c) # 1 2 3
# 튜플
tuple01 = ('홍길동', 20240610) # tuple01 = ('홍길동', 20240610)
name, stu_number = tuple01 # name, stu_number = '홍길동', 20240610
print(name, stu_number) # 홍길동 20240610
슬라이스식
- 리스트 또는 튜플의 원소 일부를 연속해서 또는 일정한 간격으로 꺼내 새로운 리스트 또는 튜플을 만드는 것
s = [11, 22, 33, 44, 55, 66, 77]
print(s[0:6]) # [11, 22, 33, 44, 55, 66]
print(s[0:7]) # [11, 22, 33, 44, 55, 66, 77]
print(s[0:7:2]) # [11, 33, 55, 77]
print(s[-4:-2]) # [44, 55, 66]
print(s[3:1]) # [] -> j값이 i값보다 작지만 오류는 나지 않음
슬라이스식의 다양한 패턴
패턴 | 설명 | 실행 예 | 실행 결과 |
s[:] | 리스트 s의 원소를 모두 출력 | s[:] | [11, 22, 33, 44, 55, 66, 77] |
s[:n] | 리스트 s의 원소 중 맨 앞부터 n개까지 출력 | s[:3] | [11, 22, 33] |
s[i:] | 리스트 s의 원소 중 s[i]부터 맨 끝까지 출력 | s[3:] | [44, 55, 66, 77] |
s[-n:] | 리스트 s의 원소 중 -n부터 맨 끝까지 출력 | s[-3:] | [55, 66, 77] |
s[::k] | 리스트 s의 원소 중 맨 앞부터 k개씩 건너뛰며 출력 | s[::2] | [11, 33, 55, 77] |
s[::-1] | 리스트 s의 원소 중 맨 끝부터 전부 출력 | s[::-1] | [77, 66, 55, 44, 33, 22, 11] |
len() 함수로 배열의 원소 수 구하기
>>> x = [15, 64, 7, 3.14, [32, 55], 'ABC']
>>> len(x)
6
빈 배열 판단하기
if x:
# x가 비어 있지 않으면(True) 출력
else:
# x가 비어 있으면(False) 실행
비교 연산자로 배열의 대소 또는 등가 관계 판단하기
>>> [1, 2, 3] == [1, 2, 3]
>>> [1, 2, 3] < [1, 2, 4]
>>> [1, 2, 3, 4] <= [1, 2, 3, 4]
>>> [1, 2, 3] < [1, 2, 3, 5]
>>> [1, 2, 3] < [1, 2, 3, 5] < [1, 2, 3, 5, 6] # and 결합
등가성과 동일성
등가성 비교(==): 좌변과 우변의 값이 같은지 비교
동일성 비교(is): 값은 물론 객체의 식별 번호까지 같은지 비교
리스트 컴프리헨션
아래와 같은 코드가 있을때..
As-is
# 1
array = []
# 2
for i in range(0, 20, 2):
#3
array.append(i * i)
print(array)
# 결과: [0, 4, 16, 36, 64, 100, 144, 196, 256, 324]
To-be
# 1 3 2
array = [i * i for i in range(0, 20, 2)]
print(array)
# 결과: [0, 4, 16, 36, 64, 100, 144, 196, 256, 324]
위와 같은 순서로 작성해서 동일한 결과가 나타난다.
만약 for 문 안에 조건문이 있다면 리스트 컴프리헨션을 할 때 가장 뒷부분에 넣어주면 된다.
그리고 마지막에는 i * i의 결과가 리스트로 반환되어 출력되는 것이다.
배열 원소의 최댓값 구하기
# a의 원소가 3개일 때
>>> maximum = a[0]
>>> if a[1] > maximum: maximum = a[1]
>>> if a[2] > maximum: maximum = a[2]
# a의 원소가 4개일 때
>>> maximum = a[0]
>>> if a[1] > maximum: maximum = a[1]
>>> if a[2] > maximum: maximum = a[2]
>>> if a[3] > maximum: maximum = a[3]
원소가 많은 경우는 아래 방법으로 비교
def max_of(a):
maximum = a[0]
for i in range(1, len(a)):
if a[i] > maximum:
maximum = a[i]
배열 원소의 최댓값을 구하는 함수 구현
# 시퀀스 원소의 최댓값 출력하기
from typing import Any, Sequence
def max_of(a: Sequence) -> Any:
"""시퀀수형 a 원소의 최댓값을 반환"""
maximum = a[0]
for i in range(1, len(a)):
if a[i] > maximum:
maximum = a[i]
return maximum
if __name__ == "__main__":
print('배열의 최댓값을 구합니다.')
num = int(input('원소 수를 입력하세요.: '))
x = [None] * num # 원소 수가 num인 리스트를 생성
for i in range(num):
x[i] = int(input(f'x[{i}]값을 입력하세요.: '))
print(f'최댓값은 {max_of(x)}입니다.')
Any: 제약이 없는 임의의 자료형을 의미
Sequence: 시퀀스형을 의미
- 시퀀스형에는 리스트형, 바이트 배열형, 문자열형, 튜플형, 바이트열형이 있음
따로따로 생성한 리스트, 튜플의 동일성 판단하기
>>> lst1 = [1, 2, 3, 4, 5]
>>> lst2 = [1, 2, 3, 4, 5]
>>> lst1 is lst2
False
lst1과 lst2 모두 [1, 2, 3, 4, 5]로 같은 리스트를 생성한 것처럼 보이지만 이는 리터럴(고정된 값)이 아니기 때문이다.
리스트, 튜플의 대입
리스트를 2개 선언해서 서로 대입해도 원소 자체는 복사되지 않는다.
대입에서 복사되는 것은 값이 아니라 참조하는 곳이기 때문이다.
>>> lst1 = [1, 2, 3, 4, 5]
>>> lst2 = lst1
>>> lst1 is lst2
True
>>> lst1[2] = 9
>>> lst1
[1, 2, 9, 4, 5]
>>> lst2
[1, 2, 9, 4, 5]
리스트를 스캔하는 4가지 방법
1. 리스트의 모든 원소를 스캔하기(원소 수를 미리 파악)
x = ['John', 'George', 'Paul', 'Ringo']
for i in range(len(x)):
print(f'x[{i}] = {x[i]}')
# 실행결과
# x[0] = John
# x[1] = George
# x[2] = Paul
# x[3] = Ringo
2. 리스트의 모든 원소를 enumerate( ) 함수로 스캔하기
x = ['John', 'George', 'Paul', 'Ringo']
for i, name in enumerate(x):
print(f'x[{i}] = {name}')
# 실행 결과
# x[0] = John
# x[1] = George
# x[2] = Paul
# x[3] = Ringo
3. 리스트의 모든 원소를 enumerate( ) 함수로 스캔하기(1부터 카운트)
x = ['John', 'George', 'Paul', 'Ringo']
for i, name in enumerate(x, 1):
print(f'{i}번째 = {name}')
# 실행 결과
# 1번째 = John
# 2번째 = George
# 3번째 = Paul
# 4번째 = Ringo
4. 리스트의 모든 원소를 스캔하기(인덱스 값을 사용하지 않음)
x = ['John', 'George', 'Paul', 'Ringo']
for i in x:
print(i)
# 실행 결과
# John
# George
# Paul
# Ringo
튜플의 스캔
튜플은 다음처럼 리스트로 작성한 x = [ ] 를 x = ( )로 수정하는 것만으로 스캔할 수 있다.
x = ('John', 'George', 'Paul', 'Ringo')
∴ 이터러블: 문자열, 리스트, 튜플, 집합, 딕셔너리 등의 자료형 객체는 모두 이터러블(반복 가능)하다는 공통점이 있다.
이터러블 객체는 원소를 하나씩 꺼내는 구조이며, 이터러블 객체를 내장 함수인 iter( )의 인수로 전달하면 그 객체에 대한 이터레이터(반복자)를 반환한다. 이터레이터는 데이터의 나열을 표현하는 객체이다. 이터레이터의 __next__ 함수를 호출하거나 내장 함수인 next( ) 함수에 이터레이터를 전달하면 원소를 순차적으로 꺼낼 수 있다.
꺼낼 원소가 더 이상 없는 경우에는 StopIteration으로 예외 발생을 시킨다.
배열 원소를 역순으로 정렬하기
1. 직접 구현하기
배열 a의 원소가 7개이고, [1, 2, 3, 4, 5, 6, 7] 의 값이 저장되어 있다면 이것을 [7, 6, 5, 4, 3, 2, 1]의 순서로 바꿀때
교환 횟수는 원소 수 // 2번이다.(소수점 이하는 버림)
from typing import Any, MutableSequence
def reverse_array(a: MutableSequence) -> None:
"""뮤터블 시퀀스 a의 원소를 역순으로 정렬"""
n = len(a)
for i in range(n // 2):
a[i], a[n - i - 1] = a[n - i - 1], a[i]
if __name__ == "__main__":
print('배열 원소를 역순으로 정렬합니다.')
nx = int(input('원소 수를 입력하세요.: '))
x = [None] * nx # 원소 수가 nx인 리스트 생성
for i in range(nx):
x[i] = int(input(f'x[{i}]값을 입력하세요.: '))
reverse_array(x) # x를 역순으로 정렬
print('배열 원소를 역순으로 정렬했습니다.')
for i in range(nx):
print(f'x[{i}] = {x[i]}')
2. 라이브러리 사용
x.reverse() # 리스트 x의 원소를 역순으로 정렬
위 reverse 함수를 사용하면 역순으로 쉽게 정렬이 가능하다.
reversed(x) # x의 원소를 역순으로 꺼내는 이터레이터를 반환
reversed 함수 사용 시 결과는 아래와 같다.
<list_reverseiterator object at 0x000002754BE6A590>
따라서 어떤 리스트의 원소를 역순으로 정렬한다면, reversed() 함수가 반환하는 이터러블 객체를 list() 함수에 넘기고 새로운 리스트를 생성해야 한다.
아래와 같이 작성하면 된다.
x = [1, 2, 3, 4, 5]
print(list(reversed(x)))
# 실행 결과
# [5, 4, 3, 2, 1]
진수 변환하기
10진수를 n진수로 변환 시 정수를 n으로 나눈 나머지를 구하는 동시에 몫을 반복해서 나눠야 한다.
몫이 0이 될 때까지 반복하고, 나머지를 역순으로 늘어놓으면 기수로 변환한 수가 된다.
59를 2진수로 변환하게되면 111011
59를 8진수로 변환하게 되면 73
59를 16진수로 변환하게 되면 3B
변환 과정은 아래와 같다.
10진수 -> n진수로 변환하는 프로그램
def card_conv(x: int, r: int) -> str:
"""정숫값 x를 r진수로 변환한 뒤 그 수를 나타내는 문자열을 반환"""
d = ''
dchar = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
while x > 0:
d += dchar[x % r] # 해당하는 문자를 꺼내 결합
x //= r
return d[::-1]
if __name__ == "__main__":
print('10진수를 n진수로 변환합니다.')
while True:
while True:
no = int(input('변환할 값으로 음이 아닌 정수를 입력하세요.: '))
if no > 0:
break
while True:
cd = int(input('어떤 진수로 변환할까요?: '))
if 2 <= cd <= 36:
break
print(f'{cd}진수로는 {card_conv(no, cd)}입니다.')
retry = input( "한번 더 변환할까요?(Y ... 예 / N ... 아니오): ")
if retry in {'N', 'n'}:
break
소수 나열하기
소수: 자신과 1 이외의 정수로 나누어 떨어지지 않는 정수
따라서 소수 13은 1과 13을 제외한 어떤 정수로도 나누어 떨어지지 않으며, 2부터 n - 1까지 어떤 정수로도 나누어 떨어지지 않는다.
만약 나누어 떨어지는 정수가 하나 이상 존재하면 그 수는 합성수이다.
아래는 1,000 이하의 소수를 나열하는 프로그램이다.
counter = 0
for n in range(2, 1001):
for i in range(2, n):
counter += 1
if n % i == 0: # 나누어 떨어지면 소수가 아님
break # 반복은 더 이상 불필요하여 중단
else: # 끝까지 나누어 떨어지지 않으면 다음을 수행
print(n)
print(f'나눗셈을 실행한 횟수: {counter}')
- n이 소수일 때: for 문은 마지막까지 실행 -> else 문을 실행하여 n 값을 출력
- n이 합성수일 때: for 문은 중단
따라서 정수 n이 소수인지 여부는 2부터 n-1까지 어떤 소수로도 나누어 떨어지지 않는 것을 만족하는지 알아보면 된다.
예를 들어 7이 소수인지는 7보다 작은 소수인 2, 3, 5로 나눗셈을 실행하는 것만으로 충분하다.(4와 6은 하지 않아도 됨)
알고리즘 개선하기 1
counter = 0 # 나눗셈 횟수를 카운트
ptr = 0 # 이미 찾은 소수의 개수
prime = [None] * 500 # 소수를 저장하는 배열
prime[ptr] = 2 # 2는 소수이므로 초깃값으로 지정
ptr += 1 # 1번째부터 저장할 수 있도록 초깃값 증가
for n in range(3, 1001, 2): # 홀수만을 대상으로 지정
for i in range(1, ptr): # 이미 찾은 소수로 나눔
counter += 1
if n % prime[i] == 0: # 나누어 떨어지면 소수가 아님
break # 반복 중단
else: # 끝까지 나누어 떨어지지 않았다면
prime[ptr] = n # 소수로 배열에 등록
ptr += 1 # 인덱스 증가
for i in range(ptr): # ptr의 소수를 출력
print(prime[i])
print(f'나눗셈을 실행한 횟수: {counter}')
파이썬에서는 for-else 문을 사용할 수 있다는 것을 기억하자.
for-else 문은 for 문에서 break문이 실행되면 else 문이 실행되지 않는다.
따라서 for 문 안의 if 문에서 합성수일때 반복이 중단되었으므로, else문은 n이 소수일 때(어떤 소수로도 나누어 떨어지지 않을 때)실행되게 되어 prime[ptr] 배열에 n을 등록하고 ptr 인덱스를 증가하는 것이다.
알고리즘 개선하기 2
counter = 0 # 곱셈과 나눗셈을 합한 횟수
ptr = 0 # 이미 찾은 소수의 개수
prime = [None] * 500 # 소수를 저장하는 배열
prime[ptr] = 2 # 2는 소수
ptr += 1
prime[ptr] = 3 # 3은 소수
ptr += 1
for n in range(5, 1001, 2): # 홀수만을 대상으로 지정
i = 1
while prime[i] * prime[i] <= n:
counter += 2
if n % prime[i] == 0: # 나누어 떨어지면 소수가 아님
break # 반복 중단
i += 1
else: # 끝까지 나누어 떨어지지 않았다면
prime[ptr] = n # 소수로 배열에 등록
ptr += 1 # 인덱스 증가
counter += 1
for i in range(ptr): # ptr의 소수를 출력
print(prime[i])
print(f'나눗셈을 실행한 횟수: {counter}')
곱셈과 나눗셈의 실행 횟수를 나타내는 counter를 증가시키는 부분은 16행과 23행 두 곳이다.
while 문을 반복할 때마다 counter를 2씩 증가시키는 이유는 곱셈(prime[i] * prime[i])과 나눗셈(n%prime[i])의 실행 횟수를 세기 때문이다.
그러나 prime[i] * prime[i] <= n이 성립하지 않는다면 while 문이 실행되지 않으므로 그 곱셈 횟수는 계산되지 않는다.
그래서 while문을 종료한 뒤 else 문에서 1을 증가시키는 것이다.
얕은 복사와 깊은 복사
얕은 복사는 참조값만 복사하는 방식이며, 깊은 복사는 참조값 뿐만 아니라 참조하는 객체 자체를 복사하여 객체가 갖는 모든 멤버를 모두 복사하므로 전체 복사라고도 한다.
얕은 복사
>>> x = [[1, 2, 3], [4, 5, 6]]
>>> y = x.copy() # x를 y로 얕은 복사
>>> x[0][1] = 9
>>> x
[[1, 9, 3], [4, 5, 6]]
>>> y
[[1, 9, 3], [4, 5, 6]]
리스트의 얕은 복사는 리스트 안의 모든 원소가 참조하는 곳까지 복사된다.
x[0]과 y[0]이 참조하는 곳이 같으므로 x[0][1]과 y[0][1]이 참조하는 곳도 같다. 즉, 리스트 x가 참조하는 곳이 다르면 y도 달라진다.
깊은 복사
>>> import copy # deepcopy를 사용하기 위한 copy 모듈을 임포트
>>> x = [[1, 2, 3], [4, 5, 6]]
>>> y = copy.deepcopy(x) # x를 y로 깊은 복사
>>> x[0][1] = 9
>>> x
[[1, 9, 3], [4, 5, 6]] # 대입된 9가 출력됨
>>> y
[[1, 2, 3], [4, 5, 6] # y 배열은 영향을 받지 않음
'자료구조, 알고리즘 입문' 카테고리의 다른 글
기본 자료구조 - 연결 리스트 (7) | 2024.10.09 |
---|---|
[알고리즘] 재귀 알고리즘을 활용한 8퀸 문제 풀이 (0) | 2022.05.18 |