https://www.acmicpc.net/problem/18352

 

18352번: 특정 거리의 도시 찾기

첫째 줄에 도시의 개수 N, 도로의 개수 M, 거리 정보 K, 출발 도시의 번호 X가 주어진다. (2 ≤ N ≤ 300,000, 1 ≤ M ≤ 1,000,000, 1 ≤ K ≤ 300,000, 1 ≤ X ≤ N) 둘째 줄부터 M개의 줄에 걸쳐서 두 개

www.acmicpc.net

 

어떤 나라에는 1번부터 N번까지의 도시와 M개의 단방향 도로가 존재한다. 모든 도로의 거리는 1이다. 이 때 특정한 도시 X로부터 출발하여 도달할 수 있는 모든 도시 중에서, 최단 거리가 정확히 K인 모든 도시들의 번호를 출력하는 프로그램을 작성하시오. 또한 출발 도시 X에서 출발 도시 X로 가는 최단 거리는 항상 0이라고 가정한다. 예를 들어 N=4, K=2, X=1일 때 다음과 같이 그래프가 구성되어 있다고 가정하자.

 

이 때 1번 도시에서 출발하여 도달할 수 있는 도시 중에서, 최단 거리가 2인 도시는 4번 도시 뿐이다. 2번과 3번 도시의 경우, 최단 거리가 1이기 때문에 출력하지 않는다. 

 

[ 입력 조건 ] 첫째 줄에 도시의 개수 N, 도로의 개수 M, 거리 정보 K, 출발 도시의 번호 X가 주어진다. (2 ≤ ≤ 300,000, 1 ≤ ≤ 1,000,000, 1 ≤ ≤ 300,000, 1 ≤ ≤ N) 둘째 줄부터 M개의 줄에 걸쳐서 두 개의 자연수 A, B가 공백을 기준으로 구분되어 주어진다. 이는 A번 도시에서 B번 도시로 이동하는 단방향 도로가 존재한다는 의미다. (1 ≤ A, ≤ N) 단, AB는 서로 다른 자연수이다.

 

[출력조건] X로부터 출발하여 도달할 수 있는 도시 중에서, 최단 거리가 K인 모든 도시의 번호를 한 줄에 하나씩 오름차순으로 출력한다. 이 때 도달할 수 있는 도시 중에서, 최단 거리가 K인 도시가 하나도 존재하지 않으면 -1을 출력한다.

입력 예시 1.

4 4 2 1
1 2
1 3
2 3
2 4
입력 예시 2.

4 4 1. 
1 2
1 3
2 3
2 4
입력 예시 3.

4 3 2 1
1 2
1 3
1 4

출력 예시 1.
4

출력 예시 2.
2
3
출력 예시 3.
-1

 

예시 2번으로 문제를 풀어보자.

n, m, k, x = map(int , input().split())
# 4 4 1 1


graph = [[] for _ in range(n+1)]
for _ in range(m):
    a, b = map(int, input("출발 도착").split())
    graph[a].append(b)
    
    
graph
# [[], [2, 3], [3, 4], [], []]

 

먼저 입력을 받아 n, m, k, x값을 할당받고,

graph 리스트를 만들어 각 도시별로 연결된 도시들을 표시해준다.

 

INF = float("inf")
[INF] * (n + 1)
# [inf, inf, inf, inf, inf]

 

앞으로 출발 도시 x로부터 각각의 모든 도시까지 거리를 distance로 계산해줄 것이다.

(n+1)을 곱해주는 이유는 가상의 0번 도시를 설정하여 인덱스를 편하게 계산해주기 위함이다.

앞으로 distance에서 자기 자신과의 거리는 0으로, 자기 자신을 제외한 다른 도시는 모두 INF로 시작하는 점을 기억하자.

 

