내가 자주 까먹는 파이썬 기능들

2024. 1. 26. 20:42PS/파이썬 함수 사용법

[ 요소 별 정렬 ]

 정렬을 하는데 요소가 많으면 첫 번째는 오름차순으로 두 번째는 내림차순으로 정리해야 하는 경우가 생길 수 있다. 이런 경우 sort 내부에서 lambda로 쉽게 오름차순과 내림차순을 설정할 수 있다.

 

백준 알고리즘에서 국영수(10825) 문제를 풀면 정렬 기준은 4가지이다.

1. 두 번째 원소를 기준으로 내림차순 정렬

2. 두 번째 원소가 같은 경우, 세 번째 원소를 기준으로 오름차순 정렬

3. 세 번째 원소가 같은 경우, 네 번재 원소를 기준으로 내림차순 정렬

4. 네 번째 원소가 같은 경우, 첫 번째 원소를 기준으로 오름차순 정렬

 

이 경우 입력이 (Junkyu 50 60 100, Sangkeun 80 60 50, Sunyoung 80 70 100) 같이 들어온다. 정렬 기준의 우선 순위가 있고 방향이 각각일 때 해결하는 방법은 아래처럼 작성하면 된다. key = lambda x로 두고 내부 값 별로 내림차순은 -를 붙이고 아닌 경우엔 안 붙이면 된다. 그리고 우선 순위는 요소의 순서대로 놓으면 된다.

import sys

N = int(sys.stdin.readline())

board = []
for i in range(N):
    tmp = list(sys.stdin.readline().split())

    board.append(tmp)

board.sort(key=lambda x: (-int(x[1]), int(x[2]), -int(x[3]), x[0]))

for student in board:
    print(student[0])

 

[ Counter ]

 문제를 풀다보면 생각보다 list내에서 해당 요소의 개수를 찾아야 하는 경우가 있다. 그렇게 어려운 건 아니지만 매번 귀찮게 for문으로 만든다면 코드가 굉장히 지저분해지고 문제를 찾기 어려울 수 있다.

 

 만약에 [1,1,1,2,3,4,5,6]에서 각각의 요소 별 개수를 쉽게 구하는 방법을 알고 싶다고 하면 다음과 같이 코드를 작성하면 된다. Counter를 collections에서 가져오고 Counter에 내가 개수를 알고 싶은 배열을 넣고 마치 Java의 Map처럼 값을 조회해오면 된다.

from collections import Counter

numberList = [1,1,1,2,3,4,5,6]
count = Counter(numberList)

print(count[1])

 

[ 순열과 조합 ]

 생각보다 순열과 조합은 사용될 일이 많다. 연구소, 감시 피하기 같은 문제를 풀다보면 내부에 장애물을 3개 설치해서 어쩌구저쩌구하는 문제들이 생기는데 이 경우엔 모든 정점들을 넣고 combinations로 경우의 수를 구한다음 해결하면 된다.

 

 아래의 코드는 아무것도 없는 공간 (0)인 경우를 모두 넣어서 정점을 [ i , j ] 형태로 모두 v_set에 밀어넣은 뒤 해당 2차원 리스트에서 3개의 요소별로 조합을 구하여 차단벽을 세우는 모든 경우의 수를 구했다.

 

 그러면 tmp라는 리스트에는 각각의 경우의 수가 [ [ v10, v11 ], [ v20, v21 ], [ v30, v31] 이런 형태로 저장된다. v10는 1번 정점의 첫번째 요소를 의미한다. 그러면 해당 값을 리스트에서 for문을 for v1, v2, v3 in tmp 이런 식으로 작성하면 요소를 꺼내서 작성할 수 있다.

from itertools import combinations

N, M = map(int, input().split())

graph = []
for _ in range(N):
    tmp = list(map(int, input().split()))
    graph.append(tmp)

v_set = []
germs=  []
for i in range(N):
    for j in range(M):
        if(graph[i][j] == 0):
            v_set.append([i,j])
        if(graph[i][j] == 2):
            germs.append([i,j])
        
tmp = list(combinations(v_set, 3))

 

 이 외에도 순열을 이용해야 하는 경우도 있는데 그 때는 combinations를 permutations로 바꿔주면 된다. 순열은 중복 없이 순서 있게 설정한 개수를 고르는 것이다. [1,2,3,4]를 2가지 경우로 구하면 [1,2],[1,3],[1,4],[2,1],[2,3],[2,4],[3,1],[3,2],[3,4],[4,1],[4,2],[4,3] 이렇게 총 12가지가 나온다. 그런데 내부에 중복된 값이 있어서 [1,1,2,3,4] 이렇게 있다고 하면 위의 원래 값에서 [1,1] 이렇게 하나만 추가된 걸 원할 수 있다. 이건 어떻게 구현을 해야 할까? 그냥 list 말고 set으로 만들어주면 된다. 대신 순서가 조금 달라진다.

 

위의 경우를 쓸 일도 있고 아래의 경우를 쓸 일도 있다. 아래의 경우와 관련된 예시는 연산자 끼워넣기 같은 문제가 있다.

from itertools import permutations

tmp = [1,2,3,4]
result = list(permutations(tmp, 2))
print(result)

tmp = [1,1,2,3,4]
result = set(permutations(tmp, 2))
print(result)

 

 그리고 중복 조합을 써야하는 경우가 진짜 중복조합을 물어보는 문제 제외하고 본 적이 없는데, 그땐 combinations_with_replacement를 사용하면 된다.

[ copy ]

 생각보다 배열 복사본을 조작하고 원본은 보전해야 하는 경우가 꽤나 있다. 그럴 때는 copy를 사용하면 된다. copy는 깊은 복사와 얕은 복사가 있는데, 간단하게 말하면 깊은 복사는 완전 복사본을 만들어서 내뱉고 얕은 복사는 이전 값이 가진 참조값만을 복사한다. 그렇기 때문에 깊은 복사를 한 값을 변경한 경우에는 원본이 바뀌지 않지만 얕은 복사는 이전 값이 바뀐다.

 

 해당 모듈을 사용할 때에는 주의할 점이 있다. copy.copy는 1차원배열에서는 깊은 복사처럼 보이지만, 2차원 배열에서는 얕은 복사인게 티가 난다. 대부분 복사할 경우는 좌표를 복사해놓으므로 얕은 복사처럼 된다.

# 깊은 복사처럼 행동
import copy

arrays = [1,2,3,4]

new1 = copy.copy(arrays)
new2 = copy.deepcopy(arrays)

new2[0] = 5
print(new2)
print(arrays)

new1[0] = 3
print(new1)
print(arrays)

 

# 얕은 복사인 경우
import copy

arrays = [[1,2],[3,4]]

new1 = copy.copy(arrays)
new2 = copy.deepcopy(arrays)

new1[0].append(3)
print(new1)
print(arrays)

new2[0].append(3)
print(new2)
print(arrays)