데이터와 제어

컬렉션과 컴프리헨션

Array, Map, Set을 목적에 맞게 고르고 정본 컴프리헨션으로 변환하는 방법.

컬렉션은 프로그램이 지켜야 할 동작에 맞춰 고릅니다. 위치와 중복이 중요한지, 키로 값을 찾아야 하는지, 유일성만 필요한지부터 판단하세요. 컴프리헨션은 필터링과 변환을 짧게 표현합니다. 이때 각 컬렉션의 순서 규칙은 그대로 지킵니다.

먼저 컬렉션 고르기

컬렉션알맞은 상황눈에 보이는 동작
Array<T>순서, 위치 접근, 중복 값이 필요할 때인덱스가 증가하는 순서로 순회하고 중복을 유지
Map<K, V>키로 값을 찾을 때키는 삽입 순서를 유지하고 기존 키의 값만 바꾸면 자리 유지
Set<T>값의 유일성이 중요할 때같은 값 가운데 처음 삽입한 값의 자리 유지

예제 실행하기

다음 코드를 collections-comprehensions.tpz로 저장합니다.

TOPAZ
let scores = [10, 25, 10]
let labels = map { "Ada": 10, "Lin": 25 }
let unique = set { 10, 25, 10 }
let focused = [ for score in scores if score >= 20 => score * 2 ]

let first = scores[0]
let missing = scores.get(9)
let hasAda = "Ada" in labels.keys
let missingLabel = match missing {
    case Some(value) => "found {value}"
    case None => "missing"
}

print("{scores}:{unique.toArray()}:{labels.keys}")
print("{focused}:{first}:{hasAda}")
print(missingLabel)
BASH
topaz check collections-comprehensions.tpz
topaz run collections-comprehensions.tpz

출력은 다음과 같습니다.

출력
[10, 25, 10]:[10, 25]:[Ada, Lin]
[50]:10:true
missing

배열은 두 10을 모두 남깁니다. 집합은 두 번째 10을 합칩니다. 첫 번째 값이 들어간 순서는 그대로 남습니다. labels.keys는 키를 삽입 순서대로 담은 배열입니다. 그래서 멤버십 검사는 "Ada" in labels.keys로 씁니다.

조회와 변경을 의도에 맞게 고르기

scores[0]은 인덱스로 값을 직접 읽습니다. 인덱스가 음수이거나 범위를 벗어나면 실행 오류가 나면서 멈춥니다. scores.get(9)는 멈추지 않는 조회입니다. 이 조회는 Option을 반환하므로 예제에서는 match로 결과를 처리합니다.

컬렉션을 제자리에서 바꾸려면 뿌리 바인딩이 변경 가능해야 합니다. values.push(...) 같은 변경 연산을 부르기 전에 let mut values = [...]로 시작하세요. 변경할 수 없는 바인딩에서도 새 컬렉션을 만드는 식은 쓸 수 있습니다.

맵은 중복을 집합과 다르게 처리합니다. 같은 단순 리터럴 키를 두 번 적으면 정적 오류입니다. 실행 중에야 두 키가 같아지면 피연산자를 계산한 다음 실행 오류가 납니다. 값을 덮어쓰지는 않습니다. 키를 추가하거나 기존 값을 바꾸려는 것이라면 Map.insert를 직접 씁니다.

리터럴, 반복문, 컴프리헨션

  • 항목을 직접 적을 때는 [a, b], set { a, b }, map { key: value }를 씁니다.
  • 본문을 여러 단계로 나눠 읽는 편이 쉬울 때는 for item in source { ... }를 씁니다. 문장 위치에서는 효과만 냅니다. 식 위치에서는 본문 결과를 Array로 모읍니다.
  • 필터링과 변환이 분명한 식 하나로 끝날 때는 [ for item in source if condition => result ]를 씁니다. 결과가 유일해야 하면 바깥을 set { ... }으로 바꿉니다. 키와 값이 필요하면 map { ... }으로 바꿉니다.

컴프리헨션의 절은 for로 시작합니다. 그 뒤에 forif를 쉼표 없이 이어 쓰고, 마지막에 =>로 결과를 적습니다. 각 절은 왼쪽에서 오른쪽으로 중첩됩니다. 최종 컬렉션은 도달한 절과 본문이 모두 성공한 뒤에만 만들어집니다.

흔한 실수

컬렉션 타입은 Array<T>, Map<K, V>, Set<T>입니다. [T]는 컬렉션 타입이 아닙니다. 집합과 맵 리터럴에는 setmap을 꼭 붙여야 합니다. #{...}는 이 문법이 아닙니다. 맨 앞에 맵을 뜻하는 단어가 없는 {key: value}도 마찬가지입니다.

key in map은 쓸 수 없습니다. key in map.keys를 쓰세요. 컴프리헨션에서 실패할 수 있는 패턴이 맞지 않으면 그 항목을 자동으로 거르지 않고 실행 오류가 납니다. 항목을 건너뛰려면 if 절을 직접 적습니다.

정확한 경계

  • 배열 스프레드는 다른 Array만 받습니다. 집합과 맵의 리터럴과 컴프리헨션에는 스프레드가 없습니다.
  • set {}map {}을 쓰려면 기대 타입인 Set<T>Map<K, V>가 있어야 합니다.
  • 맵 키와 집합 원소는 재귀적으로 키가 될 수 있는 타입이어야 합니다.
  • 항목을 지우면 그 순서 자리도 사라집니다. 다시 넣으면 맨 끝에 새 자리가 생깁니다. 기존 맵 값만 바꾸면 키 순서는 그대로입니다.
  • forArray, 정수 범위, Set, map.keys를 순회할 수 있습니다. 문자열은 스칼라를 얻는 연산을 직접 불러야 합니다.

사용자 정의 반복자, 생성기, 지연 또는 병렬 컴프리헨션, 임의 반복 값의 스프레드, 순서가 없는 컬렉션 의미는 현재 언어에 포함되지 않습니다.

학습 과정의 데이터와 제어에서 먼저 선택 기준을 익힐 수 있습니다. 컴프리헨션 바인딩은 패턴과 제어 흐름을 참고하세요. 정확한 일상 연산은 컬렉션 연산에 정리되어 있습니다.