def solution(start):
    # 앞으로 방문해야할 도시 리스트는 디큐 리스트 q로 둔다.
    q = deque()
    # 출발점 도시 x로부터의 거리를 나타내는 디큐 리스트 distance를 만든다.
    distance = deque([INF] * (n+1))
    
    # 초기값 세팅
    q.append(start)
    distance[start] = 0
    
    # 더이상 방문할 도시가 없을 때까지 반복하기
    while q:
    	# 지금 방문하는 도시 now
    	now = q.popleft()
        for next_city in graph[now]:
        	# 만약 아무도 오지 않았던 신규 방문지라면
            if distance[next_city] == INF:
            	# 본 문제에서는 도시간의 거리가 1이므로 1을 더하지만
                # 문제 조건 따라 1이 아니게 될 수 있음
            	distance[next_city] = distance[now] + 1
                q.append(next_city)
                
     # 디스턴스 리스트를 리턴
     return distance

 

dis_result = bfs_city(x)
dis_result
# deque([inf, 0, 1, 1, 2])

dis_k = [i for i, v in enumerate(dis_result) if v == k]
dis_k
# [2, 3]

if dis_k == []:
    print(-1)
else:
    while dis_k:
        print(dis_k.pop(0))
# 2
# 3

 

 

연습으로 미로 탐색 백준 2178번 문제를 풀어보자.

 

from collections import deque

 

디큐는 속도가 빠른 리스트로 생각해도 좋다.

 

코딩 테스트에서 파이썬으로 Queue, BFS, FIFO 방식의 알고리즘 접근이 필요할 때 리스트를 디큐로 바꾸어주면 속도 이슈 해결이 가능하다.

 

a_list = [1, 2, 3, 4, 5]
# [1, 2, 3, 4, 5]

a_dq = deque(a_list)
# deque([1, 2, 3, 4, 5])

 

이렇게 간단하게 디큐 함수로 바꾸어주기만 하면 된다.

 

a_dq.pop()
a_dq.popright()
# 5

a_dq.popleft()
# 1

a_dq
# deque([2, 3, 4])

a_dq.append(1004)
# deque([2, 3, 4, 1004])

 

  • 디큐 맨 마지막 값을 뺄 때 : .pop()
  • 디큐 맨 처음 값을 뺄 때 : .popleft()
    • 주의 / .pop(0)과 같이 사용하면 에러 발생!
  • pop과 popleft 모두 기존의 디큐를 수정, 업데이트하는 파괴적 함수!
  • 디큐 맨 뒤에 값을 추가할 때 : .append()

 

디큐를 활용한 bfs

graph_list = [
    [],
    [2,3,8], 
    [1,7],
    [1,4,5],
    [3,5],
    [3,4],
    [7],
    [2,6,8],
    [1,7]
]


def bfs(graph, start):
    need_visit = deque([])
    visited    = deque([])
    need_visit.append( start )
    while need_visit:
        node = need_visit.popleft()
        if node not in visited:
            visited.append(node)
            temp = graph[node]
            temp_reverse = list(reversed(temp))
            need_visit.extend( temp_reverse )
    return visited
    
    
  bfs(graph_list, 1)
  # deque([1, 8, 3, 2, 7, 5, 4, 6])

1. 선택정렬

# 0번째 아이템이 가장 작다고 가정하고 시작한다. 
# 만약 0번째 아이템보다 더 작은 아이템이 있다면 그중 가장 작은 놈과 0번째 아이템을 swap 한다.

arr = [20, 12, 10, 15, 2]

for i_step in range(len(arr)):
    # 현재 이터레이션에서 가장 작은 값의 인덱스가 무엇인지 min_step에 기록한다.
    # 앞으로 계속 갱신해 나간다.
    min_step = i_step

    for i in range(i_step+1, len(arr)):
        if arr[i] < arr[min_step]:
            min_step = i
        else:
            pass

    arr[min_step], arr[i_step] = arr[i_step], arr[min_step]
arr
>>> [2, 10, 12, 15, 20]
 

