3. 자료구조

3.2. 리스트

3.2.1. 리스트 생성

  • 리스트(list)
    • 여러 개의 값을 저장할 수 있는 컬렉션 자료형(collection data type)
    • 순서가 존재하는 시퀀스 자료형(sequence data type)
    • 한 번 생성된 이후에도 수정할 수 있는 가변형(mutable) 객체
    • 정수형, 부동소수점형, 문자열형, bool형, 리스트, 튜플, 딕셔너리 등 다양한 자료형을 저장할 수 있는 컨테이너형 객체


  • 리스트 생성
    • 리터럴 방식(list literal)
      • [] 사용하여 리스트를 직접 정의하는 방법
      • [] 안에 원소(element)를 ,로 구분하여 나열함
    • 생성자 방식(list constructor)
      • list() 함수를 사용하여 리스트를 생성하는 방법
      • 다른 iterable 객체(리스트, 튜플, 세트, 딕셔너리, 문자열 등)을 인수로 받아 리스트를 생성할 때 유용함
# 리스트 생성1
lst = [1, 2, 3, 4, 5]
print(lst)
[1, 2, 3, 4, 5]
# 리스트 생성2
lst = list([1, 2, 3, 4, 5])
print(lst)
[1, 2, 3, 4, 5]
# 리스트 생성3 : 리스트는 다양한 자료형을 저장할 수 있음
lst = [1, 3.14, "Hello", True, [11, 12, 13]]
print(lst)
[1, 3.14, 'Hello', True, [11, 12, 13]]
# 리스트 생성4 : iterable 객체 사용
lst = list("Hello")
print(lst)
['H', 'e', 'l', 'l', 'o']
# 리스트 생성5 : iterable 객체 사용
lst = list(range(10))
print(lst)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]


3.2.2. 리스트 연산

  • 문자열 연산과 동일함
  • 리스트 연결 연산자 + : 두 리스트를 연결해서 새로운 리스트 생성
  • 리스트 반복 연산자 * : 리스트를 숫자만큼 반복하여 새로운 리스트 생성 - (예) 리스트*숫자
  • 리스트 전개 연산자 * : 리스트의 개별 원소를 분리하여 다른 리스트로 전개 - (예) [*리스트]
# 리스트 연결
lst1 = [1, 2, 3]
lst2 = [11, 12, 13]
print(lst1 + lst2)
[1, 2, 3, 11, 12, 13]
# 리스트 반복
print(lst1 * 3)
[1, 2, 3, 1, 2, 3, 1, 2, 3]
# 리스트 전개
lst = [1, 2, 3]
print([lst, lst])
print([*lst, *lst])
[[1, 2, 3], [1, 2, 3]]
[1, 2, 3, 1, 2, 3]


3.2.3. 리스트 패킹과 언패킹

3.2.3.1. 패킹과 언패킹 개념

  • 패킹(packing) : 여러 개의 데이터를 한 변수에 묶어 할당하는 것
  • 언패킹(unpacking)
    • 한 변수에 묶인 데이터를 개별적인 변수들에 할당하는 것
    • 언패킹 시 기본적으로 변수 개수가 맞아야 하지만, *를 사용하면 가변 개수도 처리할 수 있음
# 리스트 패킹
lst = [1, 2, 3]
print(lst)
[1, 2, 3]
# 리스트 언패킹
lst = [1, 2, 3]
x, y, z = lst
print(x)
print(y)
print(z)
1
2
3
# 언패킹 시 변수 개수 불일치 오류
#a, b = [1, 2, 3]
#x, y, z = [4, 5]
# 언패킹 시 *을 이용한 가변 개수 처리
*x, y, z = [5, 6, 7, 8]
print(x)
print(y)
print(z)
[5, 6]
7
8


3.2.3.2. 함수의 인수에서 패킹과 언패킹

  • 가변 인수를 사용하면 전달된 인수들은 튜플로 패킹됨
  • *를 사용하여 리스트를 언패킹하여 함수에 전달함


  • [참고] 함수의 인수
