Computer >> 컴퓨터 >  >> 프로그래밍 >> Python

Python 리스트 평탄화(Flatten) 완벽 가이드: 3가지 핵심 방법

리스트 평탄화(flattening)란 리스트에서 차원을 하나 제거하는 과정을 말합니다. 여기서 차원이란 리스트 안의 특정 요소에 접근하기 위해 필요한 추가 좌표를 의미합니다. Python에서는 리스트 컴프리헨션(list comprehension), 중첩 for 루프, itertools.chain() 메서드 세 가지 방법으로 리스트를 평탄화할 수 있습니다.

Python 리스트는 여러 개의 차원을 가질 수 있습니다. 즉, 리스트 안에 또 다른 리스트가 들어 있는 구조입니다. 이러한 리스트를 흔히 '중첩 리스트(nested list)'라고 부르며, 이를 다시 일반적인 단일 리스트로 변환할 수 있습니다.

즉, 리스트 안의 리스트에 있는 모든 값을 하나의 리스트로 옮기는 것이며, 이 과정을 '리스트 평탄화'라고 합니다.

Python 리스트 평탄화의 3가지 방법

Python에서 리스트를 평탄화하는 방법은 크게 세 가지입니다.

  • 리스트 컴프리헨션(list comprehension) 사용
  • 중첩 for 루프(nested for loop) 사용
  • itertools.chain() 메서드 사용

이 글에서는 위 세 가지 방법을 각각 살펴보고, 실제 예제 코드와 함께 어떻게 동작하는지 자세히 알아보겠습니다.

방법 1: 리스트 컴프리헨션으로 평탄화하기

Python의 리스트 컴프리헨션은 기존 리스트의 내용을 기반으로 새로운 리스트를 정의하는 문법입니다. 조건이나 변환 규칙을 지정하여 새 리스트에 들어갈 각 요소를 원하는 대로 가공할 수 있습니다.

컴프리헨션은 본질적으로 for 루프를 사용해 리스트를 순회하고 새 리스트를 생성하는 과정을 축약한 '문법적 설탕(syntactic sugar)'입니다. 따라서 for 루프와 근본적으로 같은 방식으로 작동하지만 더 간결한 문법을 사용합니다.

다음은 리스트 컴프리헨션의 기본 문법입니다.

numbers = [1, 2, 3]

new_numbers = [number * 2 for number in numbers]

위 코드는 'numbers' 리스트의 각 숫자에 2를 곱한 새로운 리스트를 생성합니다.

예제: 리스트 컴프리헨션 활용

레스토랑에서 판매하는 샌드위치 속 재료를 담은 리스트가 있다고 가정해 보겠습니다. 현재 리스트 구조는 다음과 같습니다.

foods = [
	["Tomato and Cucumber", "Hummus, Beetroot, and Lettuce"],
	["Cheese", "Egg"],
	["Ham", "Bacon", "Chicken Club", "Tuna"]
]

첫 번째 리스트는 비건(vegan) 샌드위치 재료, 두 번째 리스트는 베지테리언(vegetarian) 샌드위치 재료, 세 번째 리스트는 고기가 포함된 샌드위치 재료를 담고 있습니다.

우리는 이 리스트를 하나의 단일 리스트로 변환하고 싶습니다. 일반적으로 1차원 리스트가 2차원 리스트보다 다루기 훨씬 쉽기 때문입니다. 이 경우 굳이 두 개의 차원을 유지할 필요가 없습니다. 리스트 컴프리헨션을 사용하면 두 번째 차원을 손쉽게 제거할 수 있습니다.

new_foods = [food for sublist in foods for food in sublist]
print(new_foods)

이 리스트 컴프리헨션은 'foods' 변수의 모든 하위 리스트를 순회하면서, 각 값을 하나의 메인 리스트에 추가합니다. 리스트가 생성되면 콘솔에 출력됩니다.

코드를 실행해 결과를 확인해 보겠습니다.

['Tomato and Cucumber', 'Hummus, Beetroot, and Lettuce', 'Cheese', 'Egg', 'Ham', 'Bacon', 'Chicken Club', 'Tuna']

중첩 리스트가 성공적으로 하나의 평탄한 리스트로 변환되었습니다. 모든 재료가 세 개의 리스트가 아닌 하나의 리스트 안에 담겨 있습니다.