내장함수 sorted()처럼 비파괴적 함수로 만들어보기

def my_sorted(arr) :
    arr_dup = [i for i in arr]
    for i_step in range(len(arr_dup)):
        # 현재 이터레이션에서 가장 작은 값의 인덱스가 무엇인지 min_step에 기록한다.
        # 앞으로 계속 갱신해 나간다.
        min_step = i_step

        for i in range(i_step+1, len(arr_dup)):
            if arr_dup[i] < arr_dup[min_step]:
                min_step = i
            else:
                pass

        arr_dup[min_step], arr_dup[i_step] = arr_dup[i_step], arr_dup[min_step]
    return arr_dup
 
my_sorted(arr)
>>> [2, 10, 12, 15, 20]

arr
>>> [20, 12, 10, 15, 2]
 

 

위 코드는 arr의 길이(len)가 길어질수록 효율성이 낮아질 위험이 있다.

따라서 반복문 속에 중첩을 최대한 제거해 주는 것이 좋다.

 

 

2. 삽입정렬

 

선택 정렬이 아닌 삽입정렬로 리스트를 정렬해보자.

arr = [9, 5, 1, 4, 3]

for step in range(1, len(arr)):
    for i in range(step, 0, -1):
        if arr[i-1] > arr[i]:
            arr[i-1], arr[i] = arr[i], arr[i-1]

arr
>>> [1, 3, 4, 5, 9]
 

삽입정렬은 누가 더 작은지 기록할 필요가 없기때문에

선택정렬과 비교해 보면 훨씬 더 간단하다.

 

위 알고리즘을 적용하여 비파괴적 함수로 만들어 보자.

arr = [9, 5, 1, 4, 3]

def my_sorted(arr):
    arr_dup = [i for i in arr]

    for step in range(1, len(arr_dup)):
        for i in range(step, 0, -1):
            if arr_dup[i-1] > arr_dup[i]:
                arr_dup[i-1], arr_dup[i] = arr_dup[i], arr_dup[i-1]
    return arr_dup
 
my_sorted(arr)
>>> [1, 3, 4, 5, 9]

arr
>>> [9, 5, 1, 4, 3]
 

 

삽입정렬 역시 선택정렬과 마찬가지로 arr의 길이가 커질수록 효율성이 낮아지고 시간의 복잡도가 늘어나는 단점이 있으므로 유의한다.

 

 

 

3. 버블정렬

앞뒤 원소를 비교해서 더 큰 값이 앞에 있으면 뒤로 보내준다.

arr의 길이만큼 이터레이션을 돌린다.

arr = [-2, 45, 0, 11, -9]

for i in range(len(arr)):
    for k in range(len(arr)-i-1):
        print(i, k)
 

0 0

0 1

0 2

0 3

 

1 0

1 1

1 2

 

2 0

2 1

 

3 0

arr = [-2, 45, 0, 11, -9]

for i in range(len(arr)):
    for k in range(len(arr)-i-1):
        if arr[k] > arr[k+1]:
            arr[k], arr[k+1] = arr[k+1], arr[k]

arr
>>> [-9, -2, 0, 11, 45]
 

삽입정렬과 나름 비슷한 형태로, 역시 arr의 길이가 길어질수록 효율성이 낮아진다.

 

시각화해보기

for i in range(len(arr)):
    for k in range(len(arr)-i-1):
        if arr[k] > arr[k+1]:
            print(i, k, 'swap!')
            arr[k], arr[k+1] = arr[k+1], arr[k]
            print(arr)
        else:
            print(i, k, 'pass!')
            print(arr)
 

모든 단계별로 i, k, 스왑여부, arr을 출력하여 과정을 시각화 해보았다.

 

0 0 pass!

[-2, 45, 0, 11, -9]

0 1 swap!

[-2, 0, 45, 11, -9]

0 2 swap!

