파이썬 인덱스 다루기 ft. 코딩 테스트
2024-03-28 22:21:45

예전에 쓴 포스트를 재업로드 해 본다. 추후 numpy, pandas 및 문자열 부분까지 추가로 정리해보면 좋을 것 같다. 간만에 파이썬을 다시 보니 코틀린과의 다른 점이 확실히 보여서 흥미로운 부분이 많다.

파이썬 슬라이싱

매일 알고 한 문제 풀기에 도전하고 있지만 쉬운 난이도의 문제들임에도 고전을 면치 못하고 있다. 근본 원인으로는 아직도 파이썬 문법에 익숙하지 않은 것을 들 수 있겠지만, 기초적인 행/열 데이터 다루는 것이 아직 낯설다는 점이 가장 크다. 이에 평소에 잘 모르고 대충 사용하던 파이썬 1, 2차원 배열 형태 자료구조들의 인덱스 슬라이싱과 행렬 데이터 추출에 대해 공부한것을 확실히 정리해보고자 한다.

우선 다음과 같은 1차원, 2차원 배열이 존재한다는 것을 가정하고 진행한다.

1
2
3
4
5
6
7
8
9
arr = [0,1,2,3,4,5,6]

grid = [[0,1,2,3,4,5,6],
[1,2,3,4,5,6,7],
[9,9,4,7,3,2,2],
[1,7,3,4,2,6,3],
[7,3,5,2,5,2,7],
[6,3,5,9,0,0,1],
[3,6,2,7,4,5,1]]

인덱스 슬라이싱

슬라이싱 하면 가장 먼저 떠오르는 것은 햄을 자르는 행위이다. 내가 원하는 부분부터 원하는 길이까지 설정한 다음 자른다. 파이썬의 인덱스 슬라이싱 또한 같다.

1차원 배열에서 개별 요소 추출하기

1
2
# arr의 0, 3, 6번째 요소 출력하기
print(arr[0], arr[3], arr[6]) # 0 3 6

뒤에서 n번째 요소 추출하기

뒤에서부터 출력할 시 -1부터 시작하여 -1씩 작아진다(arr[-1]은 뒤에서 첫 번째 요소, arr[-2]는 뒤에서 두 번째 요소)

1
print(arr[-1], arr[-2])         # 6 5

1차원 배열에서 여러 요소 추출하기

변수명[시작인덱스:종료인덱스] 와 같이 표현한다. 다만 종료인덱스의 값은 포함되지 않는다. 시작 인덱스 혹은 종료 인덱스 생략시 자동으로 알아서 시작부터, 혹은 끝까지 알아서 늘어진다.

1
2
# arr의 0~3, 2~6번째 요소 출력하기
print(arr[0:3], arr[2:6]) # [0, 1, 2] [2, 3, 4, 5]

1차원 배열에서 n번씩 건너띄어 요소 추출하기

변수명[시작인덱스:종료인덱스:n번마다] 와 같이 표현한다. 인덱스 대괄호([]) 안은 시작(포함):끝(미포함):n번마다 형식으로 이루어진다. 두번째 : 생략시 default값인 1로 자동 설정된다.

1
2
3
4
5
6
7
8
# arr의 짝수 번째 요소(0,2,4,6) 요소만 출력하기
print(arr[::2]) # [0, 2, 4, 6]

# arr의 홀수 번째 요소(1,3,5)만 출력하기
print(arr[1::2]) # [1, 3, 5]

# arr의 2,3,4,5번째 인덱스 중 짝수 번째만 출력하기
print(arr[2:6:2]) # [2, 4]

1차원 배열 뒤집기

[::n] 에서 n을 -1로 설정하면 거꾸로 인덱싱이 이루어지므로 배열 뒤집기가 가능하다

1
2
3
4
5
6
7
8
9
10
11
12
13

# 배열 뒤집기
print(arr[::-1]) # [6, 5, 4, 3, 2, 1, 0]

# 2,3,4번째 요소를 뒤집어서 출력하기
print(arr[5:1:-1]) # [5, 4, 3, 2]

# 2번째 요소부터 맨 뒤 요소까지 거꾸로 출력
print(arr[:1:-1]) # [6, 5, 4, 3, 2]

# 참고로 맨 뒤 -1 설정 없이 시작:끝 인덱스에 음수를 넣을 경우 뒤집어지지 않는다
print(arr[:-3]) # [0, 1, 2, 3]
print(arr[-3:]) # [4, 5, 6]

1차원 배열 왼쪽/오른쪽으로 n칸씩 움직이기

temp 변수를 선언하여 n-1 번째 인덱스까지 혹은 n번 인덱스부터 값을 따로 저장한 후 해당 공백만큼 기존 배열 값들을 이동한 후 다시 temp에 저장된 일부 요소들을 더해준다.

1
2
3
4
5
# 왼쪽으로 4칸 시프트
temp = arr[:4]
arr = arr[4:]
arr = arr + temp
print(arr) # [4, 5, 6, 0, 1, 2, 3]

temp 없이 짧게 구하는 방법도 있다. 어느 쪽으로 시프트(혹은 회전)하느냐에 따라 [:n], [n:] 의 위치를 바꿔주기만 하면 된다.

1
2
arr = arr[4:] + arr[:4]
print(arr)

**가장 빠른 방법(list에 한해서는)**도 있지만 오른쪽으로 시프트하는 경우 추가 연산이 발생하여 비효율적이다.