종류 내용
키워드 인수 함수의 인터페이스에서 저장한 변수 이름을 사용하여 함수의 인수를 지정하는 방법
디폴트 인수 별도의 인수 값이 입력되지 않을 때 인터페이스 선언에서 지정한 초기 값을 사용하는 방법
가변 인수 함수의 인터페이스에서 지정하는 변수 이외의 추가 변수를 함수에 입력할 수 있도록 지원하는 방법
키워드 가변 인수 매개변수의 이름을 따로 지정하지 않고 입력하는 방법
# 키워드 인수(keyword arguments)
def greet(name, greeting):
    print(f"{greeting}, {name}!")

greet(name="Alice", greeting="Hi")
Hi, Alice!
# 디폴트 인수(default arguments)
def greet(name, greeting="Hello"):
    print(f"{greeting}, {name}!")

greet("Alice")
greet("Bob", "Hi")
Hello, Alice!
Hi, Bob!
# 가변 인수(variable-length arguments) : *로 표현
# 함수에 전단하는 인수의 개수가 불확실할 때 사용
# 가변 인수는 반드시 일반적인 키워드 인수에 대한 선언이 모두 끝난 후 마지막에 선언되어야 함
def sum_numbers(*args):
    return sum(args)

print(sum_numbers(1, 2, 3))
print(sum_numbers(5, 10))
6
15
# 키워드 가변 인수(keyword variable-length arguments) : **로 표현
# 입력 받은 값은 딕셔너리 지료형임
# 키워드 가변 인수는 반드시 모든 매개변수의 마지막에 선언되어야 함
def print_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_info(name="Alice", age=25)
name: Alice
age: 25
# 가변 인수와 패킹, 언패킹
def print_args(*args):
    print("Packed arguments:", args)                        # 전달된 값이 튜플로 패킹됨

print_args(1, 2, 3, 4)

lst = [11, 12, 13]
print_args(lst)                                             # 리스트 자체를 인수로 전달
print_args(*lst)                                            # 리스트를 언패킹하여 함수에 전달
Packed arguments: (1, 2, 3, 4)
Packed arguments: ([11, 12, 13],)
Packed arguments: (11, 12, 13)
# 키워드 인수와 언패킹
def sum_numbers(a, b, c, d):
    return a + b + c + d

lst = [1, 2, 3, 4]

print(sum_numbers(lst[0], lst[1], lst[2], lst[3]))          # 개별 원소를 인수로 전달
print(sum_numbers(*lst))                                    # 리스트를 언패킹하여 함수에 전달
10
10


3.2.3.3. 함수의 반환값에서 패킹과 언패킹

  • 함수에서 여러 개의 값을 반환하면 자동으로 튜플로 패킹됨
  • 반환 값을 리스트로 묶어(패킹) 반환할 수도 있음
  • 함수가 반환한 리스트를 언패킹하여 개별 변수에 할당할 수 있음
# 반환값과 패킹, 언패킹
def get_student_info():
    return ["Alice", 20, "Statistics"]                      # 리스트로 패킹함

# 패킹된 값 출력
info = get_student_info()
print(info)

# 언패킹하여 개별 변수에 할당
name, age, major = get_student_info()
print(name)
print(age)
print(major)
['Alice', 20, 'Statistics']
Alice
20
Statistics


3.2.4. 리스트 인덱싱과 슬라이싱

  • 문자열과 문법이 동일함
  • 인덱싱(indexing) : [index]를 사용하여 리스트의 특정 위치에 있는 원소에 접근함
  • 슬라이싱(slicing) : [(start index):(stop index)]를 사용하여 리스트의 일부 원소를 추출함
    • list[(start index):(end index)] = iterable를 사용하면 start:end로 지정한 부분이 iterable 객체의 모든 원소로 대체됨
    • 슬라이싱을 이용하여 기존 리스트의 일부를 삭제하거나, 새로운 원소를 추가 및 변경할 수 있음