[-2, 0, 11, 45, -9]

0 3 swap!

[-2, 0, 11, -9, 45] --> 45 확정

1 0 pass!

[-2, 0, 11, -9, 45]

1 1 pass!

[-2, 0, 11, -9, 45]

1 2 swap!

[-2, 0, -9, 11, 45] --> 11, 45 확정

2 0 pass!

[-2, 0, -9, 11, 45]

2 1 swap!

[-2, -9, 0, 11, 45] --> 0, 11, 45 확정

3 0 swap!

[-9, -2, 0, 11, 45]

 

 

스택과 큐 모두 일을 처리하는 순서, 값을 처리하는 순서에 대한 개념.

새롭게 추가되는 값 / 지금 당장 처리해야하는 값을 생각하며 일을 한다.

 

 

Stack

  • Last In First Out : LIFO
  • 예 / 엘레베이터 : 늦게 온 사람이 제일 먼저 내린다. (문이 하나!)
  • 새로운 것 : 뒤로 쌓는다 -> 리스트 : .append()
  • 처리할 것 : 뒤에서부터 꺼내 쓴다 -> 리스트 : .pop()
task = ['eat', 'poop', 'sleep']
task.append('cry')
>>> ['eat', 'poop', 'sleep', 'cry']
task.pop()
>>> ['eat', 'poop', 'sleep']
 

리스트.pop() 은 파괴적 함수로 리스트의 마지막 값을 제거한다.


Queue

  • First In First Out : FIFO
  • 예 / 놀이공원 매표소 : 먼저 온 사람이 먼저 떠난다! (출입구가 앞 뒤로 두 개!)
  • 새로운 것 : 뒤로 쌓는다 -> 리스트 : .append() 메소드
  • 처리할 것 : 앞에서부터 꺼내 쓴다 -> 리스트 : .pop(0) 메소드
task = ['eat', 'poop', 'sleep']
task.append('cry')
>>> ['eat', 'poop', 'sleep', 'cry']
task.pop(0)
>>> ['poop', 'sleep', 'cry']
 

task.pop(0)과 같이 맨 앞의 인덱스(0)을 지정해 주어 앞에 있던 eat이 사라졌다.


참고사항

파이썬 언어의 특성상 Queue를 사용할 때 속도 이슈가 있음을 주의하면 좋다! 제일 앞의 원소를 추출하는 pop(0) 때문이다. 따라서 코딩테스트에서 특히 주의할 것. 로직에 결함이 없더라도 시간 초과로 통과가 안될 수 있음...!

코딩 테스트에서 반드시 큐를 사용하겠다고 한다면 --> deque라고 하는 패키지를 불러서 사용하는 것이 좋다.

 

 

인접행렬

  • 자기 자신 : 0
  • 연결 된 엣지 : 1 (또는 1*weight)
  • 연결 안 된 엣지 : Inf (무한대 값으로 0과 구분)
# infinite 값을 선언하기
INF = float('inf')

# 행렬 그래프로 나타내기 - 가중치 weight 고려해서 표현
graph = [
    [0, 7, 5],
    [7, 0, INF],
    [5, INF, 0]
]
 

도시끼리 연결되었는지 어떻게 확인할까?

graph[0][1]
>>> 7 (0번과 1번 도시가 연결됨)

graph[2][1]
>>> inf (2번과 1번 도시가 연결 안 됨)
 

 

인접리스트

모든 노드에 연결된 노드에 대한 정보를 차례차례 연결하여 저장

# 연결된 도시만 리스트로 표현

graph = [
    [(1, 7), (2, 5)], # --> 0번 도시의 연결 리스트업
    [(0, 7)], # --> 1번 도시의 연결 리스트업
    [(0, 5)]  # --> 2번 도시의 연결 리스트업
]
 

도시끼리 연결되었는지 연결리스트로는 어떻게 확인할까?

graph[0][0][1]
>>> 7

