반응형

개요

리스트는 프로그래머들의 경험이 잘 녹아있는 자료형이다. 오래된 프로그래밍 언어인 C++ 에서는 vector<> 라는 자료형과 같으며, 그외에는 동일한 list 라는 자료형을 사용하고 있다.

리스트형은 내부에 같은 리스트를 포함한 자료형들 즉 데이터를 담을 수 있다. 이렇게 담는 의미가 강한 자료형을 container 자료형으로 불리기도 한다. 그리고 리스트내에 담긴 데이터를 원소라고 부른다.

또한 리스트는 순서가 있는 자료형이다. 이러한 순서(혹은 순번)을 index라고 표현하며, 첫 원소의 index는 0이다.

list 의 index가 0부터 시작하는 이유

현재 대부분의 프로그래밍 언어의 문법들은 C 에서 많은 영향을 받았다. 이 때 C 에서는 list와 같은 연속적인 데이터를 저장하기 위해서 array 자료형을 제공하였다. 이 array는 연속적인 데이터가 시작되는 메모리의 위치를 의미했다.

그리고 array의 작동원리는 메모리의 특정 시작지점으로 부터 얼만큼(방향은 부호) 떨어진 곳을 읽느냐는 의미로 설계 되어있다보니 자연스럽게 메모리에서 시작지점에 저장된 데이터를 읽으려면 0 부터 시작하게 되었다.

그리고 기존의 많은 프로그래머들은 이 방식이 익숙해졌다. 결국 대부분의 연속형 자료형의 인덱스는 0 부터 시작하도록 만들어지고 사용되고 있다.

정의

리스트는 대괄호로 정의할 수 있다. 리스트에 원소(혹은 요소)로 담기는 자료형은 제약이 없다. 심지어 리스트도 담길 수 있다.(그러나 좋은 코드는 규칙성이 있게 담거나 특정 자료형을 모아서 담는 것이 좋다.)

a = []
a
# []

b = [1, 2, 3]
b
# [1, 2, 3]

c = ["1", "hello", 'world']
c
# ["1", "hello", "world"]

d = [1, 'hello', b]
d
# [1, "hello", [1, 2, 3]]

연산

리스트간 연산이 가능하다. 다만 자세히 관찰하면 문자열의 연산과 유사점이 있다.

list + list

두 개의 리스트 덧셈을 할 경우 연산자의 좌측 리스트에 이어서 연사자의 우측 리스트가 연결이 된 리스트를 반환한다. 문자열의 덧셈 연산과 유사하다. 다만, 문자열의 덧셈과 차이점은 문자열의 경우 동일한 문자열 자료형만 덧셈이 가능하다. 반면 리스트는 어떤 원소가 있어도 상관이 없다.

a = [1, 2]
b = [3, 4]
c = a + b
c
# [1, 2, 3, 4]

list += list

할당 연산자와 동일하게 동작한다. 연산자 좌측 리스트와 우측 리스트의 덧셈 연산을 한 결과를 좌측 리스트 변수에 대입하게 된다.

a = [1, 2]
b = [3, 4]
a += b
a
[1, 2, 3, 4]

list * int

앞의 list를 뒤에 있는 정수 만큼 더한 것과 같다. 문자열의 곱연산과 같다.

a = [1, 2]
b = a * 3
b
# [1, 2, 1, 2, 1, 2]

인덱싱[int]

리스트는 순서 즉 index가 있기때문에 처음 원소를 0번으로 정의하고 이후부터는 순서대로 번호를 붙일 수 있다. 이러한 번호로 읽거나 수정이 가능하다. 이런 대괄호 기호를 인덱싱 연산자라 부른다.

주의할 점은 인덱싱 연산자 내에 들어가는 숫자는 반드시 정수가 되야 한다. 그리고 인덱싱 연산자내에 범위를 벗어나는 index가 들어간 경우 에러가 발생한다.

# IDLE Shell
#    0  1  2  3
a = [1, 3, 5, 7]
a[0]
# 1

a[2]
# 5

a[2] = 0
a[2]
# 0

a[4] = 1
# Error: indexError

a[1.1]
# Error

파이썬의 독특한 점은 인덱싱에 음수를 사용할 수 있다. 인덱스에 -1 이 들어갈 경우 리스트의 가장 마지막 원소를 반환한다. 그리고 음수에서 작아질 수록(숫자가 커질 수록) 역순으로 반환하게 된다.

