Python 중첩 리스트 펼치기(Nested List Flattening): 복잡한 데이터 구조를 간단하게!

중첩 리스트(Nested List)는 리스트 안에 또 다른 리스트가 포함된 데이터 구조로, 다차원 데이터를 표현하거나 복잡한 데이터 처리를 할 때 자주 등장합니다. 그러나 중첩된 리스트는 데이터를 다루는 과정을 복잡하게 만들기 때문에, 이를 한 차원으로 펼치는 작업(Flattening)이 필요합니다.

이번 글에서는 중첩 리스트를 간단히 펼치는 방법과 함께, 딥러닝이나 데이터 전처리 같은 실제 코드 상황에서 유용하게 활용할 수 있는 예제를 소개합니다.


중첩 리스트란 무엇인가?

중첩 리스트는 리스트 안에 또 다른 리스트가 포함된 구조를 말합니다. 예를 들어:

nested_list = [[1, 2, [3, 4]], [5, 6], 7]

위와 같은 중첩 리스트는 다차원 데이터를 표현하거나 계층적인 정보를 담는 데 유용하지만, 데이터를 한 차원으로 펼쳐야 할 경우 복잡한 작업이 필요합니다.


기본 중첩 리스트 펼치기 방법: 재귀 함수 사용

중첩 리스트를 펼치려면 재귀 함수를 사용하는 것이 기본적이고 가장 직관적인 방법입니다. 아래 예제는 중첩 리스트를 한 차원으로 만드는 간단한 재귀 함수를 보여줍니다.

def flatten_list(nested):
    for item in nested:
        if isinstance(item, list):
            yield from flatten_list(item)  # 재귀적으로 내부 리스트를 처리
        else:
            yield item

# 실행 예시
nested_list = [[1, 2, [3, 4]], [5, 6], 7]
flattened = list(flatten_list(nested_list))
print(flattened)

출력 결과

[1, 2, 3, 4, 5, 6, 7]

설명

  • flatten_listyield from을 사용해 중첩된 리스트를 재귀적으로 순회합니다.
  • 리스트가 아니면 값을 반환(yield)하고, 리스트라면 다시 함수 호출을 통해 내부 리스트를 처리합니다.
  • 이 방식은 중첩된 깊이에 상관없이 모든 데이터를 한 차원으로 펼칠 수 있습니다.

딥러닝 데이터 전처리에서 중첩 리스트 펼치기

딥러닝 모델을 다룰 때, 중첩된 리스트 형태의 데이터를 Flatten해야 하는 경우가 자주 있습니다. 예를 들어, 이미지 데이터나 텍스트 데이터가 계층적으로 저장되어 있을 때 이를 한 차원으로 펼쳐 모델에 입력해야 합니다.

예제: 중첩 리스트로 저장된 배치 데이터 펼치기

def flatten_batch_data(batch_data):
    for batch in batch_data:
        if isinstance(batch, list):
            yield from flatten_batch_data(batch)  # 재귀적으로 배치 데이터를 펼침
        else:
            yield batch

# 딥러닝 배치 데이터 예시
nested_batches = [
    [[1, 2], [3, 4]],  # 배치 1
    [5, [6, 7]],       # 배치 2
    8                  # 배치 3
]

flattened_data = list(flatten_batch_data(nested_batches))
print(flattened_data)

출력 결과

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

설명

  • flatten_batch_data는 딥러닝 배치 데이터를 한 차원으로 펼칩니다.
  • 모델에 데이터를 입력할 때 중첩된 구조를 제거해야 한다면 이런 방식이 유용합니다.

리스트 컴프리헨션으로 중첩 리스트 펼치기

리스트 컴프리헨션은 짧고 간결한 코드 작성을 가능하게 하지만, 중첩 구조가 깊지 않을 때 적합합니다. 아래는 간단한 2차원 리스트를 펼치는 예제입니다.

nested_list = [[1, 2], [3, 4], [5, 6]]

# 리스트 컴프리헨션을 사용한 플래튼
flattened = [item for sublist in nested_list for item in sublist]
print(flattened)

출력 결과

[1, 2, 3, 4, 5, 6]

설명

  • 리스트 컴프리헨션을 사용하면 중첩 리스트의 각 요소를 한 줄로 펼칠 수 있습니다.
  • 하지만 깊이가 2 이상인 중첩 리스트에는 적합하지 않습니다. 이 경우 재귀 함수가 필요합니다.

중첩 리스트를 펼칠 때 itertools 활용하기

Python의 itertools 모듈은 복잡한 데이터 처리에 유용하며, 중첩 리스트를 처리할 때도 활용할 수 있습니다.

from itertools import chain

nested_list = [[1, 2], [3, 4], [5, 6]]

# itertools.chain으로 리스트 펼치기
flattened = list(chain.from_iterable(nested_list))
print(flattened)

출력 결과

[1, 2, 3, 4, 5, 6]

설명

  • itertools.chain.from_iterable은 리스트의 각 요소를 펼쳐줍니다.
  • 리스트의 깊이가 2차원인 경우 유용하며, 이 이상의 깊이는 재귀가 필요합니다.