# 행렬보다 직관성은 떨어질 수 있다.
 

리스트 대신 딕셔너리로 표현하여 연결성을 좀 더 직관적으로 표현할 수도 있다.

graph_dict = {
    "0" : [("1", 7), ("2", 5)],
    "1" : [("0", 7)],
    "2" : [("0", 5)],
}
 

만약 문제에서 0번 도시가 없고 1번 도시부터 있는 경우

graph = [
    [], # --> 0번 도시는 비워두고 시작
    [(1, 7), (2, 5)], # --> 1번 도시부터 기록
    [(0, 7)],
    [(0, 5)] 
]
 

이렇게 0번 도시를 빈칸으로 넣어 주면 나중에 인덱스 헷갈릴 일이 없다!!! 꿀팁

 

 

[메모리]

인접 행렬(Adjacency Matrix): 모든 관계를 저장하므로 노드 개수가 많을 수록 불필요한 메모리가 소요된다.

인접 리스트(Adjacency List): 연결된 정보만을 저장하기 때문에 메모리를 효율적으로 사용한다.

출처 입력

[속도]

인접 행렬(Adjacency Matrix): 모든 경우의 수가 제시되어 있으므로 인접리스트보다 빠르다.

인접 리스트(Adjacency List): 인접행렬방식에 비해 정보를 얻는 속도가 느리다. 인접리스트방식에서는 연결된 데이터를 하나씩 확인해야 하기 때문이다.

출처 입력

"두 노드 간에 연결이 있는지 없는지에 대한 확인" 문제

→ 인접행렬이 빠르다

 

"노드에 연결된 모든 노드들을 순회/탐색" 문제

→ 인접리스트가 효율적이다

 

 

엣지를 따라 모든 노드를 방문하는 것을 그래프 탐색이라고 한다.

대표적으로 DFS(깊이 우선 탐색), BFS(너비 우선 탐색)이 있다.

 

 

Depth First Search (깊이 우선 탐색)

깊이 우선 탐색은 기본적으로 Stack이다. (Last In First Out)

오른쪽 왼쪽 상관은 없음

 

예제

graph_list = [
    [],        # 0번 도시(없음)
    [2, 3, 8], # 1번 도시
    [1, 7],    # 2번 도시
    [1, 4, 5], # 3번 도시
    [3, 5],    # 4번 도시
    [3, 4],    # 5번 도시
    [7],       # 6번 도시
    [2, 6, 8], # 7번 도시
    [1, 7]     # 8번 도시
]
 

1번 도시 방문

  • To do : [2, 3, 8]
    • 2번 도시 방문
    • To do : [1, 7] -> 1번은 됐고 7번 방문
      • To do : [2, 6, 8] -> 2번은 됐고 6번 방문
        • To do : [7] / 끝!
        • 8번 방문 / 끝!
    • 3번 도시 방문
    • To do : [1, 4, 5] -> 1번은 됐고 4번 방문
      • To do : [3, 5] -> 3번은 됐고 5번 방문 / 끝!
Done = [1]

Done = [1, 2]

Done = [1, 2, 7]

Done = [1, 2, 7, 6]

Done = [1, 2, 7, 6, 8]

Done = [1, 2, 7, 6, 8, 3]

Done = [1, 2, 7, 6, 8, 3, 4]

Done = [1, 2, 7, 6, 8, 3, 4, 5]

예제 문제 DFS 코드(함수)로 구현하기

graph = [
    [], #0
    [2, 3, 8], #1
    [1, 7], #2
    [1, 4, 5], #3
    [3, 5], #4
    [3, 4], #5
    [7], #6
    [2, 6, 8], #7
    [1, 7], #8
]

def dfs(graph, start_city) :
    to_visit = graph[start_city]
    done_visit = [start_city]

    while to_visit:
        visited = to_visit.pop()
        if visited not in done_visit:
            done_visit.append(visited)
            to_visit.extend(graph[visited])

    return done_visit