# 리스트 인덱싱1
lst = [10, 20, 30, 40, 50]
print(lst[0])
10
# 리스트 인덱싱2
lst = [10, 20, 30, 40, 50]
print(lst[-1])
50
# 리스트 접근 연산자 []를 이중으로 사용할 수 있음
lst = [1, 3.14, "Hello", True, [11, 12, 13]]
print(lst[2])
print(lst[2][0])
Hello
H
# 리스트 슬라이싱1
lst = [10, 20, 30, 40, 50]
print(lst[0:2])
[10, 20]
# 리스트 슬라이싱2
lst = [10, 20, 30, 40, 50]
print(lst[:2])
print(lst[2:])
print(lst[::2])
[10, 20]
[30, 40, 50]
[10, 30, 50]
# 리스트는 가변형 객체이므로 인덱스를 이용한 수정이 가능함
lst = [10, 20, 30, 40, 50]
lst[2] = 3
print(lst)
[10, 20, 3, 40, 50]
# 슬라이싱을 이용한 원소 삭제
lst = [10, 20, 30, 40, 50]
lst[1:4] = []                              # 빈 리스트를 할당하면 해당 부분이 삭제됨
print(lst)
[10, 50]
# 슬라이싱을 이용한 원소 추가
lst = [10, 20, 30, 40, 50]
lst[1:1] = [6, 7]                          # 인덱스 1 위치에 [6, 7]의 원소를 개별적으로 추가
print(lst)
[10, 6, 7, 20, 30, 40, 50]
# 슬라이싱을 이용한 원소 수정
lst = [10, 20, 30, 40, 50]
lst[1:3] = [6, 7, 8]                       # 인덱스 1~2 부분을 [6, 7, 8]로 수정
print(lst)
[10, 6, 7, 8, 40, 50]


3.2.5. 중첩 리스트

  • 리스트 안에 또 다른 리스트가 포함된 구조
  • 2차원 리스트
    • 모든 내부 리스트가 동일한 길이를 가진 경우
    • 일반적으로 행(row)과 열(column)로 이루어짐
    • 행렬(matrix)처럼 활용하거나, 데이터를 일정한 구조로 관리할 때 유용함
# 중첩 리스트
nested_lst = [
    [1, 2, 3],
    [4, 5, 6, 7],
    [8, 9]
]

print(nested_lst)
[[1, 2, 3], [4, 5, 6, 7], [8, 9]]
for items in nested_lst:
    for item in items:
        print(item)
1
2
3
4
5
6
7
8
9
# 2차원 리스트
kor = [80, 82, 75, 95]
eng = [67, 95, 84, 83]
math = [74, 88, 82, 76]
midterm = [kor, eng, math]
print(midterm)
[[80, 82, 75, 95], [67, 95, 84, 83], [74, 88, 82, 76]]
# 2차원 리스트 인덱싱
midterm[0][3]
95


3.2.6. 리스트 메소드

3.2.6.1. 리스트 추가