방법 2: 중첩 for 루프로 평탄화하기

동일한 결과는 중첩 for 루프를 통해서도 얻을 수 있습니다. '중첩 for 루프'란 말 그대로 'for 루프 안에 또 다른 for 루프가 있는 구조'를 의미합니다. 앞서 작성한 리스트 컴프리헨션은 사실상 아래의 for 루프를 다른 방식으로 표현한 것입니다.

new_foods = []
for sublist in foods:
	for food in sublist:
		new_foods.append(food)

print(new_foods)

이 코드는 두 개의 for 루프를 사용해 원래 리스트의 각 하위 리스트와 그 안의 모든 요소를 순회합니다. 실행 결과를 확인해 보겠습니다.

['Tomato and Cucumber', 'Hummus, Beetroot, and Lettuce', 'Cheese', 'Egg', 'Ham', 'Bacon', 'Chicken Club', 'Tuna']

리스트 컴프리헨션과 완전히 동일한 결과가 출력됩니다. 다만 대부분의 경우 중첩 for 루프보다 리스트 컴프리헨션이 더 나은 선택입니다. 코드가 더 짧고, 읽기도 더 쉽기 때문입니다.

방법 3: itertools.chain()으로 평탄화하기

itertools는 Python 표준 라이브러리에 포함된 모듈로, 반복 가능한 객체(iterable)와 제너레이터(generator)를 쉽게 다룰 수 있도록 다양한 메서드를 제공합니다.

여기서는 그중 chain() 메서드만 살펴보겠습니다. 이 메서드는 리스트의 리스트를 인자로 받아 평탄화된 리스트를 반환합니다.

먼저 import 문을 사용해 itertools 모듈을 코드에 불러옵니다.

import itertools

다음으로 리스트의 리스트를 정의하고, chain() 메서드를 사용해 리스트를 평탄화합니다.

foods = [
	["Tomato and Cucumber", "Hummus, Beetroot, and Lettuce"],
	["Cheese", "Egg"],
	["Ham", "Bacon", "Chicken Club", "Tuna"]
]

new_foods = itertools.chain(*foods)

코드에서 *(별표) 기호는 리스트 언패킹(unpacking)을 수행합니다. 이를 통해 리스트가 chain() 메서드가 처리할 수 있는 개별 함수 인자로 변환됩니다.

chain() 메서드는 itertools.chain 객체를 반환합니다. 따라서 평탄화된 리스트를 실제로 확인하려면 이 객체를 list() 메서드로 변환해야 합니다.

print(list(new_foods))

코드를 실행해 보겠습니다.

['Tomato and Cucumber', 'Hummus, Beetroot, and Lettuce', 'Cheese', 'Egg', 'Ham', 'Bacon', 'Chicken Club', 'Tuna']

리스트가 성공적으로 평탄화되었습니다.

itertools는 리스트 평탄화에 효과적인 도구이지만, 앞서 소개한 두 가지 방법보다는 진입 장벽이 다소 높습니다. 코드에 새로운 의존성(dependency)을 추가하기 위해 itertools를 임포트해야 하고, chain() 메서드에 사용된 언패킹 개념은 초보자에게 다소 어렵게 느껴질 수 있기 때문입니다.

마무리: 어떤 방법을 선택해야 할까?

Python 리스트는 리스트 컴프리헨션, 중첩 for 루프, itertools.chain() 메서드 세 가지 방식으로 평탄화할 수 있습니다. 그중 리스트 컴프리헨션이 가장 '파이썬답다(Pythonic)'고 평가받는 방법으로, 대부분의 상황에서 권장됩니다.

중첩 for 루프도 충분히 효과적이지만, 리스트 컴프리헨션보다 더 많은 코드 줄을 소모합니다. itertools.chain() 역시 강력한 방법이지만 초보자가 이해하기에는 다소 어려울 수 있습니다.

Python이 기본적으로 필요한 도구를 모두 제공하는 상황에서, 굳이 새로운 라이브러리인 itertools를 임포트할 필요는 없습니다. 특별한 이유가 없다면 리스트 컴프리헨션을 우선적으로 사용하는 것이 좋습니다.

Python 학습을 더 깊이 있게 진행하고 싶다면, 공식 문서와 함께 다양한 실습 예제를 통해 리스트 컴프리헨션과 반복문 활용 능력을 꾸준히 연습해 보시길 권장합니다.