데이터와 제어

컬렉션과 컴프리헨션

Array, Map, Set을 목적에 맞게 선택하고 올바른 컴프리헨션으로 변환하는 방법.

컬렉션은 프로그램이 수행할 동작에 맞춰 선택합니다. 요소의 순서와 중복 허용 여부, 키 기반 조회 필요성, 값의 유일성 보장 여부를 먼저 판단하세요. 컴프리헨션을 사용하면 필터링과 변환을 간결하게 표현할 수 있으며, 이 과정에서 각 컬렉션의 순서 규칙은 그대로 유지됩니다.

먼저 컬렉션 고르기

컬렉션알맞은 상황눈에 보이는 동작
Array<T>순서 유지, 인덱스 접근, 중복 값이 필요할 때인덱스가 증가하는 순서로 순회하고 중복을 유지
Map<K, V>키로 값을 조회할 때키의 삽입 순서를 유지하며, 기존 키의 값을 변경해도 위치 유지
Set<T>값의 유일성이 중요할 때중복 값 중 처음 삽입된 요소의 위치 유지

예제 실행하기

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

TOPAZ
let 점수들 = [10, 25, 10]
let 레이블들 = map { "Ada": 10, "Lin": 25 }
let 고유값들 = set { 10, 25, 10 }
let 집중값들 = [ for 점수 in 점수들 if 점수 >= 20 => 점수 * 2 ]

let 첫값 = 점수들[0]
let 누락 = 점수들.get(9)
let 에이다포함 = "Ada" in 레이블들.keys
let 누락표시 = match 누락 {
    case Some() => "found {}"
    case None => "missing"
}

print("{점수들}:{고유값들.toArray()}:{레이블들.keys}")
print("{집중값들}:{첫값}:{에이다포함}")
print(누락표시)
BASH
topaz check collections-comprehensions.tpz
topaz run collections-comprehensions.tpz

출력은 다음과 같습니다.

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

배열은 두 10을 모두 남깁니다. 반면 집합은 두 번째 10을 병합하며, 첫 번째 값이 삽입된 순서는 그대로 유지됩니다. 레이블들.keys는 키를 삽입 순서대로 담은 배열입니다. 따라서 멤버십 검사는 "Ada" in 레이블들.keys 형태로 작성합니다.

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

점수들[0]은 인덱스로 값을 직접 읽어옵니다. 인덱스가 음수이거나 범위를 벗어나면 런타임 오류가 발생하며 실행이 중단됩니다. 점수들.get(9)는 실행이 중단되지 않는 조회 방식입니다. 이 연산은 Option을 반환하므로 예제에서는 match로 결과를 처리합니다.

컬렉션을 제자리에서 변경하려면 뿌리 바인딩이 가변적이어야 합니다. 값들.push(...) 같은 변경 연산을 호출하기 전에 let mut 값들 = [...]로 시작하세요. 변경할 수 없는 바인딩이라도 새 컬렉션을 생성하는 식은 작성할 수 있습니다.

맵은 중복 처리 방식이 집합과 다릅니다. 리터럴에 동일한 단일 키를 두 번 작성하면 정적 오류가 발생합니다. 런타임에 두 키가 동일해지는 경우 피연산자를 계산한 후 런타임 오류가 발생하며, 기존 값을 덮어쓰지 않습니다. 키를 추가하거나 기존 값을 변경하려는 경우 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> 정보가 필요합니다.
  • 맵 키와 집합 요소는 재귀적으로 키 지정이 가능한 타입이어야 합니다.
  • 항목을 삭제하면 해당 위치의 순서도 함께 제거됩니다. 항목을 다시 추가하면 맨 끝에 새로운 자리가 생성됩니다. 기존 맵의 값만 변경하는 경우 키 순서는 그대로 유지됩니다.
  • for 문은 Array·정수 범위·Set·map.keys를 순회할 수 있습니다. 문자열의 경우 스칼라 값을 추출하는 연산을 직접 호출해야 합니다.

사용자 정의 이터레이터·제너레이터·지연 평가 또는 병렬 컴프리헨션·임의 이터러블 값의 스프레드·순서가 없는 컬렉션 시맨틱은 현재 언어 명세에 포함되지 않습니다.

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