함수 설명
append() - 새로운 값을 기존 리스트의 맨 끝에 추가
extend() - 새로운 리스트를 기존 리스트에 추가(덧셈 연산과 같은 효과)
insert() - 기존 리스트의 i번째 인덱스에 새로운 값을 추가
- i번째 인덱스를 기준으로 뒤쪽의 인덱스는 하나씩 밀림
# 리스트에 한 개의 원소 추가1
lst = [1, 2, 3]
lst.append(4)                              # 실행 결과로 아무것도 출력하지 않음
print(lst)                                 # lst 원본 값이 변함
[1, 2, 3, 4]
# 리스트에 한 개의 원소 추가2
# 원본 값 변화 없이 리스트에 한 개의 원소를 추가하려면 전개 연산자 *를 사용하면 됨
lst = [1, 2, 3]
new_lst = [*lst, 4]
print(lst)                                 # lst 원본 값이 변하지 않음
print(new_lst)
[1, 2, 3]
[1, 2, 3, 4]
# append() 함수는 여러 개의 인수를 입력하거나, 연속으로 사용할 수 없음
lst = [1, 2, 3]
#lst.append(4,5)
#lst.append(4).append(5)
# 리스트에 여러 개의 원소 추가
lst = [1, 2, 3]
lst.extend([11, 12])                       # 실행 결과로 아무것도 출력하지 않음
print(lst)                                 # lst1 원본 값이 변함
[1, 2, 3, 11, 12]
# extend() 함수와 리스트 연결 연산자 +의 차이
lst1 = [1, 2, 3]
lst2 = [11, 12, 13]
lst1 + lst2                                # 실행 결과가 출력됨
print(lst1)                                # lst1 원본 값이 변하지 않음
print(lst2)                                # lst2 원본 값이 변하지 않음
[1, 2, 3]
[11, 12, 13]
# 특정 위치에 한 개의 원소 추가
lst = [1, 2, 3]
lst.insert(0, 100)                         # 실행 결과로 아무것도 출력하지 않음
print(lst)                                 # lst 원본 값이 변함
[100, 1, 2, 3]
# 특정 위치에 여러 개의 원소 추가 : 슬라이싱을 이용
lst = [1, 2, 3]
lst[1:1] = [11, 12]                        # 인덱스 1의 위치에 리스트 [11, 12]의 원소를 개별적으로 삽입
print(lst)                                 # lst 원본 값이 변함
[1, 11, 12, 2, 3]


3.2.6.2. 리스트 삭제

함수 설명
pop() - 특정 인덱스 값을 삭제하고 그 값을 반환
- 인덱스를 생략하면 마지막 원소를 삭제
del 키워드 - 특정 인덱스 값을 삭제
- 리스트 객체 자체를 삭제 가능함
remove() - 리스트에서 첫 번째로 일치하는 값을 삭제
- 값으로 삭제하므로 인덱스를 사용하지 않음
clear() - 리스트의 모든 원소를 삭제
# 특정 인덱스 값 삭제1
lst = [1, 2, 3, 4, 5]
result = lst.pop(3)                        # 실행 결과로 삭제한 값을 반환
print(lst)                                 # lst 원본 값이 변함
print(result)
[1, 2, 3, 5]
4
# 특정 인덱스 값 삭제2
lst = [1, 2, 3, 4, 5]
result = lst.pop()                         # 인덱스를 생략하면 마지막 원소를 삭제 후 반환
print(lst)                                 # lst 원본 값이 변함
print(result)
[1, 2, 3, 4]
5
# 특정 인덱스 값 삭제3
lst = [1, 2, 3, 4, 5]
del lst[3]                                 # 실행 결과로 아무것도 반환하지 않음
print(lst)                                 # lst 원본 값이 변함
[1, 2, 3, 5]
# 리스트 객체 삭제
lst = [1, 2, 3, 4, 5]
del lst
#print(lst)
# 일치하는 값 삭제1
lst = [1, 2, 3, 4, 5]
lst.remove(4)                              # 실행 결과로 아무것도 출력하지 않음
print(lst)                                 # lst 원본 값이 변함
[1, 2, 3, 5]
# 일치하는 값 삭제2
lst = [1, 2, 3, 1, 4, 5, 1, 6, 7]
lst.remove(1)                              # 첫 번째로 일치하는 값만 삭제됨
print(lst)                                 # lst 원본 값이 변함
[2, 3, 1, 4, 5, 1, 6, 7]
# 리스트 모든 원소 삭제
lst = [1, 2, 3, 4, 5]
lst.clear()                                # 실행 결과로 아무것도 출력하지 않음
print(lst)                                 # lst 원본 값이 변함
[]


3.2.6.3. 리스트 재배열

