튜플

튜플 에서 Julia

1개의 연습 문제

튜플 소개

Tuple은 1차원 array인 Vector와 여러모로 아주 비슷해요. 하지만 몇 가지 중요한 차이가 있어요.

  • 튜플은 대괄호 [ ] 대신 소괄호 ( )로 쓰지만, 의미가 분명한 경우에는 소괄호를 생략할 수 있어요.
  • 튜플은 반드시 같은 타입일 필요가 없어요. 각 원소의 타입이 따로 저장돼요.
  • 튜플은 불변이에요.

불변성 덕분에 컴파일러가 더 많은 최적화를 할 수 있고, 튜플은 벡터보다 성능이 좋아요. 그래서 값을 바꿀 필요가 없을 때는 튜플을 쓰는 게 좋아요.

julia> t = (3, 5.2, "xyz")
(3, 5.2, "xyz")

# Non-homogeneous:
julia> dump(t)
Tuple{Int64, Float64, String}
  1: Int64 3
  2: Float64 5.2
  3: String "xyz"

julia> t[2]
5.2

# Immutable:
julia> t[2] = 7.3
ERROR: MethodError: no method matching setindex!(::Tuple{Int64, Float64, String}

Tuple() 생성자도 있는데, 이걸 쓰면 반복 가능한 컬렉션을 튜플로 바꿀 수 있어요. 문자열을 문자의 컬렉션으로 취급한다는 점이 특히 유용해요.

julia> Tuple("Julia")
('J', 'u', 'l', 'i', 'a')

어떤 튜플이든 인덱스로 원소에 접근하거나 원소를 순회할 수 있어요.

숫자로만 이루어진 튜플은 배열과 마찬가지로 sum() 같은 수학 함수에도 쓸 수 있어요.

언패킹

여러 변수에 한 번에 대입하면 튜플을 각각의 원소로 풀어낼 수 있어요. 필요하지 않은 원소에는 더미 변수로 밑줄 _를 사용해요.

julia> tup = (1, "Julia", true)
(1, "Julia", true)

julia> num, name, _ = tup
(1, "Julia", true)

julia> uppercase(name), 2num
("JULIA", 2)

튜플의 모든 원소를 다른 튜플(또는 더 일반적으로 다른 컬렉션)로 복사하려면, 튜플을 풀어 주는 splat 연산자 ...를 쓰면 돼요. 이 내용은 함수 개념에서 더 자세히 다뤄요.

julia> new_tup = (tup..., π, '😊')
(1, "Julia", true, π, '😊')

이름 있는 튜플

Tuple은 값만 담아요. NamedTuple은 각 값에 이름을 짝지어 줘요.

그러면 각 필드에 "점(dot)" 표기법으로 접근할 수 있어요.

julia> nt = (a = 1, b = 7.3)
(a = 1, b = 7.3)

julia> dump(nt)
@NamedTuple{a::Int64, b::Float64}
  a: Int64 1
  b: Float64 7.3

julia> nt.b
7.3

이름은 내부적으로 Symbol로 저장돼요(불변 타입이고, 이름 앞에 콜론 :이 붙어요). 그래서 다른 문법으로도 접근할 수 있어요.

julia> nt[:b]
7.3

이름 있는 튜플을 만들 때 두 단계로 나누면 편리할 때가 있어요.

  1. 사용자 정의 데이터 타입으로 필드를 정의해요.
  2. 이걸 생성자로 사용해서 튜플을 이름 있는 튜플로 변환해요.
# Step 1
julia> Student = NamedTuple{(:name, :grade), Tuple{String, Int}}
@NamedTuple{name::String, grade::Int64}

julia> typeof(Student)
DataType

# Step 2
julia> s = Student( ("Helen", 3) )
(name = "Helen", grade = 3)

julia> dump(s)
@NamedTuple{name::String, grade::Int64}
  name: String "Helen"
  grade: Int64 3

이름 있는 튜플을 만드는 방법은 의외로 다양하니, 관심 있다면 문서를 확인해 봐요.

이름("키"라고도 해요)과 값을 각각 따로 얻는 것도 간단해요.

julia> keys(s)
(:name, :grade)

julia> values(s)
("Helen", 3)

이름 있는 튜플을 순회하면 _값_만 나오고 _이름_은 무시된다는 점에 주의해요.

순회할 때 이름도 필요하다면 단계가 좀 더 필요해요. NamedTuple을 Pairs로 이루어진 Vector로 변환할 수 있어요(Pair는 다른 개념에서 다뤄요).

julia> pairs(s)
pairs(::NamedTuple) with 2 entries:
  :name  => "Helen"
  :grade => 3

julia> collect(pairs(s))
2-element Vector{Pair{Symbol, Any}}:
  :name => "Helen"
  :grade => 3

이렇게 보면 이름 있는 튜플은 딕셔너리와 꽤 비슷해요. 하지만 Julia의 [Dict][dict]와는 몇 가지 중요한 점에서 달라요.

  • Dict는 가변이지만, NamedTuple은 다른 튜플과 마찬가지로 불변이에요.
  • Dict는 키로 받는 타입이 자유롭지만, NamedTuple은 심볼만 키로 쓸 수 있어요.

튜플은(이름이 있든 없든) 함수로 구조화된 데이터를 주고받는 아주 흔한 방법이에요. 이런 경우 불변성이 좋은데, 데이터 안전성을 보장하고 컴파일러가 더 적극적으로 최적화할 수 있게 해 주기 때문이에요.

GitHub에서 편집 링크가 새 창이나 탭에서 열려요

튜플 배우기