# IDLE Shell
#     0        1        2
a = ["hello", "world", "??"]
a[-1]
# "??"

a[-2]
# "world"

리스트 속에 있는 리스트에 접근할 경우 가장 좌측 인덱스가 가장 바깥부터 접근을 한다.

# IDLE Shell
#     0        1        2
a = ["hello", "world", "??"]
a[-1]
# "??"

a[-2]
# "world"

위의 예시의 경우 리스트 문법의 동작 원리를 설명하기 위한 것으로 실무에서는 이러한 복합적인 구조를 거의 사용하지 않는다. 완전한 2차원 리스트 혹은 완전한 3차원 형태로 사용하는 경우는 있다. 이는 프로그래밍을 할때 최대한 단순한 구조로 하는 것이 유지 보수에 유리하기 때문이다.

슬라이싱

리스트의 특정 구간을 자를 수 있다. 범위를 지정할 때는 기호 콜론(:)을 사용한다. 만약 처음이나 끝을 의미할 경우 해당 인덱스 값을 굳이 안넣어도 된다. 직관적으로 1:3은 마치 1~3 과 같은 의미가 된다.

# IDLE Shell
#    0  1  2  3  4
a = [1, 3, 5, 7, 9]

b = a[1:3]
b
# [3, 4, 7]

c = a[:3]
c
# [1, 3, 5, 7]

d = a[2:]
d
# [5, 7, 9]

대입

리스트의 대입 연산은 일반적인 숫자 대입과 다르다. 일반적인 숫자를 대입할 경우에는 값이 복사되어 변수에 담긴다. 이렇나 자료들은 immutable이라 한다. 하지만 리스트와 같은 자료형은 mutable로 구분되어 대입연산으로 다른 변수에 대입을 해도 값을 복제하지 않고 값을 공유한다.

만약 C를 공부한적이 있다면, mutable은 얕은 복사, immutable은 깊은 복사와 거의 같은 개념이다. immutable과 mutable 자료형에 대해서는 클래스 이후에 다시 살펴 보도록 하자.

# immutable
va = 1
vb = va
va = 3
va
# 3
vb
# 1

# mutable
la = [1, 2, 3]
lb = la
la[1] = 4
lb
# [1, 4, 3]

# 아래의 경우 la에 새로운 리스트가 할당되고
# lb는 기존의 리스트가 유지 된다
la = [4, 5, 6]
lb
# [1, 4, 3]

언박싱(unboxing)

파이썬의 독특한 문법으로 복수의 변수에 리스트를 대입 연산을 시도할 경우 변수명의 개수와 리스트의 원소 개수가 같은 경우 각각의 원소가 순서대로 변수에 들어가게 된다. 이때 리스트 자료형이 풀어지는 것과 같다(그래서 언박싱이라 이름을 지은 듯하다).

a = [1, 2, 3]
b, c, d = a
b
# 1

c
# 2

d
# 3

element in list

리스트 내에 동일한 원소가 있을 경우 True, 없을 경우 False를 반환한다.

a = [1, 2, 3, 4]
1 in a
# True

0 in a
# False

함수

len()

리스트의 길이를 반환해준다. 리스트의 길이는 리스트에 들어 잇는 원소의 개수이다. 리스트와 유사한 문자열에서도 사용된다.

a = [1, 2, 3, 4]
len(a)
# 4

len([1, 2, 3, 4, 5])
# 5

del

리스트에 있는 원소를 삭제해준다. 인덱싱과 슬라이싱을 응용하여 사용이 가능하다. 주의해야 할 점은 index로 삭제할 경우 기존 index가 삭제되면서 index가 바뀌게 되는 점이다. index가 바뀌면서 발생하는 문제를 피하기위해서 역순으로 삭제하는 요령이 있다.

#    0  1  2  3  4
a = [1, 2, 3, 4, 5]
del a[1]
a
# [1, 3, 4, 5]

del a[2:]
a
# [1, 3]

 

반응형

'Python > 배경이 있는 파이썬' 카테고리의 다른 글