함수 설명
reverse() - 리스트 원소를 역순으로 재배열
sort() - 리스트 원소를 오름차순(default)으로 정렬
- 내림차순으로 정렬하려면 reverse=True로 설정
# 리스트 역순 재배열
lst = [52, 273, 103, 32, 275, 1, 7]
lst.reverse()                              # 실행 결과로 아무것도 출력하지 않음
print(lst)                                 # lst 원본 값이 변함
[7, 1, 275, 32, 103, 273, 52]
# 리스트 정렬
lst = [52, 273, 103, 32, 275, 1, 7]
lst.sort()                                 # 실행 결과로 아무것도 출력하지 않음, 오름차순 정렬
print(lst)                                 # lst 원본 값이 변함

lst.sort(reverse=True)                     # 내림차순 정렬
print(lst)                                 # lst 원본 값이 변함
[1, 7, 32, 52, 103, 273, 275]
[275, 273, 103, 52, 32, 7, 1]


3.2.6.4. 리스트 검색

함수 설명
count() 리스트 내부에 특정 값이 몇 번 나타나는지 개수를 반환
index() 리스트 내부에 특정 값의 첫 번째 인덱스를 반환하며, 없으면 오류 발생
in 연산자 리스트 내부에 특정 값이 있다면 True, 없다면 False를 반환
# 특정 값 개수
lst = [1, 2, 3, 1, 4, 5, 1, 6, 7]
print(lst.count(1))
print(lst.count(9))
3
0
# 특정 값의 첫 번쩨 인덱스
lst = [1, 2, 3, 1, 4, 5, 1, 6, 7]
print(lst.index(1))
#print(lst.index(9))
0
# 특정 값 찾기
lst = [1, 2, 3, 4, 5]
print(3 in lst)
print(6 in lst)
True
False


3.2.6.5. 리스트 통계

함수 설명
len() 리스트 원소의 개수(리스트 길이) 반환
sum() 리스트 원소의 합계 반환
max() 리스트 원소 중 최대값 반환
min() 리스트 원소 중 최소값 반환
lst = [1, 2, 3, 4, 5]
print(len(lst))
print(sum(lst))
print(max(lst))
print(min(lst))
5
15
5
1


3.2.7. 리스트 컴프리헨션

[식 for 변수 in 반복범위 if 조건식]

  • 기존 리스트를 사용하여 간결하게 새로운 리스트를 생성하는 기법
  • 리스트와 for 문을 한 줄에 사용할 수 있어 코드가 더 직관적으로 작성됨
  • 필터링, 중첩 반복문, 이차원 리스트 등 다양한 방식으로 활용됨


  • 리스트 컴프리헨션은 수학에서 집합을 정의할 때 특정 조건을 만족하는 원소들의 집합으로 표현하는 방법과 유사함
    • 수학적 집합 : S = {x | x는 0 이상 10 이하의 정수}
    • 리스트 컴프리헨션 : [x for x in range(10)]
## 0~9 정수 저장
# 일반적인 반복문 + 리스트
result1 = []
for i in range(10):
    result1.append(i)

print(result1)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
## 0~9 정수 저장
# 리스트 컴프리헨션
result2 = [i for i in range(10)]
print(result2)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
## 짝수만 저장 (필터링, if 문과 함께 사용)
# 일반적인 반복문 + 리스트
result1 = []
for i in range(10):
    if i % 2 == 0:
        result1.append(i)

print(result1)
[0, 2, 4, 6, 8]
## 짝수만 저장 (필터링, if 문과 함께 사용)
# 리스트 컴프리헨션
result2 = [i for i in range(10) if i % 2 == 0]
print(result2)
[0, 2, 4, 6, 8]
## 두 개의 문자열 조합하여 새로운 리스트 생성 (중첩 반복문)
# 일반적인 반복문 + 리스트
word1 = "Hello"
word2 = "World"
result1 = []

for i in word1:
    for j in word2:
        result1.append(i+j)