dfs(graph, 1)
>>> [1, 8, 7, 6, 2, 3, 5, 4]
 

pop으로 꺼내서 할당하는 visited의 값이 가장 큰 숫자부터 고르기때문에 [1, 8, 7, 6, 2, 3, 5, 4]가 나온다. 예제 문제처럼 pop으로 꺼내는 도시가 가장 작은 숫자부터 고르게 하려면 어떻게 해야 할까?

def dfs_2(graph, start_city) :
    to_visit = list(reversed(graph[start_city])) #💛
    done_visit = [start_city]
    while to_visit:
        visited = to_visit.pop()
        if visited not in done_visit:
            done_visit.append(visited)
            temp = graph[visited] #💛
            temp_reversed = list(reversed(temp)) #💛
            to_visit.extend(temp_reversed) #💛
    return done_visit

dfs_2(graph, 2)
>>> [1, 2, 7, 6, 8, 3, 4, 5]
 

뒤집어서 맨 뒤부터 추가해주기!

 

 

 

BFS의 경우 pop()을 pop(0)으로 변경해주면 끝인데,

속도 이슈가 있기때문에 코딩테스트에 권장하지 않음

-> 너비 우선 탐색법이 필요한 경우 depop을 고려

 

 

자기 자신을 리턴하는 재귀함수!

현업에서 쓸 일은 거의 없지만, 코딩 테스트에서 자주 등장한다.

파이썬의 재귀함수에는 한계치(limit)가 있으므로 주의해서 사용한다.

 

기본 형태

def recursive_func():
    print('재귀함수 만세!!!!!!💛')
    return recursive_func()

recursive_func()
 

>>> RecursionError : maximum recursion depth exceeded 어쩌구 발생

^_^... 따라하지 마시오...

 


 

기본 예제 - 팩토리얼 함수 만들기

(1) while 반복문 이용하기

def factorial_while(n):
    i = 1
    answer = 1
    while i <= n:
        answer *= i
        i += 1
    return answer
 

(2) 재귀함수 이용하기

def factorial(n):
    if n == 1:
        return 1
    elif n == 2:
        return 2
    else:
        return n * factorial(n-1)
 
 

 

 

가끔 코딩 테스트에서 이거.. 리스트를 평탄화해주면 쉽게 풀 수 있겠는데? 라는 생각이 들 때가 있습니다. 프로그래머스 0단계에서는 이 평탄화 함수를 작성하는 것 자체가 문제로 출제되기도 했었던거 같기도 하구요. 어쨌든 알고 있으면(또는 외워 두면) 정말 너무나!!!! 큰 도움이 되는!!!!! 평탄화 함수 작성하는 방법!!! 정리해 봅시다.

 

* 설명 없이 함수만 빠르게 확인하고 싶으시다면 글 맨 아래로 가세요! *

 

 

 

 

먼저, 중첩 리스트란 무엇일까요?

normal_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
weird_list = [1, 2, [3], 4, 5, [6], 7, [8, 9, 10]]
crazy_list = [1, 2, [[3], 4, 5], [[6], 7, [8, [9, [10]]]]]

위 세 가지 리스트를 살펴 봅시다.

 

먼저 멀쩡한 리스트 normal_list는 참 보기 좋습니다.

그런데 weird_list는 리스트 안에 리스트를 원소로 가지고 있네요. [3], [6], [8, 9, 10]과 같이 말이죠. 이런 놈들을 중첩 리스트라고 부르겠습니다. 그래도 여기까진 어찌저찌 해체해볼 수 있을 것 같습니다.

 

그런데 crazy_list.. 이 미친놈을 보니까 리스트 안에 리스트가 또 리스트를 가지고 있어요. 중첩에 중첩에 중첩에 중첩에 중...!!!

 

 

평탄화란? (Flatten)