딕셔너리(dict)  (0) 2024.04.29
튜플(Tuple)  (1) 2024.04.27
문자열(str)  (1) 2024.04.27
문자 code  (0) 2024.04.24
숫자형(int, float)  (0) 2024.04.24
반응형

list boolean 판별 all, any

배경

Python의 가장 큰 매력은 있으면 편할 것 같은 내장함수가 많다는 것이다. 종종 여러요소들을 검사할 경우 검사 결과에 대해서 list에 담아서 확인을 하고 싶은 경우가 있다. 경우에 따라 모두 True이어야 하거나 하나라도 True 인 경우가 필요하다면, all()과 any() 내장 함수를 사용하면 된다.

사용 조건은 iterable (반복자 사용가능)으로 즉, 좀 더 쉽게 접근하자면 for in 문 사용이 가능한 list 같은 자료구조에 사용할 수 있다.

 

all()

입력받은 list 인스턴스중 모두다 True 이여야 True 값을 반환한다. 즉, 각 list내의 boolean 값이 각각 and 논리연산을 한 것과 같다.

a = [True, True, True]
b = [True, False, True]
all(a)
# True
all(b)
# False

 

any()

입력받은 list 인스턴스중 하나라도 True이면 True를 반환한다. 각 list내에 boolean 값이 각각 or 논리연산을 한 것과 같다.

a = [False, False, False]
b = [True, False, False]
any(a)
# False
any(b)
# True

 

 

반응형
반응형

Python3 List의 편리한 기능들

작성 계기

잠시 Python을 사용해본적은 있지만, 최근 프로젝트를 아예 Python을 사용하기 시작하면서 기존의 C++의 STL에서의 Vector와 많은 부부니 비교가 되었다. 특히, 알았을때와 몰랐을때의 생산성의 차이를 많이 느끼게 되었기 때문에 기록겸 정리를 해 놓았다. 본 내용들은 기본서에서는 잘 다루지는 않는 내용이지만, 그렇다고 어려운 것은 아니다.

List의 구조

기본적으로 C++ STL의 Vector와 거의 비슷한다. 배열처럼 사용이 가능하며, 동적으로 길이를 늘리고 줄일 수 가 있다. 하지만, 경험에 의한 것인지 좀 더 편리한 기능들이 많이 있다. 그리고 정적 언어와 달리 List는 특정한 자료형을 구분 하지 않고 담을 수가 있다. 물론 C++에서도 포인터를 이용하면 구현을 할 수 있겠지만, 별로 추천하는 방법은 아니다.

Python의 List의 특징

여기서는 주로 다른 프로그램 언어에서 보기 힘든 Python의 List의 특징을 작성하겠다.

원소를 포함한 비교 연산이 가능함

Python의 str 구조도 마찬가지이지만 C/C++ 언어에서는 문자열 두개를 비교할때 각 문자의 길이 하나하나를 비교를 해서 같아야만 같은 문자열로 판단하는 알고리즘을 메서드로 구현해서 판단을 해야 한다. Python의 경우 이러한 비교 연산을 위한 Hash라는 값을 인스턴스 혹은 객체를 생성할때 만들어 놓는데, 이 숫자가 같은지만 비교를 하여 비교연산에 소모되는 연산량을 줄였다. 클래스의 Hash에 대해서 자료를 찾아보면 자세한 내용을 알 수 있다.

예시)

a = [1, 'hello', 2]
b = [2, 'hello', 1]
c = [1, 'hello', 2]
a == b
# False
a == c
# True

위의 예시 코드를 Python idle shell에서 실행해서 간단히 확인 할 수 있다.

곱연산으로 반복적인 원소 채우기

Python에서 특이한 기능중 하나로 원소가 있는 리스트와 숫자를 곱 연산 할경우 해당 리스트를 반복적으로 숫자만큼 채우는 것이 가능하다. 이 기능은 Python의 기본 문자열 자료형인 str에서도 나타나는 특징이다.

예시)

a = [1, 2]*3
# a = [1, 2, 1, 2, 1, 2]

예시 코드를 Python idle shell에서 확인 해보면 된다.

원소를 배분

Python에서 가장 두드러지는 문법 중 하나로 배열의 길이 만큼 객체명을 좌항에 작성을 하면 우항에 그 크기에 많은 리스트를 놓으면 각 원소를 순서대로 대입이 되는 특징이 있다. 필자의 경우 이러한 특징을 곱연산의 특징과 응용해서 여려개의 객체명을 초기화 해야 할 경우에 간단하게 사용하곤 한다.