실전 응용: 중첩된 JSON 데이터를 펼쳐보기

복잡한 JSON 데이터를 다룰 때 중첩된 리스트를 펼쳐야 할 경우도 있습니다. 아래는 JSON 데이터에서 리스트를 Flatten하는 예제입니다.

import json

# JSON 데이터 예시
json_data = '''
[
    {"name": "Alice", "scores": [10, 20, [30, 40]]},
    {"name": "Bob", "scores": [50, [60, 70]]}
]
'''

# JSON 파싱 및 플래튼
data = json.loads(json_data)

def flatten_json_scores(data):
    for entry in data:
        yield entry["name"], list(flatten_list(entry["scores"]))

# 실행
flattened_scores = list(flatten_json_scores(data))
print(flattened_scores)

출력 결과

[('Alice', [10, 20, 30, 40]), ('Bob', [50, 60, 70])]

설명

  • JSON 데이터를 파싱한 뒤 flatten_list를 사용해 각 scores를 펼칩니다.
  • 복잡한 JSON 데이터 구조를 Flatten하여 데이터를 쉽게 다룰 수 있도록 합니다.

결론: 중첩 리스트 펼치기의 다양한 활용

Python에서 중첩 리스트를 펼치는 작업은 데이터 전처리, 딥러닝, JSON 데이터 처리 등 다양한 상황에서 필수적입니다. 이번 가이드에서 소개한 방법들을 상황에 맞게 선택해 사용하세요:

  1. 재귀 함수: 깊이가 무제한인 중첩 리스트를 펼칠 때.
  2. 리스트 컴프리헨션: 깊이가 2인 리스트를 간단히 펼칠 때.
  3. itertools.chain: 2차원 리스트를 빠르게 펼칠 때.
  4. 딥러닝 데이터 처리: 배치 데이터를 펼쳐 모델에 입력할 때.

Python ChainMap으로 중첩 딕셔너리 깔끔하게 다루기: 딕셔너리 합치기의 종결자

파이썬에서 딕셔너리는 매우 자주 사용되는 데이터 구조입니다. 하지만, 딕셔너리가 중첩되거나 여러 딕셔너리를 한꺼번에 다뤄야 할 경우, 코드가 복잡해지고 비효율적이 될 수 있습니다. 이럴 때, collections 모듈의 ChainMap은 이런 문제를 해결할 수 있는 강력한 도구가 됩니다.

ChainMap은 여러 딕셔너리를 하나의 맵처럼 처리할 수 있도록 해줍니다. 이 글에서는 ChainMap의 개념과 실제 코딩 상황에서 어떻게 유용하게 사용할 수 있는지 예제를 통해 알아보겠습니다.


1. ChainMap이란 무엇인가?

ChainMap은 Python의 collections 모듈에 포함된 클래스입니다. 여러 딕셔너리를 체인처럼 연결하여 하나의 딕셔너리처럼 동작하게 합니다. 연결된 딕셔너리에서 데이터를 검색할 때, 첫 번째 딕셔너리부터 차례대로 검색하여 값을 반환합니다.

주요 특징

  • 연결된 딕셔너리를 수정하지 않고 논리적으로 합칠 수 있습니다.
  • 데이터가 여러 계층(레이어)로 구성된 상황에서 매우 유용합니다.
  • 검색 순서ChainMap에 딕셔너리를 추가한 순서를 따릅니다.

2. 기본 사용법: 여러 딕셔너리 연결하기

먼저 ChainMap의 기본 사용법을 살펴보겠습니다. 두 개 이상의 딕셔너리를 연결하여 하나의 맵처럼 사용하는 간단한 예제입니다.

from collections import ChainMap

# 두 개의 딕셔너리
defaults = {"theme": "light", "show_line_numbers": True, "font_size": 12}
user_settings = {"theme": "dark", "font_size": 14}

# ChainMap으로 연결
config = ChainMap(user_settings, defaults)

# 값 검색
print(config["theme"])  # dark (user_settings 우선)
print(config["show_line_numbers"])  # True (defaults에서 가져옴)

출력 결과

dark
True

설명

  • ChainMapuser_settingsdefaults를 연결합니다.
  • 키를 검색할 때, user_settings에서 먼저 찾고, 없으면 defaults에서 찾습니다.
  • 이렇게 하면 기본 설정값과 사용자 설정값을 효율적으로 병합할 수 있습니다.

3. 실제 코드 상황: 설정값 처리하기

애플리케이션에서 기본 설정값을 정의하고, 사용자 설정값으로 덮어쓰기 해야 하는 상황은 매우 흔합니다. ChainMap을 사용하면 이를 간단하게 처리할 수 있습니다.

from collections import ChainMap

def get_final_settings(defaults, user_overrides):
    # ChainMap으로 기본값과 사용자 값을 합침
    return ChainMap(user_overrides, defaults)

# 기본 설정값
default_config = {"theme": "light", "language": "English", "autosave": True}

# 사용자 지정값
user_config = {"theme": "dark", "autosave": False}