print(result1)
['HW', 'Ho', 'Hr', 'Hl', 'Hd', 'eW', 'eo', 'er', 'el', 'ed', 'lW', 'lo', 'lr', 'll', 'ld', 'lW', 'lo', 'lr', 'll', 'ld', 'oW', 'oo', 'or', 'ol', 'od']
## 두 개의 문자열 조합하여 새로운 리스트 생성 (중첩 반복문)
# 리스트 컴프리헨션
word1 = "Hello"
word2 = "World"

result2 = [i+j for i in word1 for j in word2]
print(result2)
['HW', 'Ho', 'Hr', 'Hl', 'Hd', 'eW', 'eo', 'er', 'el', 'ed', 'lW', 'lo', 'lr', 'll', 'ld', 'lW', 'lo', 'lr', 'll', 'ld', 'oW', 'oo', 'or', 'ol', 'od']
# 리스트 컴프리헨션 (필터링 + 중첩 반복문)
case1 = ["A", "B", "C"]
case2 = ["D", "E", "A"]
result = [i + j for i in case1 for j in case2 if not(i==j)]
print(result)
['AD', 'AE', 'BD', 'BE', 'BA', 'CD', 'CE', 'CA']
## 리스트의 각 원소를 대문자, 소문자, 길이로 변환하여 이차원 리스트 생성 (이차원 리스트)
# 일반적인 반복문 + 리스트
words = "The quick brown fox jumps over the lazy dog".split()
word_info1 = []

for w in words:
    word_info1.append([w.upper(), w.lower(), len(w)])

word_info1
[['THE', 'the', 3],
 ['QUICK', 'quick', 5],
 ['BROWN', 'brown', 5],
 ['FOX', 'fox', 3],
 ['JUMPS', 'jumps', 5],
 ['OVER', 'over', 4],
 ['THE', 'the', 3],
 ['LAZY', 'lazy', 4],
 ['DOG', 'dog', 3]]
## 리스트의 각 원소를 대문자, 소문자, 길이로 변환하여 이차원 리스트 생성 (이차원 리스트)
# 리스트 컴프리헨션
words = "The quick brown fox jumps over the lazy dog".split()
word_info2 = [[w.upper(), w.lower(), len(w)] for w in words]
word_info2
[['THE', 'the', 3],
 ['QUICK', 'quick', 5],
 ['BROWN', 'brown', 5],
 ['FOX', 'fox', 3],
 ['JUMPS', 'jumps', 5],
 ['OVER', 'over', 4],
 ['THE', 'the', 3],
 ['LAZY', 'lazy', 4],
 ['DOG', 'dog', 3]]
# [주의] 반복문에서 대괄호의 위치에 따라 리스트의 구조가 달라짐
case1 = ["A", "B", "C"]
case2 = ["1", "2", "3"]

# 일차원 리스트, 앞의 for 문이 먼저 실행됨
result1 = [i + j for i in case1 for j in case2]
print(result1)

# 이차원 리스트, 뒤의 for 문이 바깥쪽에서 먼저 실행됨
result2 = [[i + j for i in case1] for j in case2]
print(result2)
['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3']
[['A1', 'B1', 'C1'], ['A2', 'B2', 'C2'], ['A3', 'B3', 'C3']]


(과제) 리스트 컴프리헨션 실습

  • 문제 1. 1부터 10까지의 제곱수를 리스트로 만들기 - [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
  • 문제 2. 문자열 words = [“apple”, “banana”, “cherry”, “durian”] 에서 첫 글자만 리스트로 만들기 - [‘a’, ‘b’, ‘c’, ‘d’]
  • 문제 3. 두 리스트 list1 = [1,2,3], list2 = [4,5,6] 의 각 원소를 곱하여 리스트로 만들기 - [4, 10, 18]
  • 문제 4. 1~20까지의 수 중에서 3의 배수만 리스트로 만들기 - [3, 6, 9, 12, 15, 18]
  • 문제 5. 문자열 words = [“alligator”, “bear”, “cat”, “dog”, “elephant”, “fox”] 에서 길이가 5 이상인 단어만 리스트로 만들기 - [“alligator”, “elephant”]