예시)

a1, a2, a3 = [None]*3
# a1 = None
# a2 = None
# a3 = None

예시 코드를 Python idle shell에서 확인 해보면 된다.

단, List의 길이와 좌항의 변수의 길이가 안 맞을 경우 에러가 생기기 때문에 일반화 방식으로 작성할 경우 개수를 검사하는 로직을 미리 추가 해주는 것이 좋다.

간단한 index 로 나누어 대입

이 기능은 다른 언어에서도 있기는 하지만, Python에서 문법적으로 간결하게 제공을 하고 있다. ':'을 대괄호 안에서 사용하여 범위를 자를 수 있다. 필자의 경우 특정 2차원 배열을 1차원 배열에 넣고 관리할때 응용해서 사용하기도 한다.

예시)

a = [1, 2, 3, 4, 5]
b = a[2:]
# b = [3, 4, 5]
c = a[:3]
# c = [1, 2, 3]

index는 많이 헷깔리는 경우가 있는데, python은 친 인간 언어적인 편이라서 사람이 일반적으로 세는 방식으로 자른다. 그러나 전통적인 배열로 사용할 경우에는 0부터 시작한다.

리스트간 병합(Merrge)

List를 초기 정의한 이후 원소를 추가를 할 때 일반적으로 append와 같은 C++의 STL 경우 push 메서드를 호출해서 한쪽으로 하나씩 넣는게 일반적이다. 그러나 List의 연산자 + 혹은 +=을 사용하면 리스트를 동등하게 머지하는 결과를 나타낸다. append와 비슷해 보이나 다른 방식이기 때문에 주의해야 한다.

예시)

a = [1, 2]
b = [3, 4]
c = a + b
# c = [1, 2, 3, 4]

리스트 컴프리헨션(Comprehensions)

공식 문서에서는 간단하게 리스트를 만드는 방법이라고 소개를 하고 있다. 이터러블의 멤버들에게 특정 연산을 사용하는 경우에 응용이 가능하다. 가장 쉽게 응용이 가능한 것은 기존에 어떤 수치 리스트를 파일에 저장하기 위해서 문자형으로 바꾸거나 혹은 반대로 어떤 파일을 읽어서 숫자로 된 리스트를 수치형 자료로 변경할 경우에 응용하면 편한 기능이다. 이 방법을 몰라도 형 변환은 못 하는 것은 아니다. 다만, 간결하고 편할 뿐이다.
예시)

squaresSrc = [1, 4, 8, 16]
squaresDst = [str(it) for it in squaresSrc]
# squaresDst = ['1', '4', '8', '16']

그리고 여기서 더 나아가 컴프리헨션을 통해 중첩과 if문을 응용이 가능하다. 여기서 중첩을 할 경우에는 실제 코드에서 2중 for 문을 사용한 것과 같다. 컴프리헨션에서는 실행되는 순서는 첫 for문 부터 읽어서 가장 마지막에 앞에 있는 연산을 수행한다.

예시)공식 문서 예제

>>>[(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

이러한 기능은 편리하지만, 자신이 협업 혹은 유지보수하는 인력 수준에 따라서 중첩 혹은 if문이 추가된 방식에 대해서 가독성이 떨어진다면, 가능하면 사용을 자제하는 것이 좋을 것 같다.

기타 기본적인 재배열(sort) 관련 메서드

List가 선언된 객체에 자체적으로 오름차순과 내림차순으로 재배열할 경우 sort(), 그리고 현재 배열된 원소를 역으로 배열할 경우 reverse()를 기본적으로 지원을 한다. 또한 sort()의 경우 입력인자로는 정해진 flag를 넣어서 방식의 조절이 가능하다. 다만, 이 메서들을 사용시 주의해야 할 점은 List 객체 자체를 바꿔버리기 때문에 재배열을 하기 전의 데이터를 보존을 해야한다면, 깊은 복사를 하고 나서 해야한다는 단점이 있다.

이후 추가로 편의성 기준으로 정리가 필요하면 후에라도 추가를 하도록 하겠다.

참조자료

스택오브 플로워: list str to int

반응형

+ Recent posts