# 최종 설정값
final_config = get_final_settings(default_config, user_config)

print(final_config["theme"])  # dark
print(final_config["language"])  # English
print(final_config["autosave"])  # False

출력 결과

dark
English
False

설명

  • get_final_settings 함수는 ChainMap을 사용해 사용자 설정값(user_config)이 기본 설정값(default_config)을 덮어쓰도록 만듭니다.
  • 이 방식은 중첩된 설정값을 다룰 때, 코드를 깔끔하고 간결하게 유지할 수 있습니다.

4. 중첩 딕셔너리에서 데이터 검색하기

중첩된 딕셔너리에서 특정 값을 검색해야 하는 상황에서도 ChainMap은 유용합니다. 아래는 중첩된 환경 변수 맵에서 값을 검색하는 예제입니다.

from collections import ChainMap

# 환경 변수 설정
global_env = {"PATH": "/usr/bin", "USER": "admin"}
local_env = {"USER": "local_user", "EDITOR": "vim"}
runtime_env = {"DEBUG": "True", "USER": "runtime_user"}

# ChainMap으로 연결
env = ChainMap(runtime_env, local_env, global_env)

# 환경 변수 검색
print(env["USER"])  # runtime_user (runtime_env 우선)
print(env["PATH"])  # /usr/bin (global_env에서 가져옴)
print(env["DEBUG"])  # True

출력 결과

runtime_user
/usr/bin
True

설명

  • runtime_env, local_env, global_env을 연결하여 환경 변수 값을 우선순위에 따라 검색합니다.
  • ChainMap은 키를 상위 딕셔너리부터 차례로 검색하며, 최상위 딕셔너리(runtime_env)의 값을 우선합니다.

5. ChainMap에서 데이터 수정하기

ChainMap은 연결된 딕셔너리의 첫 번째 맵에 데이터를 수정합니다. 이를 활용하면 특정 딕셔너리의 값을 효율적으로 업데이트할 수 있습니다.

from collections import ChainMap

# 기본값과 사용자 설정
defaults = {"theme": "light", "font_size": 12}
user_settings = {"theme": "dark"}

# ChainMap 생성
config = ChainMap(user_settings, defaults)

# 값 수정
config["font_size"] = 14  # user_settings에 추가됨
print(config["font_size"])  # 14
print(user_settings)  # {"theme": "dark", "font_size": 14}

출력 결과

14
{'theme': 'dark', 'font_size': 14}

설명

  • config["font_size"] = 14user_settings에 값을 추가하거나 수정합니다.
  • ChainMap을 통해 상위 딕셔너리의 데이터를 쉽게 변경할 수 있습니다.

6. ChainMap으로 다중 소스 데이터 병합하기

여러 데이터 소스를 병합해야 할 때도 ChainMap은 유용합니다. 예를 들어, 여러 데이터베이스에서 정보를 가져와 합칠 수 있습니다.

from collections import ChainMap

# 데이터 소스
db1 = {"id": 1, "name": "Alice"}
db2 = {"email": "alice@example.com"}
db3 = {"phone": "123-456-7890"}

# ChainMap으로 병합
merged_data = ChainMap(db1, db2, db3)

print(merged_data["name"])  # Alice
print(merged_data["email"])  # alice@example.com
print(merged_data["phone"])  # 123-456-7890

출력 결과

Alice
alice@example.com
123-456-7890

설명

  • db1, db2, db3의 데이터를 병합하여 하나의 데이터 소스처럼 사용할 수 있습니다.
  • 기존 딕셔너리를 수정하지 않으므로 데이터의 원본을 안전하게 유지할 수 있습니다.

7. 딕셔너리 우선순위 변경하기

ChainMap은 연결된 딕셔너리의 순서를 바꿔 우선순위를 변경할 수 있습니다. 이를 활용해 동적으로 데이터 흐름을 조정할 수 있습니다.

from collections import ChainMap

# 설정값
defaults = {"theme": "light", "autosave": True}
user_settings = {"theme": "dark"}
runtime_settings = {"autosave": False}

# ChainMap 생성
config = ChainMap(runtime_settings, user_settings, defaults)

# 동적으로 순서 변경
config = config.new_child({"theme": "blue"})
print(config["theme"])  # blue

출력 결과

blue

설명

  • config.new_child()를 사용해 새로운 딕셔너리를 추가하고 우선순위를 변경합니다.
  • ChainMap의 동적인 순서 변경은 상황에 따라 데이터 흐름을 유연하게 조정할 수 있도록 도와줍니다.

요약: ChainMap으로 딕셔너리를 효율적으로 다루는 법

Python의 ChainMap은 중첩 딕셔너리를 간단하고 효율적으로 관리할 수 있도록 도와줍니다. 특히 다음과 같은 상황에서 유용합니다:

  1. 여러 데이터 소스를 병합할 때.
  2. 기본값과 사용자 설정값을 관리할 때.
  3. 환경 변수나 설정 계층을 처리할 때.
  4. 데이터 우선순위를 동적으로 조정할 때.

+ Recent posts