1
2
3
4
drr = arr[:]
for _ in range(3):
drr.append(drr.pop(0))
print(drr)

2차원 배열 다루기

행 출력하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 모든 행 출력하기
for g in grid:
print(g) # 각 행이 순서대로 출력

# 모든 행의 0~3번째 인덱스 까지만 출력하기
for g in grid:
print(g[:4]) # 각 행의 0,1,2,3번째 인덱스가 정상 출력된다

# 모든 행을 뒤집어(거꾸로) 출력하기
for g in grid:
print(g[::-1]) # [6, 5, 4, 3, 2, 1, 0]

# 7행 중 첫 다섯 행만 출력하기
for i in range(5):
print(grid[i]) # [0, 1, 2, 3, 4, 5, 6] ...

# 첫 다섯 행 중 0~4번째 인덱스만 출력하기
for i in range(5):
print(grid[i][:5]) # [0, 1, 2, 3, 4] ...

# 첫 다섯 행 중 0~4번째 인덱스만 거꾸로 출력하기
for i in range(5):
print(grid[i][4::-1]) # [4, 3, 2, 1, 0] ...

List comprehension 방식으로 모든 행 출력하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 0행 출력
print([i for i in grid[0]]) # []으로 감싸주지 않으면 object 주소만 출력된다

# 모든 행 출력
print([r for r in grid])

# 모든 행의 1~4열 출력
print([r[1:5] for r in grid]) # 2차원 리스트

# 모든 행의 1~4열 거꾸로 출력
print([r[4:0:-1] for r in grid])# 2차원 리스트

# 모든 행의 개별 요소를 전부 출력
print([e for r in grid for e in r])
# 해석: for r in grid:
# for e in r:
# print(e, end=' ')
# [e for e in r for r in grid] 로 실행시 r이 정의되지 않음(not defined)에러 발생

(번외)리스트 안의 값 출력하기

1
2
3
4
5
6
# 모든 행의 개별 요소를 띄어쓰기와 리스트 기호 [] 없이 출력
for e in arr:
print(e, end=' ') # 0 1 2 3 4 5 6

# 동일 작업을 간편하게 표현(* 를 사용)
print(*[e for e in arr]) # 0 1 2 3 4 5 6

엄밀히 말하면 완전히 같지는 않다. 전자는 매번 개별 요소를 출력하고 end=뒤에 오는 파라미터 값을 추가로 출력한다. 즉 0~6 모두 옆에 공백을 추가해주는 형식이다. 반면 후자는 자동으로 띄어쓰기가 이루어지고 굳이 end를 설정해주지 않아도 된다.

열 출력하기( =Transpose Matrix 만들기)

개인적으로 가장 헷갈렸던 부분이다. 크게 for문 사용, 리스트 컴프리헨션 사용, 그리고 zip(*iterable) 방식이 있다. iterable이란 문자열이나 리스트와 같이 순환 가능한 자료구조를 의미한다. 순환 가능하다는 것의 의미는 grid와 같은 변수(파이썬의 모든 자료형=객체=데이터=변수는 Object의 진전을 이은 객체이므로 grid 변수 또한 객체이다)가 있다고 가정했을 때 0번째, 1번째, …, n번째를 돌며 반복 가능한 객체를 말한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 2차원 리스트의 0번째 열만 출력하기
for i in range(7):
print(grid[i][0], end=' ')

# 0번째 열을 리스트 컴프리센션을 이용하여 한 줄로 출력
print(*[r[0] for r in grid])

# 2차원 리스트의 열끼리 출력(0, 1, 2, ...)
for j in range(7):
for i in range(7):
print(grid[i][j], end=' ')
print()

# list comprehension을 통한 간편한 처리
print()
for i in range(7):
print(*[r[i] for r in grid])

zip(*iterable) 방식으로 2차원 행렬을 튜플 형식의 Transpose 행렬로 바꿀 수 있다. 이를 리스트로 바꾸고 싶다면 각 행별로 list()로 감싸주어 형변환을 진행하자.

1
2
3
4
5
6
7
8
grid_z = list(zip(*grid))       # [(), (), (), ..., ()] 형식이 된다.
print(list(grid_z[0])) # 형변환 안하면 튜플 형식이 나온다.

# 이렇게도 표현 가능하다. 하지만 튜플 형식 값이 나오므로 따로 list() 처리가 필요하다.
print(list(zip(*grid))[0]) # (0, 1, 9, 1, 7, 6, 3)

# 모두 리스트 형으로 바꾸기
print(list(map(list,zip(*grid))))

대망의 numpy 방식을 알아보자.
for문을 돌려서 각 행의 열들만 출력하는 방식과 a.T 메소드로 전치행렬을 만들어버리는 방식도 존재한다.

1
2
3
4
5
6
7
8
9
10
11
import numpy as np
a = np.array(grid) # numpy 배열로 변환
# grid_np = np.array([[1,2,3], ['a','b','c']]) 처럼 직접 입력도 가능하다

# for문으로 열만 추출
for i in range(7):
print(a[:,i])

# .T 메소드로 전치행렬 만들기
grid_t = a.T
print(grid_t)

결론

numpy 씁시다. pandas 씁시다(pandas에선 loc(), iloc() 등으로 행과 열을 지정하여 요소들을 추출할 수 있다.).