weird_list, crazy_list, crazy_list_and_tuple처럼 중첩된 리스트를

normal_list처럼 1차원의 리스트로 평평(Flat)하게 평탄화(Flatten)해주는 작업을 의미합니다.

 

만약 우리가 리스트 안에 있는 모든 값들 중에서 가장 큰 값을 구해야 된다면 어떻게 할까요?

normal_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
max(normal_list)
# 10

crazy_list = [1, 2, [[3], 4, 5], [[6], 7, [8, [9, [10]]]]]
max(crazy_list)
# ????????

1차원 리스트인 normal_list의 경우 내장함수 max()를 사용하면 아주 쉽게 최댓값을 구해낼 수 있지만, 우리의 미친놈 crazy_list는 그렇게 호락호락하지가 않습니다. 

 

 

평탄화 함수 작업하기

리스트 안에 리스트가 들어가 있는 중첩 리스트를 하나의 리스트로 평탄화(Flatten)하는 회귀함수를 작성해 봅니다. 먼저 weird_list를 가지고 생각을 열어 보겠습니다.

weird_list = [1, 2, [3], 4, 5, [6], 7, [8, 9, 10]]

def easy_flatten(nested_list):
    result = []

    for element in nested_list:
        if isinstance(element, list):
            result.extend(element)
        else:
            result.append(element)

    return result
    
easy_flatten(weird_list)
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

 

weird_list의 원소를 하나씩 점검합니다. 만약 원소가 리스트라면 result에 그 리스트를 extend해서 합쳐 주고, 원소가 리스트가 아니라면 해당 원소를 append를 이용해서 result에 추가해 줍니다. weird_list가 예쁘게 평탄화된 것을 확인할 수 있습니다.

 

 

그렇다면 easy_flatten 함수를 가지고 crazy_list도 평탄화 작업이 될까요?

crazy_list = [1, 2, [[3], 4, 5], [[6], 7, [8, [9, [10]]]]]
easy_flatten(crazy_list)
# [1, 2, [3], 4, 5, [6], 7, [8, [9, [10]]]]

 

우리가 원하는 결과가 나오지 않았습니다. 중첩에 중첩이 여러번 반복되는 경우를 생각해주지 못했기 때문입니다. 그렇다면 여러 개의 중첩을 풀어주는 함수를 어떻게 작성할 수 있을까요? 중첩이 몇 번 반복되는지 알 수 없다는 점에 착안하여, 재귀함수(recursion)를 작성해 보겠습니다.

 

 

재귀함수를 적용한 Flatten 함수

def hard_flatten(nested_list):
    result = []

    for element in nested_list:
        if isinstance(element, list):
            # 유일하게 달라진 부분 - element 대신 hard_flatten(element)❤️
            result.extend(hard_flatten(element))
        else:
            result.append(element)

    return result

 

만약 nested_list의 element가 list 객체라면 result에 hard_flatten(element)를 추가해주도록 재귀함수가 작성되었습니다. 이렇게 여러 번 중첩이 반복되는 경우 자기 자신을 호출함으로써 문제를 간단히 해결할 수 있게 되었습니다.

crazy_list = [1, 2, [[3], 4, 5], [[6], 7, [8, [9, [10]]]]]
hard_flatten(crazy_list)
#[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

 

우리가 원하는 결과값을 얻었네요!

이 리스트 평탄화 함수는 여러 번 작성해보시면서 외워 두시면 필요할 때 요긴히 사용하실 수 있을거에요.

 

 

(적용 예시)

 
# 리스트 k에서 하트를 모두 제거한 1차원 리스트를 출력하세요
k = [['a', 'b', ['🩶', 'd']], 'e', 'f', '🩶', [['🩶'], 'i']]

[element for element in hard_flatten(k) if element != '🩶']
# ['a', 'b', 'd', 'e', 'f', 'i']
 

 

감사합니당!

+ Recent posts