-
Chapter 7: Arrays, Dictionaries & SetsRaywenderlich/Swift Apprentice 2022. 8. 27. 03:10
Version
Swift 5.5, iOS 15, Xcode 13
이 섹션(section)의 도입부(introduction)에서 설명(discussed)한 것처럼 컬렉션(collections)은 여러 값(values)을 함께(together) 저장(store)할 수 있는 유연한(flexible) "컨테이너(containers)"이다. 이러한 컬렉션(collections)에 대해 논의(discussing)하기 전에 가변(mutable) 컬렉션과 불변(immutable) 컬렉션(collections)의 개념(concept)을 이해(understand)해야 한다.
컬렉션(collection) 유형(types) 간의 차이(differences)을 알아보는(exploring) 과정에서, 추가(adding) 또는 검색(searching)과 같은 특정(certain) 작업(operations)을 얼마나 빠르게(quickly) 수행(perform)할 수 있는지와 같은 성능(performance)도 고려(consider)하게 된다.
성능(performance)에 대해 이야기하는 일반적인(usual) 방법은 빅오 표기법(big-O notation)을 사용하는 것이다. 아직(already) 익숙(familiar with)하지 않은 경우, 간단한(brief) 소개(introduction)를 위해 이 장(chapter)을 읽어 본다.
Big-O 표기법(notation)은 실행 시간(running tim) 또는 작업(operation)을 완료(complete)하는 데 걸리는 시간을 설명(describe)하는 방법이다. 여기서 작업(operation)에 걸리는 정확한(exact) 시간은 그다지 중요(important)하지 않다. 중요한(matters) 것은 규모(scale)의 상대적(relative) 차이(difference)이다.
임의 순서(random order)의 이름 목록(list)이 있고, 해당 목록(list)에서 첫 번째 이름을 찾아야(look up) 한다고 상상해 본다(imagine). 목록(list)에 이름이 하나(single)든 수 백만(million)이든 상관 없이(doesn’t matter) 이름을 확인(glancing at)하는 데에는 항상 같은 시간(same amount of time)이 걸린다. 이것이 상수 시간(constant time) 연산(operation), 빅오 표기법(big-O notation)에서는 O(1)의 예(example)이다.
이제 목록(list)에서 특정(particular) 이름을 찾아야(find) 한다고 가정해 본다. 일치(match)하는 항목을 찾거나(find) 끝에 도달(reach)할 때까지 목록(list)의 모든 이름을 살펴봐야(scan through) 한다. 다시 말하지만(again), 이 작업에 소요되는(takes) 정확한(exact) 시간에는 관심이(concerned with) 없으며 다른 작업(operations)과 비교한(compared to) 상대적인(relative) 시간만 고려한다.
실행 시간(running time)을 파악(figure out)하기 위해 작업 단위(units)의 관점(terms)에서 생각해 본다. 모든 이름을 살펴봐야(look at) 하므로, 이름당 하나의 "단위(unit)" 작업이 있다고 간주한다(consider). 이름이 100개라면 100 단위의 작업(units of work)이다. 이름이 두 배(double)인 200개라면 작업량(amount of work)이 어떻게 달라지는지(change) 생각해 본다.
정답(answer)은 작업량(amount of work)을 또한(also) 두 배(doubles)가 된다. 마찬가지로(similarly), 이름의 수를 4배(quadruple)로 늘리면 작업량(amount of work)도 4배(quadruples)로 늘어난다.
이러한 작업량(work) 증가(increase)는 선형 시간(linear time) 연산(operation), 빅오 표기법(big-O notation)에서는 O(N)의 예(example)이다. 입력(input) 크기(size)는 변수(variable) N이며, 이는 해당 과정(process)에 걸리는 시간도 N임을 의미(means)한다. 입력 크기(input size, 목록에 있는 이름의 수(the number of names in the list))와 하나를 검색(search for)하는 데 걸리는 시간(time) 사이에는 직접적인(direct) 선형 관계(linear relationship)가 있다.
상수 시간(constant time) 연산(operations)의 O(1)에서 숫자(number) 1을 사용하는 이유를 알 수 있을 것이다. 무엇이든 상관없이(no matter what) 하나의 작업 단위(single unit of work)일 뿐이다.
웹(Web)을 검색(searching)하여 빅오 표기법(big-O notation)에 대해 더 자세히 알아볼 수 있다. 이 책에서는 상수 시간(constant time)과 선형 시간(linear time)만 다루지만, 다양한(such) 시간 복잡도(time complexities)가 있다.
컬렉션(collection)은 방대한 양의 데이터(vast amounts of data)를 저장(store)할 수 있기 때문에, 컬렉션(collection) 유형(types)을 다룰 때(dealing with) Big-O 표기법은 특히(particularly) 중요(important)하다. 값을 추가, 삭제 또는 편집할 때 실행 시간을 알고 있어야 합니다. 예를 들어(for example), 컬렉션(collection) 유형(type) A에 상수 시간(constant-time) 검색(searching)이 있고 컬렉션 유형(collection) B에 선형 시간(linear-time) 검색(searching)이 있는 경우, 수행하려는 검색의 양에 따라(depend on) 사용할 검색(searching)을 선택(choose)해야 할 것이다.
Mutable versus immutable collections
이전(previous)에 사용한
Int
나String
같은 유형(types)과 마찬가지로, 컬렉션(collection)을 생성(create)할 때 상수(constant) 또는 변수(variable)로 선언(declare)해야 한다.컬렉션(collection)을 생성(created)한 후 변경(change)할 필요가 없다면,
let
을 사용한 상수(constant)로 선언(declaring)하여 변경할 수 없도록(immutable) 해야 한다. 반면(alternatively) 컬렉션(collection)의 값(values)을 추가(add), 제거(remove) 또는 업데이트(update)해야 하는 경우var
를 사용한 변수(variable)로 선언(declaring)하여 변경 가능한(mutable) 컬렉션(collection)을 생성(create)해야 한다.Arrays
배열(arrays)은 Swift에서 가장 많이 접하게 되는(most common) 컬렉션(collection) 유형(type)이다. 배열(arrays)은 일반(regular) 변수(variables)나 상수(constants)처럼 유형이 지정되며(typed), 간단한(simple) 목록(list)처럼 여러(multiple) 값(values)을 저장(store)한다.
첫 배열(array)을 생성(create)하기 전에 시간을 내어, 배열(array)이 무엇인지 왜 배열(array)을 사용(use)해야 하는지 자세히(detail) 생각해(consider) 본다.
What is an array?
배열(array)은 동일한 유형(type)의 값들(values)이 정렬된(ordered) 컬렉션(collection)이다. 배열(array)의 요소(elements)는 0부터 인덱싱(zero-indexed)된다. 즉, 첫 번째 요소(element)의 인덱스(index)는 0이고 두 번째 요소(element)의 인덱스(index)는 1이 되는 식이다. 이를 알고 있다면, 마지막 요소(element)의 인덱스(index)는 배열(array)의 값들(values) 개수(number)에서 1을 뺀(minus) 값임을 확인(determine)할 수 있다.
이 배열(array)은 인덱스(indices) 0-4에 5개의 요소(elements)가 있다.
문자열(strings)을 포함(holds)하는 배열(array)에 문자열이 아닌(non-string) 유형(types)을 추가(add)할 수 없으므로, 모든 값(values)은
String
유형(type)이다. 동일한 값이(value) 여러 번(multiple times) 나타날(appear) 수 있다.When are arrays useful?
배열(arrays)은 항목(items)을 특정 순서(particular order)로 저장(store)하려는 경우에 유용(useful)하다. 요소(elements)를 정렬하거나, 전체(entire) 배열(array)을 반복(iterating)하지 않고 인덱스(index) 별로 요소(elements)를 가져와야(fetch) 하는 경우가 있을 수 있다.
예를 들어(for example), 고득점 점수(high score data)를 저장(storing)하는 경우 순서(order)가 중요(matter)하다. 가장 높은 점수(the highest score)가 목록(list)의 첫 번째(index 0)에 오도록 하고, 그 다음으로 가장 높은 점수(next-highest score)가 나오도록 할 수 있다.
Creating arrays
배열(array)을 만드는 가장 쉬운 방법(the easiest way)은 배열 리터럴(array literal)을 사용하는 것이다. 이것은 배열(array)에 값(values)을 제공(providing)하는 간결한(concise) 방법(method)이다. 배열 리터럴(array literal)은 쉼표로 구분되고(separated by commas), 대괄호(square brackets)로 묶(enclosed)인 값(values)의 목록(list)이다.
let evenNumbers = [2, 4, 6, 8]
이 배열 리터럴(array literal)은 정수(integers)만 포함(contains)되어 있으므로, Swift는
evenNumbers
의 유형(type)을Int
값(values)의 배열(array)로 추론(infers)한다. 이 유형(type)은[Int]
로 쓴다(written). 대괄호(square brackets) 안(inside)의 유형(type)은 배열(array)에 저장(store)할 수 있는 값(values)의 유형(type)을 정의(defines)하며, 컴파일러(compiler)는 배열(array)에 요소(elements)를 추가(adding)할 때 이를 강제(enforce)한다.예를 들어(for example) 문자열(string)을 추가(add)하려 하면, 컴파일러(compiler)에서 오류(error)를 반환(return)하고 코드(code)는 컴파일(compile)되지 않는다. 빈(empty) 배열 리터럴(array literal)
[]
을 사용(using)하여 빈 배열(array)을 생성(create)할 수 있다. 컴파일러(compiler)는 유형(type)을 추론(infer)할 수 없기 때문에, 유형 주석(type annotation)을 사용하여 유형(type)을 명시(explicit)해야 한다:var subscribers: [String] = []
모든 값(values)이 기본값(default value)으로 설정(set to)된 배열(array)을 생성(create)할 수도 있다:
let allZeros = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0]
변경(change)되지 않는 배열(arrays)을 상수(constants)로 선언(declare)하는 것은 좋은 습관(practice)이다. 예를 들어(for example), 다음 배열(array)을 고려(consider)해 본다:
let vowels = ["A", "E", "I", "O", "U"]
vowels
은 문자열(strings) 배열(array)이며, 그 값(values)은 변경(changed)할 수 없다. 그러나 모음(vowels) 목록(list)은 빈번하게(often) 변경(change)되는 것이 아니므로 상관없다.Accessing elements
배열(array)에서 값(values)을 가져오는(fetch) 방법을 알지 못한다면(unless), 배열(arrays)을 생성(create)한 의미가 없다. 이 섹션(section)에서는 배열(array)의 요소(elements)에 접근(access)하는 다양한 방법(several different ways)을 배워본다.
Using properties and methods
카드 게임을 만들고 참가자(players)의 이름을 배열(array)에 저장(store)하려고 한다. 참가자(players)가 게임(game)에 참여(join)하거나 떠날(leave) 때 목록이(list) 변경(change)되어야 하므로 변경 가능한(mutable) 배열(array)을 선언(declare)해야 한다:
var players = ["Alice", "Bob", "Cindy", "Dan"]
이 예(example)에선
players
를 변수(variable)에 할당(assigned)했기 때문에 변경 가능한(mutable) 배열(array)이다.게임(game)을 시작하기 전에 충분한 참가자(players)가 있는지 확인(make sure)해야 한다.
isEmpty
속성(property)을 사용하여 참가자(player)가 한 명 이상 있는지 확인(check)할 수 있다:print(players.isEmpty) // > false
Note: 속성(properties)에 대한 모든 내용은 11장(chapter), "Properties"에서 배우게 된다. 지금은(for now), 값(values)에 내장된(built into) 변수(variables)로 생각하면 된다. 속성(property)에 접근(access)하려면, 값(value)을 보유(holds)하는 상수(constant) 또는 변수(variable)의 이름 뒤(after)에 점(dot)을 찍고, 접근(access)하려는 속성(property)의 이름을 그 다음에 사용하면 된다.
배열(array)이 비어(empty) 있지는 않지만, 게임(game)을 시작하려면 최소 두 명이 필요하다.
count
속성(property)을 사용하여 참가자(players)의 수를 얻을(get) 수 있다:if players.count < 2 { print("We need at least two players!") } else { print("Let’s start!") } // > Let’s start!
게임을 시작할 시간이다(It’s time to start the game)! 플레이(play) 순서(order)는 배열(array)의 이름 순서(order)에 따라 결정(decide)된다. 첫 번째 참가자(player)의 이름은 어떻게 알 수(get) 있을까?
배열(arrays)은 첫 번째(first) 객체(object)를 가져오는(fetch)
first
속성(property)을 제공(provide)한다:var currentPlayer = players.first
currentPlayer
의 값(value)을 출력(printing)하면 흥미로운(interesting) 사실을 알 수(reveals) 있다:print(currentPlayer as Any) // > Optional("Alice")
배열(array)이 비어(empty) 있으면
first
는nil
을 반환(return)해야 하기 때문에,first
속성(property)은 실제로는(actually) 옵셔널(optional)을 반환(returns)한다.print()
메서드(method)는currentPlayer
가 옵셔널(optional)임을 인식(realizes)하고 경고(warning)를 보여준다(generates). 경고(warning)를 표시(suppress)하지 않으려면, 출력(printed)할 유형(type)에as Any
를 추가(add)하기만 하면 된다.마찬가지로(similarly), 배열(arrays)에는 마지막(last) 값(value)을 반환(returns)하는
last
속성(property)이 있으며 배열(array)이 비어 있다면(empty)nil
을 반환(returns)한다:print(players.last as Any) // > Optional("Dan")
배열(array)에서 값(values)을 가져오는(get) 또 다른 방법은
min()
을 호출(calling)하는 것이다. 이 메서드(method)는 가장 작은 인덱스(the lowest index)가 아니라, 배열(array)에서 가장 작은 값(the lowest value)을 가진 요소(element)를 반환(returns)한다. 배열(array)에 문자열(strings)이 포함(contained)되어 있다면, 알파벳 순서(alphabetical order)로 가장 작은(lowest) 문자열(string)을 반환(return)한다. 이 경우(case)에는"Alice"
이다:currentPlayer = players.min() print(currentPlayer as Any) // > Optional("Alice")
Note: 메서드(methods)에 대한 모든 내용은 12장(chapter), "Methods"에서 배우게 된다. 지금은(for now), 값(values)에 내장된(built into) 함수(functions)로 생각하면 된다. 메소드(method)를 호출(call)하려면, 값(value)을 보유(holds)하는 상수(constant) 또는 변수(variable)의 이름 뒤(after)에 점(dot)을 찍고, 호출(call)하려는 메서드(method)의 이름을 그 다음에 사용하면 된다. 함수(functions)와 마찬가지로, 메서드(method)를 호출(calling)할 때 매개변수(parameter) 목록(list)이 비어(empty) 있더라도 이를 포함(include)해야 한다.
명백하게(obviously),
first
와min()
이 항상(always) 같은 값(value)을 반환(return)하지는 않는다. 예를 들면(for example) 다음과 같다:print([2, 3, 1].first as Any) // > Optional(2) print([2, 3, 1].min() as Any) // > Optional(1)
짐작(guessed)했겠지만, 배열(arrays)에는
max()
메서드(method)도 있다.Note:
first
및last
속성(properties)과min()
및max()
메서드(methods)는 배열(arrays)의 고유(unique)한 특성이 아니다. 모든 컬렉션(collection) 유형(type)에는 이러한 속성(properties)과 메서드(methods)가 있으며, 그 외에도 더 다양한(plethora) 것들이 있다. 이에 대한 자세한 내용은 16장(chapter), "Protocols"에서 배우게 될 것이다.이제 첫 번째 참가자(player)를 확인하는(get) 방법을 알았으므로, 해당 참가자(player)가 누구인지 확인(announce)한다:
if let currentPlayer = currentPlayer { print("\(currentPlayer) will start") } // > Alice will start
min()
에서 반환한 옵셔널(optional)을 해제(unwrap)하기 위해if let
을 사용한다. 그렇지 않으면(otherwise) 해당 구문(statement)은Optional("Alice") will start
로 출력(print)되며, 이는 원하는(want) 것이 아니다.이러한 속성(properties)과 메서드(methods)는 첫 번째(first), 마지막(last), 최소(minimum) 또는 최대(maximum) 요소(elements)를 가져(get)오려는 경우에 유용(helpful)하다. 그러나 이러한 속성(properties)이나 메서드(methods) 중 하나로 원하는 요소(element)를 얻을(obtained with) 수 없다면 어떻게 해야 할까?
Using subscripting
배열(array)의 요소(elements)에 접근(access)하는 가장 편리한(the most convenient) 방법은 첨자(subscript) 구문(syntax)을 사용하는 것이다. 이 구문(syntax)을 사용하면 대괄호(square brackets) 안에 해당 인덱스(index)를 사용하여, 모든(any) 값(value)에 직접(directly) 접근(access)할 수 있다:
var firstPlayer = players[0] print("First player is \(firstPlayer)") // > First player is "Alice"
배열(arrays)은 0부터 인덱싱(zero-indexed)되므로 인덱스(index) 0을 사용하여 첫 번째 개체(object)를 가져온다(fetch). 더 큰(greater) 인덱스(index)를 사용하여 배열(array)의 다음(next) 요소(elements)를 가져올 수 있지만, 배열(array) 크기(size)를 초과(beyond)하는 인덱스(index)에 접근(access)하려고 하면 런타임 오류(runtime error)가 발생한다.
var player = players[4] // > fatal error: Index out of range
players
에는 4개의 문자열(strings)만 포함(contains)되어 있기 때문에 이 오류(error)가 발생(receive)한다. 인덱스(index) 4는 다섯 번째 요소(element)를 나타내지만, 이 배열(array)에는 다섯 번째 요소(element)가 없다.첨자(subscripts)를 사용할 때, 존재하지 않는(non-existing) 인덱스(index)에 접근(access)하려 해도
nil
을 반환(return)하지 않고 단순히(simply) 런타임 오류(runtime error)가 발생(causes)하기 때문에 옵셔널(optionals)에 대해 걱정(worry)할 필요가 없다.Using countable ranges to make an ArraySlice
가산 범위(countable ranges)와 함께 첨자(subscript) 구문(syntax)을 사용하여 배열(array)에서 하나 이상의 값(value)을 가져(fetch)올 수 있다. 예를 들어(for example), 다음 두 명의 참가자(players)를 얻으려면 다음과 같이 하면 된다:
let upcomingPlayersSlice = players[1...2] print(upcomingPlayersSlice[1], upcomingPlayersSlice[2]) // > "Bob Cindy\n"
상수(constant)
upcomingPlayersSlice
는 실제로(actually) 원본 배열(array)의ArraySlice
이다. 이러한 유형(type)의 차이(difference)가 발생하는 이유는upcomingPlayersSlice
가players
와 저장소(storage)를 공유(shares)한다는 점을 분명히(clear) 하기 위해서이다.사용한 범위(range)는 배열(array)의 두 번째와 세 번째 항목(items)을 나타내는(representing)
1...2
이다. 시작 값(start value)이 끝 값(end value)보다 작거나 같고(smaller than or equal to) 배열(array)의 범위(bounds) 내(within)에 있는 한(as long as), 인덱스(index)를 사용할 수 있다.또한, 다음과 같이
ArraySlice
에서 0부터 인덱싱(zero-indexed)되는 완전히 새로운(brand-new)Array
을 쉽게 만들 수 있다:let upcomingPlayersArray = Array(players[1...2]) print(upcomingPlayersArray[0], upcomingPlayersArray[1]) // > "Bob Cindy\n"
Checking for an element
contains(_:)
을 사용하여 배열(array)에 특정(element) 요소(element)가 하나 이상(at least one) 있는지(occurrence) 확인(check)할 수 있으며, 배열(array)에서 요소(element)를 찾으면true
를 반환(returns)하고 그렇지 않으면(otherwise)false
를 반환한다.이(strategy)를 사용하여 주어진(given) 참가자(player)가 게임(game)에 있는지 확인(checks)하는 함수(function)를 작성할 수 있다:
func isEliminated(player: String) -> Bool { !players.contains(player) }
이제 참가자(player)가 제거(eliminated)되었는지 확인(check)해야 할 때, 언제든지(any time) 이 함수(function)를 사용할 수 있다.
print(isEliminated(player: "Bob")) // > false
ArraySlice
를 사용하여 특정(specific) 범위(range)에 요소(element)가 있는지(existence) 확인(test)할 수도 있다:players[1...3].contains("Bob") // true
이제 배열(arrays)에서 데이터(data)를 가져올 수 있으므로, 변경 가능한(mutable) 배열(arrays)과 값(values)을 변경(change)하는 방법을 살펴본다.
Modifying arrays
요소(elements) 추가(adding) 및 제거(removing), 기존(existing) 값(values) 수정(updating), 요소(elements)를 다른 순서(order)로 이동 등 변경 가능한(mutable) 배열(arrays)에 대한 모든 종류의 변경(changes)을 수행할 수 있다. 이 섹션(section)에서는 배열(array)을 사용하여 게임(game) 진행 상황(what’s going on)을 맞추는(match up) 방법에 대해 알아본다.
Appending elements
새로운 참가자(players)가 게임(game)에 참여(join)한다면, 등록(sign up)하고 배열(array)에 이름을 추가(add)해야 한다. Eli는 기존(existing) 4인에 합류(join)한 첫 번째 참가자(players)이다.
append(_:)
메서드(method)를 사용하여 배열(array)의 끝에 Eli를 추가(add)할 수 있다:players.append("Eli")
문자열(string)이 아닌 다른 것을 추가(append)하려 하면 컴파일러(compiler)는 오류(error)를 표시(show)한다. 배열(arrays)은 동일한 유형(type)의 값(values)만 저장(store)할 수 있음을 유념(remember)해야한다. 또한
append(_:)
는 변경 가능한(mutable) 배열(arrays)에서만 작동한다.게임(game)에 참여(join)할 다음 참가자(player)는 Gina이다.
+=
연산자(operator)를 사용하여 다른 방법으로 그녀를 게임(game)에 추가(append)할 수 있다:players += ["Gina"]
이 표현식(expression)의 오른쪽(right-hand side)은 문자열(string)
"Gina"
라는 단일(single) 요소(element)가 있는 배열(array)이다.+=
를 사용하여 해당 배열(array)의 요소(elements)를players
에 추가(appending)한다.이제 배열(array)은 다음과 같다:
print(players) // > ["Alice", "Bob", "Cindy", "Dan", "Eli", "Gina"]
여기서는 배열(array)에 단일(single) 요소(element)를 추가(added)했지만, Gina 뒤에 더 많은 이름을 추가(append)하면
+=
연산자(operator)를 사용하여 여러(multiple) 항목(items)을 추가하는 것이 얼마나 쉬운지 알 수 있다.Inserting elements
이 카드 게임(game)의 불문율(unwritten rule)은 참가자(players)의 이름이 알파벳(alphabetical) 순서(order)로 되어 있어야 한다는 것이다. 이 목록(list)에는 문자(letter) F로 시작하는 참가자(player)가 없다(missing). 다행히(luckily) Frank가 방금(just) 도착(arrived)했다. 목록(list)의 Eli와 Gina 사이(between)에 그를 추가(add)하고 싶다. 이를 위해(to do that),
insert(_:at:)
메서드(method)를 사용할 수 있다:players.insert("Frank", at: 5)
at
인수(argument)는 요소(element)를 추가(add)할 위치를 정의(defines)한다. 배열(array)은 0부터 인덱싱(zero-indexed)되어 있으므로, 인덱스(index) 5는 Gina의 인덱스(index)이며 Frank가 그녀를 대신할 때(takes her place) 뒤(높은 인덱스)로 이동하도록 한다.Removing elements
게임(game) 중 다른 참가자들(players)은 Cindy와 Gina의 부정행위(cheating)를 포착(caught)했다. 그들은 게임(game)에서 제외(removed)되어야 한다. Gina가 참가자(players) 목록(list)의 마지막(last)이라는 것을 알고 있으므로,
removeLast()
메서드(method)를 사용하여 Gina를 쉽게 제거(remove)할 수 있다.var removedPlayer = players.removeLast() print("\(removedPlayer) was removed") // > Gina was removed
이 메서드(method)는 마지막 요소(element)를 제거(removes)한 다음 반환(returns)하는 두 가지 작업을 수행한다. 이를 출력(print)하거나 부정 행위자(cheaters)의 배열(array)과 같이 다른 곳에 저장(store)해야 하는 경우에 유용하다.
게임(game)에서 Cindy를 제거(remove)하려면, 그녀의 이름이 저장(stored)된 정확한(exact) 인덱스(index)를 알아야 한다. 참가자(players) 목록(list)을 확인해 보면, 그녀는 3번째 이므로 인덱스(index)는 2가 된다.
removedPlayer = players.remove(at: 2) print("\(removedPlayer) was removed") // > Cindy was removed
요소(element)의 인덱스(index)를 아직 알지 못한 경우, 이를 얻을 수 있는 메서드(method)도 있다. 배열(array)에 동일한 값(value)의 복사본이 여러 개 포함(contain)되어 있을 수 있으므로,
firstIndex(of:)
는 요소(element)의 첫 번째 인덱스(index)를 반환(returns)한다. 메서드(method)가 요소(element)를 찾지 못하면,nil
을 반환(returns)한다.Mini-exercise
firstIndex(of:)
를 사용하여players
에서"Dan"
요소의 위치(position)를 알아내(determine) 본다.Updating elements
Frank는 이제부터 모두가 그를 Franklin라고 불러야 한다고 결정(decided)했다. 배열(array)에서
"Frank"
값(value)을 제거(remove)한 다음"Franklin"
을 추가(add)할 수 있지만, 이 간단한(simple) 작업(task)을 위해 너무 많은 일을 해야 한다. 대신(instead) 첨자(subscript) 구문(syntax)을 사용하여 이름을 업데이트(update)해야 한다.print(players) // > ["Alice", "Bob", "Dan", "Eli", "Frank"] players[4] = "Franklin" print(players) // > ["Alice", "Bob", "Dan", "Eli", "Franklin"]
배열(array)의 범위(bounds)를 벗어나는(beyond) 인덱스(index)를 사용하면 코드(code)가 충돌(crash)하므로 주의해야(be careful) 한다.
게임(game)이 계속되면서(continues) 일부 참가자(players)는 탈락하고(eliminated), 새로운 참가자가 이를 대체(replace)한다. 범위(ranges)와 함께 첨자(subscripting)를 사용하여 한 줄의 코드(code)로 여러(multiple) 값(values)을 업데이트(update)할 수도 있다:
players[0...1] = ["Donna", "Craig", "Brian", "Anna"] print(players) // > ["Donna", "Craig", "Brian", "Anna", "Dan", "Eli", "Franklin"]
이 코드(code)는 처음 두 참가자(players)인 Alice와 Bob을 새로운 참가자(players)의 배열(array)에 있는 네 명의 참가자(player)로 대체(replaces)한다. 보다시피(as you can see), 범위(range)의 크기(size)는 추가(adding)하는 값(values)을 포함(holds)하는 배열(array)의 크기(size)와 같을 필요가 없다.
Moving elements
엉망진창인 상황이다(take a look at this mess).
players
배열(array)에는 A에서 F로 시작(start with)하는 이름이 포함(contains)되어 있지만, 순서(order)가 정확(correct)하지 않아 게임(game) 규칙(rules)을 위반(violates)한다. 값(values)을 올바른(correct) 위치(positions)로 하나씩 이동(moving)하여 이 상황(situation)을 해결(fix)할 수 있다:let playerAnna = players.remove(at: 3) players.insert(playerAnna, at: 0) print(players) // > ["Anna", "Donna", "Craig", "Brian", "Dan", "Eli", "Franklin"]
...또는
swapAt(_:_:)
를 사용하여 요소(elements)를 교환(swapping)한다:players.swapAt(1, 3) print(players) // > ["Anna", "Brian", "Craig", "Donna", "Dan", "Eli", "Franklin"]
이는 몇 가지(a few) 요소(elements)에 대해 작동(works)하지만, 전체(entire) 배열(array)을 정렬(sort)하려면
sort()
를 사용해야 한다:players.sort() print(players) // > ["Anna", "Brian", "Craig", "Dan", "Donna", "Eli", "Franklin"]
원본(original) 배열(array)을 그대로 두고(untouched), 정렬된(sorted) 복사본(copy)을 반환(return)하려면
sort()
대신sorted()
를 사용한다.Iterating through an array
시간이 늦어져, 참가자들(players)은 하룻밤을 묵고(stop for the night) 내일 계속하기(continue)로 결정(decide to)했다. 그 동안(in the meantime)에는 점수(scores)를 별도(separate)의 배열(array)에 보관(keep)할 것이다. 딕셔너리(dictionaries)에 대해 배우면서(learn) 이에 대한 더 나은 접근 방식(approach)을 알아볼(investigate) 수 있지만, 지금은 배열(arrays)을 계속(continue) 사용한다:
let scores = [2, 2, 8, 6, 1, 2, 1]
참가자(players)가 떠나기(leave) 전 게임(game)에 남아 있는 참가자의 이름을 출력(print)하려 한다. 4장(chapter), "Advanced Control Flow"에서 배운
for-in
반복문(loop)를 사용하여 이 작업을 수행할 수 있다:for player in players { print(player) } // > Anna // > Brian // > Craig // > Dan // > Donna // > Eli // > Franklin
이 코드(code)는 인덱스(index) 0에서
player.count - 1
까지players
의 모든 요소(elements)를 살펴보고 그 값(values)을 출력(prints)한다. 첫 번째 반복(iteration)에서player
는 배열(array)의 첫 번째 요소(element)와 같다. 두 번째 반복(iteration)에서는 배열의 두 번째 요소(element)와 같다. 반복문(loop)이 배열(array)의 모든 요소(elements)를 출력(printed)할 때까지 계속된다(and so on).각 요소(element)의 인덱스(index)가 필요한 경우, 배열(array)의
enumerated()
메서드(method)의 반환(return) 값(value)을 반복(iterate over)할 수 있다. 이 메서드(method)는 각 요소(element)의 인덱스(index)와 값(value)이 포함된 튜플(tuples)을 반환한다:for (index, player) in players.enumerated() { print("\(index + 1). \(player)") } // > 1. Anna // > 2. Brian // > 3. Craig // > 4. Dan // > 5. Donna // > 6. Eli // > 7. Franklin
이제 방금 배운 기술(technique)을 사용하여 정수(integers) 배열(array)을 입력(input)으로 사용하고 해당 요소(elements)의 합계(sum)를 반환(returns)하는 함수(function)를 작성할 수 있다:
func sumOfElements(in array: [Int]) -> Int { var sum = 0 for number in array { sum += number } return sum }
이 함수(function)를 사용하여 참가자(players)의 점수(scores) 합계(sum)를 계산(calculate)할 수 있다:
print(sumOfElements(in: scores)) // > 22
Mini-exercise
참가자(players)의 이름(names)과 점수(scores)를 출력(prints)하는 for-in 반복문(loop)를 작성해 본다.
Running time for array operations
배열(arrays)은 메모리(memory)에 연속(contiguous) 블록(block)으로 저장(stored)된다. 즉, 배열(array)에 10개의 요소(elements)가 있다면 10개의 값(values)이 모두 나란히 저장된다(stored one next to the other). 이를 염두에 두고(with that in mind) 다양한(various) 배열(array)의 작업(operations)의 성능(performance) 비용(cost)을 확인해 보면 다음과 같다:
요소 접근(accessing elements): 고정(fixed) 또는 상수(constant) 시간(time)이 소요되므로, 요소(element)를 가져오는 비용(cost)은 저렴(cheap)하다. 때때로(sometimes) 이것은 O(1)로 쓴다. 모든 값(values)이 순차적(sequential)이므로 임의 접근(random access)를 사용하여 특정(particular) 인덱스(index)에서 값(value)을 가져(fetch)오는 것이 쉽다. 컴파일러(compiler)가 알아야 할 것은 배열(array)이 시작되는 위치와 가져오려는(fetch) 인덱스(index)뿐이다.
요소 삽입(inserting elements): 요소(element) 추가(adding)의 복잡도(complexity)는 새 요소(element)를 추가(adding)하는 위치(position)에 따라(depends on) 다르다.
- 배열(array)의 시작(beginning) 부분에 추가(add)하면, Swift는 공간(room)을 확보하기 위해 모든 요소(elements)를 하나씩 이동(shift)해야 하기 때문에 배열(array)의 크기(size)에 비례(proportional)하는 시간(time)이 필요(requires)하다. 이를 선형 시간(linear time)이라 하며, O(n)라고 표기하기도 한다.
- 마찬가지로(likewise), 배열(array)의 중간(middle)에 추가(add)하면 해당 인덱스(index)부터의 모든 값(values)을 뒤로 이동(shifted over)해야 한다.
n/2
작업(operations)이 필요하므로, 실행 시간(running time)은 여전히(still) 배열(array) 크기(size) 또는 O(n), 선형(linear)이다. append
를 사용하여 배열(array)의 끝(end)에 추가(add)할 때, 공간(room)이 있다면 O(1)이 필요하다. 공간(room)이 없으면 Swift는 새 요소(element)를 추가(adding)하기 전에 다른 곳에 공간(space)을 만들고 전체 배열(entire array)을 복사(copy)해야 한다. 이 작업은 O(n)이 필요하다. 대부분(most of the time) 배열(arrays)은 가득 차 있지 않기 때문에, 평균적인 경우(average case)는 O(1)이다.
요소 삭제(deleting elements): 요소(element)를 삭제(deleting)하면 제거된(removed) 요소(element)가 있던 자리에 공백(gap)이 남는다. 배열(array)의 모든 요소(elements)는 순차적(sequential)이어야 하므로, 요소(elements)를 앞으로(forward) 이동(shifting)하여 이 간격을 매워야(closed) 한다.
복잡도(complexity)는 요소(elements) 삽입(inserting)과 유사하다. 끝(end)에서 요소(element)를 제거(removing)하는 경우는 O(1) 작업(operation)이고, 그렇지 않으면 복잡도(complexity)는 O(n)이다.
요소 검색(searching for an element): 검색(searching for)하려는 요소(element)가 배열(array)의 첫 번째 요소(element)인 경우, 한 번의 작업(single operation) 이후 검색(search)이 종료된다. 요소(element)가 존재(exist)하지 않으면, 요소(element)를 찾을 수 없다는 것을 인식(realize)할 때까지
N
번의 작업(operations)을 수행(perform)해야 한다. 평균적으로(on average), 요소(element)를 검색(searching)하는 데n/2
번의 작업(operations)이 필요하므로 검색(searching)은 O(n) 복잡도(complexity)를 갖는다.딕셔너리(dictionaries)와 집합(sets)에 대해 배우면, 성능(performance) 특성(characteristics)이 배열(arrays)과 어떻게 다른지 알 수 있다. 그러면 특정(particular) 사례(case)에 사용할 컬렉션(collection) 유형(type)에 대한 힌트(hint)를 얻을 수 있다.
Dictionaries
딕셔너리(dictionary)는 순서가 지정되지 않은(unordered) 쌍(pairs)의 모음(collection)이며, 각 쌍(pair)은 키(key)와 값(value)으로 구성된다(comprises).
아래 도표(diagram)와 같이 키(keys)는 고유(unique)하다. 동일한 키(key)는 딕셔너리(dictionary)에 두 번(twice) 나타날(appear) 수 없지만, 다른(different) 키(keys)는 동일한 값(value)을 가리킬(point to) 수 있다. 모든 키(keys)는 동일한 유형(type)이어야 하며 모든 값(values)은 동일한 유형(type)이어야 한다.
딕셔너리(dictionaries)는 식별자(identifier)를 사용하여 값(values)을 조회(look up)하려는 경우에 유용하다. 예를 들어(for example), 이 책(book)의 목차(the table of contents)는 장(chapter) 이름을 페이지(page) 번호(numbers)에 매핑하여 읽고 싶은 장(chapter)으로 쉽게 건너뛸(skip) 수 있다.
배열(array)의 경우, 인덱스(index)로만 값(value)을 가져올(fetch) 수 있다. 인덱스(index)는 정수(integer)여야 하며, 모든 인덱스(indexes)는 순차적(sequential)이어야 한다. 딕셔너리(dictionary)에서 키(keys)는 특정(particular) 순서(order) 없이 모든(any) 유형(type)이 될 수 있다.
Creating dictionaries
사전(dictionary)을 만드는 가장 쉬운 방법은 딕셔너리(dictionary) 리터럴(literal)을 사용하는 것이다. 이는 대괄호(square brackets)로 묶인(enclosed) 쉼표(commas)로 구분된(separated) 키-값 쌍(key-value pairs)의 목록(list)이다.
이전 카드(card) 게임(game)의 경우, 두 개의 배열(arrays)을 사용하여 참가자(players)를 점수(scores)에 매핑(map)하는 대신 딕셔너리(dictionary) 리터럴(literal)을 사용할 수 있다:
var namesAndScores = ["Anna": 2, "Brian": 2, "Craig": 8, "Donna": 6] print(namesAndScores) // > ["Craig": 8, "Anna": 2, "Donna": 6, "Brian": 2]
이 예(example)에서 컴파일러(compiler)는 딕셔너리(dictionary) 유형(type)을
[String: Int]
로 추론(infers)한다.namesAndScores
는 키(keys)로 문자열(strings)을, 값(values)으로 정수(integers)를 갖는 딕셔너리(dictionary)이다.딕셔너리(dictionary)를 출력(print)해 보면, 쌍(pairs)에 특별한(particular) 순서(order)가 없음을 알 수 있다. 배열(arrays)과 달리 딕셔너리(dictionaries)는 순서가 없음(unordered)을 기억해야 한다. 빈(empty) 딕셔너리(dictionary) 리터럴(literal)은
[:]
로 사용한다. 이를 사용하여 다음과 같이 기존(existing) 딕셔너리(dictionary)를 비울(empty) 수 있다:namesAndScores = [:]
...또는 다음과 같이 새로운 딕셔너리(dictionary)를 생성한다:
var pairs: [String: Int] = [:]
컴파일러(compiler)는 빈(empty) 딕셔너리(dictionary) 리터럴(literal)에서 딕셔너리(dictionary)의 유형(type)을 추론(infer)할 수 없으므로 유형 주석(type annotation)이 필요하다.
딕셔너리(dictionary)를 생성(create)한 후 해당 용량(capacity)을 정의(define)할 수 있다:
pairs.reserveCapacity(20)
딕셔너리(dictionary)에 저장(store)할 데이터의 양을 알고 있을때,
ReserveCapacity(_:)
를 사용하면 성능(performance)을 쉽게 향상(improve)시킬 수 있다.Accessing values
배열(arrays)과 마찬가지로, 딕셔너리(dictionary) 값(values)에 접근(access)하는 방법은 여러 가지(several)가 있다.
Using subscripting
딕셔너리(dictionaries)는 값(values)에 접근(access)하기 위한 첨자(subscripting)를 지원(support)한다. 배열(arrays)과 달리(unlike) 인덱스(index)가 아닌 키(key)로 해당 값(value)에 접근(access)한다. 예를 들어(for example), Anna의 점수(score)를 얻으려면 다음과 같이 입력(type)한다:
namesAndScores = ["Anna": 2, "Brian": 2, "Craig": 8, "Donna": 6] // Restore the values print(namesAndScores["Anna"]!) // 2
반환(return) 유형(type)은 옵셔널(optional)이다. 딕셔너리(dictionary)는
Anna
키(key)의 쌍(pair)이 있는지 확인하고, 있다면 해당 값(value)을 반환(return)한다. 딕셔너리(dictionary)가 키(key)를 찾지 못하면nil
을 반환(return)한다.namesAndScores["Greg"] // nil
배열(arrays)의 경우, 범위를 벗어난(out-of-bounds) 첨자(subscript) 접근(access)은 런타임 오류(runtime error)를 발생(causes)시키지만, 딕셔너리(dictionaries)는 결과(results)가 옵셔널(optional)로 래핑(wrapped in)되기 때문에 다르다(different). 옵셔널(optionals)을 사용한 첨자(subscript) 접근(access)은 매우 강력하다(powerful). 배열(array)을 사용할 때와 달리, 모든 키(keys)를 반복(iterating)하지 않고도 특정(specific) 참가자(player)가 게임(game)에 있는지 확인(find out)할 수 있다.
Using properties and methods
딕셔너리(dictionaries)는 배열(arrays)과 같이 Swift의
Collection
프로토콜(protocol)을 준수(conform)한다. 그렇기 때문에 많은 동일한 속성(properties)을 공유(share)한다. 예를 들어(for example), 배열(arrays)과 딕셔너리(dictionaries)에는 모두isEmpty
와count
속성(properties)이 있다.namesAndScores.isEmpty // false namesAndScores.count // 4
Note: 컬렉션(collection)에 요소(elements)가 있는지 여부를 알고 싶다면,
count
를 0으로 비교(comparing)하는 것보다 항상(always)isEmpty
속성(property)을 사용하는 것이 좋다. 배열(arrays)과 딕셔너리(dictionaries)는 상수 시간(constant time)에count
를 계산(compute)하지만 모든 컬렉션(collection)이 그런 것은 아니다. 예를 들어(for example),String
에 대한count
는 모든 문자(characters)를 반복(loop)해야 한다. 이와 대조적으로(by contrast)isEmpty
는 모든(every) 컬렉션(collection) 유형(type)에 대해 얼마나 많은 값(values)이 있는지 관계없이 항상 상수 시간(constant time)에 실행(runs)된다.Modifying dictionaries
딕셔너리(dictionaries)를 생성(create)하고, 그 내용(contents)에 접근(access)하는 것은 쉽다. 하지만 수정(modifying)하는 것은 어떨까?
Adding pairs
Bob이 게임(game)에 참여(join)하고 싶어한다.
그가 합류(join)하기 전에 세부 사항(details)을 살펴본다(take a look):
var bobData = [ "name": "Bob", "profession": "Card Player", "country": "USA" ]
이 딕셔너리(dictionary)는
[String: String]
유형(type)이며, 변수(variable)에 할당(assigned)되기 때문에 변경 가능(mutable)하다. Bob에 대한 추가 정보(information)를 받았고, 이를 딕셔너리(dictionary)에 추가(add)하려 한다. 다음과 같이 하면 된다:bobData.updateValue("CA", forKey: "state")
첨자(subscripting)를 사용하여 쌍(pairs)을 추가(add)하는 더 간단한 방법(a shorter way)도 있다:
bobData["city"] = "San Francisco"
Bob은 전문(professional) 카드꾼(card player)이다. 지금까지는, 그가 명단(roster)에 훌륭하게(excellent) 추가(addition)된 것 같다.
Mini-exercise
주어진 참가자(player)의 도시(city)와 주(state)를 출력(prints)하는 함수(function)를 작성해 본다.
Updating values
과거(past)에 Bob은 카드(cards) 게임 중, 부정 행위(cheating)를 하다가 적발(caught)된 것으로 보인다. 그는 단순한 전문가(professional)가 아니라 카드깡(card shark)이다. 그는 아무도 자신을 알아보지(recognize) 못하게 이름과 직업(profession)을 바꿔(change)달라고 부탁(asks)한다.
Bob이 자신의 방식을 바꾸고자(eager to change) 하는 것처럼 보이기에 수정에 동의한다. 먼저(first), 그의 이름을 Bob에서 Bobby로 변경(change)한다:
bobData.updateValue("Bobby", forKey: "name") // Bob
쌍(pairs) 추가(adding)에 대해 배웠을때, 이 메서드(method)를 보았다.
updateValue(_:forKey:)
는 주어진 키(key)의 값(value)을 새 값(new value)으로 바꾸고, 이전 값(old value)을 반환(returns)한다. 키가 존재(exist)하지 않는다면, 이 메서드(method)는 새 쌍(pair)을 추가하고nil
을 반환(return)한다.추가(adding)할 때와 마찬가지로, 첨자(subscripting)를 사용하여 더 적은 코드(code)로 이 작업을 수행할 수 있다:
bobData["profession"] = "Mailman"
updateValue(_:forKey:)
와 마찬가지로, 이 코드(code)는 이 키(key)의 값(value)을 업데이트(updates)하거나 키가 없다면 새 쌍(pair)을 생성한다(creates).Removing pairs
Bob, 그러니까 Bobby는 여전히 자신이 안전(safe)하지 않다고 느끼며, 그의 행방(whereabouts)에 대한 모든 정보(information)를 제거(remove)하기 원한다:
bobData.removeValue(forKey: "state")
이 메서드(method)는 딕셔너리(dictionary)에서
state
키(key)와 관련(associated) 값(value)을 제거(remove)한다. 예상할 수 있듯이(as you might expect), 첨자(subscripting)를 사용하여 이 작업을 수행하는 더 간단한(shorter) 방법이 있다:bobData["city"] = nil
키(key)의 관련 값(associated value)으로 nil을 할당(assigning)하면 딕셔너리(dictionary)에서 해당 쌍(pair)이 제거(removes)된다.
Note: 옵셔널(optional) 유형(types)의 값(values)이 있는 딕셔너리(dictionary)를 사용하는 경우라도,
dictionary[key] = nil
은 키(key)를 완전히(completely) 제거(removes)한다. 키(key)를 유지(keep)하고, 값(value)을nil
로 설정(set)하려면updateValue
메서드(method)를 사용해야 한다.Iterating through dictionaries
for-in
반복문(loop)은 딕셔너리(dictionary)를 반복(iterate over)할 때도 작동(works)한다. 그러나 딕셔너리(dictionary)의 항목(items)은 쌍(pairs)이므로 튜플(tuple)을 사용한다:for (player, score) in namesAndScores { print("\(player) - \(score)") } // > Craig - 8 // > Anna - 2 // > Donna - 6 // > Brian - 2
키(keys)만 반복(iterate over)하는 것도 가능(possible)하다:
for player in namesAndScores.keys { print("\(player), ", terminator: "") // no newline } print("") // print one final newline // > Craig, Anna, Donna, Brian,
딕셔너리(dictionary)의
values
속성(property)을 사용하여, 동일한 방식(manner)으로 값(values)만 반복(iterate over)할 수 있다.Running time for dictionary operations
딕셔너리(dictionaries)가 어떻게 작동(work)하는지 알려면(examine) 해싱(hashing)이 무엇이며 어떻게 작동(works)하는지 이해(understand)해야 한다. 해싱(hashing)은
String
,Int
,Double
,Bool
등의 값(value)을 해시 값(hash value)이라고 하는 숫자 값(numeric value)으로 변환(transforming)하는 과정(process)이다. 그런 다음 이 값(value)을 사용하여 해시 테이블(hash table)에서 값(values)을 빠르게(quickly) 조회(look up)할 수 있다.Swift 딕셔너리(dictionaries)는 키(keys)에 대한 유형 요구 사항(requirement)이 있다. 키(keys)는 반드시 Hashable이어야 한다. 그렇지 않으면 컴파일러 오류(compiler error)가 발생한다.
다행히(fortunately), Swift의 모든 기본(basic) 유형(types)은 이미(already)
Hashable
이며, 해시 값(hash value)을 가지고 있다. 이 값(value)은 결정적(deterministic)이어야 한다. 즉, 주어진 값(given value)은 항상(always) 동일한 해시 값(hash value)을 반환(return)해야 한다.some string
에 대한 해시 값(hash value)을 몇 번을 계산(calculate)하더라도 항상(always) 같은 값(value)이 나온다. 그러나 해시 값(hash value)은 프로그램(program)을 실행(run)할 때마다 다르기 때문에 절대(never) 저장(save)해서는 안 된다.다음으로 딕셔너리(dictionary)의 다양한(various) 작업(operations) 성능(performance)을 알아본다. 뛰어난 성능(performance)은 값(value) 충돌(collisions)을 피하는 우수한 해싱 함수(hashing function)를 갖는 데 달려(hinges) 있다.
해싱 함수(hashing function)가 좋지 않은(poor) 경우, 아래의 모든 작업(operations)은 선형 시간(linear time) O(n) 성능(performance)으로 저하(degenerate)된다. 다행히(fortunately), 내장(built-in) 유형(types)은 훌륭한(great) 범용(general-purpose)
Hashable
을 구현(implementations)하고 있다.요소 접근(accessing elements): 키(key)의 값(value)을 가져오는 것은 상수 시간(constant time) 작업(operation) 또는 O(1)이다. 요소 삽입(inserting elements): 요소(element)를 삽입(insert)하려면 딕셔너리(dictionary)에서 키(key)의 해시 값(hash value)을 계산(calculate)한 다음 해당 해시(hash)를 기반(based on)으로 데이터(data)를 저장(store)해야 한다. 이는 모두 O(1) 연산(operations)이다.
요소 삭제(deleting elements): 다시 말하지만, 딕셔너리(dictionary)는 요소(element)를 정확히(exactly) 찾아(find) 제거(remove)하기 위해 해시 값(hash value)을 계산(calculate)해야 한다. 이것 또한 O(1) 작업(operation)이다.
요소 검색(searching for an element): 위에서 언급(mentioned)한 것처럼, 요소(element)에 접근(accessing)하는 것은 상수 실행 시간(constant running time)이므로 검색(searching) 복잡도(complexity)도 O(1)이다.
이러한 실행 시간(running times)은 배열(arrays)에 비해(compare) 유리(favorably)하지만, 딕셔너리(dictionaries)를 사용하면 순서(order) 정보(information)를 잃게(lose) 된다는 점을 기억(remember)해야 한다.
Sets
집합(set)은 동일한 유형(type)의 고유한(unique) 값(values)이 순서가 없는(unordered) 컬렉션(collection)이다. 이는 한 항목(item)이 컬렉션(collection)에 두 번 이상 나타나지 않도록 하면서 항목(items)의 순서(order)가 중요(important)하지 않을때 매우(extremely) 유용(useful)할 수 있다.
위의 집합(set) 그림(illustration)에는 4개의 문자열(strings)이 있다. 요소(elements)에는 순서(order)가 없다.
Creating sets
Set
다음 꺾쇠 괄호(angle brackets) 안(inside)에 유형(type)을 작성하여, 명시적으로(explicitly) 집합(set)을 선언(declare)할 수 있다:let setOne: Set<Int> = [1]
Set literals
집합(sets)에는 자체 리터럴(literals)이 없다. 배열 리터럴(array literals)을 사용하여 초기 값(initial values)으로 집합(set)을 생성(create)한다. 다음 예(example)를 생각해 본다(consider):
let someArray = [1, 2, 3, 1]
이것은 배열(array)이다. 배열 리터럴(array literals)을 사용하여 집합(set)을 만드는(create) 방법은 다음과 같다:
var explicitSet: Set<Int> = [1, 2, 3, 1]
변수(variable)를
Set
으로 명시적(explicitly) 선언(declare)해야 한다. 그러나(however) 컴파일러(compiler)가 다음과 같이 요소(element) 유형(type)을 추론(infer)하도록 할 수 있다:var someSet = Set([1, 2, 3, 1])
집합(set)의 가장 중요한(the most important) 특징(features)을 보려면, 방금 생성한(created) 집합(set)을 출력(print)해 본다:
print(someSet) // > [2, 3, 1] but the order is not defined
먼저(first), 특별한(specific) 순서(ordering)가 없음을 알 수 있다. 둘째(second), 값(value)
1
를 두 개 사용하여 집합(set)을 만들었지만 해당 값(value)은 한 번만 나타난다(appears). 집합(set)의 값(values)은 고유(unique)해야 한다.Accessing elements
contains(_:)
를 사용하여 특정(specific) 요소(element)의 존재(existence)를 확인(check for)할 수 있다:print(someSet.contains(1)) // > true print(someSet.contains(4)) // > false
집합(set)의 요소(elements) 중 하나를 반환(return)하는
first
및last
속성(properties)을 사용할 수도 있다. 그러나(however), 집합(sets)은 순서가 없기(unordered) 때문에 어떤 항목(item)을 받게 될지 알 수 없다.Adding and removing elements
insert(_:)
를 사용하여 집합(set)에 요소(elements)를 추가(add)할 수 있다. 요소(element)가 이미(already) 있다면(exists), 메서드(method)는 아무 작업도 수행하지 않는다.someSet.insert(5)
다음과 같이 집합(set)에서 요소(element)를 제거(remove)할 수 있다:
let removedElement = someSet.remove(1) print(removedElement!) // > 1
remove(_:)
는 제거(removed)된 요소(element)가 집합(set)에 있으면 반환(returns)하고 그렇지 않으면(otherwise)nil
을 반환(returns)한다.Running time for set operations
집합(sets)은 딕셔너리(dictionaries)와 매우 유사한(similar) 구현(implementation)을 가지고 있으며, 요소(elements)가 해시 가능(hashable)해야 한다. 모든 작업(operations)의 실행 시간(running time)은 사전(dictionaries)과 동일(identical)하다.
Challenges
다음 단계로 넘어가기 전에(before moving on) 배열(arrays), 딕셔너리(dictionaries), 집합(sets)에 대한 지식(knowledge)을 확인(test)하기 위한 몇 가지 챌린지(challenges)가 있다. 스스로 해결(solve)해 보려고 하는 것이 가장 좋지만, 막힌다면(get stuck) 다운로드(download)나 책의 소스 코드 링크(source code link)에서 해답(solutions)을 참고할 수 있다.
Challenge 1: Which is valid
다음 중 올바른(valid) 구문(statements)은 무엇인지 확인해 본다.
1. let array1 = [Int]() 2. let array2 = [] 3. let array3: [String] = []
다음 5개 구문(statements)에 대해
array4
는 다음과 같이 선언(declared)되어 있다:let array4 = [1, 2, 3]
4. print(array4[0]) 5. print(array4[5]) 6. array4[1...2] 7. array4[0] = 4 8. array4.append(4)
마지막 5개의 구문(statements)에 대해
array5
는 다음과 같이 선언(declared)되어 있다:var array5 = [1, 2, 3]
9. array5[0] = array5[1] 10. array5[0...1] = [4, 5] 11. array5[0] = "Six" 12. array5 += 6 13. for item in array5 { print(item) }
Challenge 2: Remove the first number
정수(integers) 배열(array)에서 주어진 정수(integer)의 첫 번째(first) 항목(occurrence)을 제거(removes)하는 함수(function)를 작성한다. 함수의 시그니처(signature)는 다음과 같다:
func removingOnce(_ item: Int, from array: [Int]) -> [Int]
Challenge 3: Remove the numbers
정수(integers) 배열(array)에서 주어진 정수(integer)의 모든 항목(occurrences)을 제거(removes)하는 함수(function)를 작성한다. 함수의 시그니처(signature)는 다음과 같다:
func removing(_ item: Int, from array: [Int]) -> [Int]
Challenge 4: Reverse an array
배열(arrays)에는 원본 배열(original array)의 요소(elements)를 역순(reverse order)으로 정렬한 배열을 반환(returns)하는
reversed()
메서드(method)가 있다.reversed()
를 사용하지 않고 동일한 작업(same thing)을 수행하는 함수(function)를 작성해 본다. 함수(function)의 시그니처(signature)는 다음과 같다:func reversed(_ array: [Int]) -> [Int]
Challenge 5: Return the middle
배열(array)의 중간(middle) 요소(element)를 반환(returns)하는 함수(function)를 작성한다. 배열(array)의 크기(size)가 짝수(even)이면, 두 중간(middle) 요소(elements) 중 첫 번째(first)를 반환(return)한다.
func middle(_ array: [Int]) -> Int?
Challenge 6: Find the minimum and maximum
정수(integers) 배열(array)에서 최소값(minimum)과 최대값(maximum)을 계산(calculates)하는 함수(function)를 작성한다. 이 값을 직접 계산해야 하며,
min
및max
메서드(methods)을 사용(use)해선 안 된다. 주어진 배열이 비어 있다면nil
을 반환(return)한다.함수(function)의 시그니처(signature)는 다음과 같다:
func minMax(of numbers: [Int]) -> (min: Int, max: Int)?
Challenge 7: Which is valid
다음 중 올바른(valid) 구문(statements)은 무엇인지 확인해 본다.
1. let dict1: [Int, Int] = [:] 2. let dict2 = [:] 3. let dict3: [Int: Int] = [:]
다음(next) 네 구문(statements)에 대해, 아래 딕셔너리(dictionary)를 사용한다:
let dict4 = ["One": 1, "Two": 2, "Three": 3]
4. dict4[1] 5. dict4["One"] 6. dict4["Zero"] = 0 7. dict4[0] = "Zero"
다음(next) 세 구문(statements)에 대해, 아래 딕셔너리(dictionary)를 사용한다:
var dict5 = ["NY": "New York", "CA": "California"]
8. dict5["NY"] 9. dict5["WA"] = "Washington" 10. dict5["CA"] = nil
Challenge 8: Long names
두 글자(two-letter) 상태(state) 코드(codes)를 키(keys)로 사용하고, 전체 상태(full state) 명을 값(values)으로 하는 딕셔너리(dictionary)가 주어지면 이름이 8자보다 긴 모든 상태(states)를 출력(prints)하는 함수(function)를 작성한다. 예를 들어(for example), 딕셔너리(dictionary)
["NY": "New York", "CA": "California"]
의 출력은California
가 된다.Challenge 9: Merge dictionaries
두 개의 딕셔너리(dictionaries)를 하나로 결합(combines)하는 함수(function)를 작성한다. 특정(certain) 키(key)가 두 딕셔너리(dictionaries)에 모두 나타나면(appears), 첫 번째 딕셔너리(dictionary)의 쌍(pair)을 무시(ignore)한다. 함수(function)의 시그니처(signature)는 다음과 같다:
func merging(_ dict1: [String: String], with dict2: [String: String]) -> [String: String]
Challenge 10: Count the characters
문자열(string)에 어떤 문자(characters)가 있는지(occur), 그리고 이러한 각 문자(characters)가 몇 개 있는지(occur) 계산(calculates)하는 함수(function)
OccurrencesOfCharacters
를 선언(declare)한다. 결과(result)는 딕셔너리(dictionary)로 반환(return)한다. 함수(function)의 시그니처(signature)는 다음과 같다:func occurrencesOfCharacters(in text: String) -> [Character: Int]
Hint:
String
은 for 문(statement)으로 반복(iterate over)할 수 있는 문자(characters)의 모음(collection)이다. Bonus: 코드를 더 짧게(shorter) 만들기 위해, 딕셔너리(dictionaries)에는 해당 값이 없는 경우 기본값(default value)을 추가(add)할 수 있는 특수(special) 첨자(subscript) 연산자(operator)가 있다. 예를 들어(for example),dictionary["a", default: 0]
는 문자(character) "a"가 발견되지 않으면(not found)nil
을 반환(returning)하는 대신(instead of) 0 항목(entry)을 생성(creates)한다.Challenge 11: Unique values
딕셔너리(dictionary)의 모든 값(values)이 고유(unique)한 경우
true
를 반환(returns)하는 함수(function)를 작성한다. 집합(set)을 사용하여 고유성(uniqueness)을 확인(test)한다. 함수(function)의 시그니처(signature)는 다음과 같다:func isInvertible(_ dictionary: [String: Int]) -> Bool
Challenge 12: Removing keys and setting values to nil
주어진(given) 딕셔너리(dictionary)는 다음과 같다:
var nameTitleLookup: [String: String?] = ["Mary": "Engineer", "Patrick": "Intern", "Ray": "Hacker"]
"Patrick"
키(key)의 값(value)을nil
로 설정(set)하고,"Ray"
에 대한 키(key)와 값(value)을 완전히(completely) 제거(remove)한다.Key points
Sets:
- 동일한 유형(type)의 고유한(unique) 값(values)이 정렬되지 않은(unordered) 컬렉션(collections)이다.
- 특정 항목이 컬렉션(collection)에 포함(included)된 있는지 여부를 확인해야 할 때 가장 유용(useful)하다.
Dictionaries:
- 키-값(key-value) 쌍(pairs)의 정렬되지 않은(unordered) 컬렉션(collections)이다.
- 키(keys)는 모두 동일한 유형(type)이고, 값(values) 또한 모두 동일한 유형(type)이다.
- 첨자(subscripting)를 사용하여 값(values)을 가져오고 쌍을 추가(add), 업데이트(update), 제거(remove)할 수 있다.
- 키(key)가 딕셔너리(dictionary)에 없으면 검색(lookup)은
nil
을 반환(returns)한다. - 딕셔너리(dictionary)의 키(key)는
Hashable
프로토콜(protocol)을 준수(conforms)하는 유형(type)이어야 한다. String
,Int
,Double
과 같은 기본(basic) Swift 유형(types)은 기본적으로(out of the box)Hashable
하다.
Arrays:
- 같은 유형(type)의 값(values)들이 순서가 지정된(ordered) 컬렉션(collections)이다.
- 첨자(subscripting) 또는 여러 속성(properties) 및 메서드(methods) 중 하나를 사용하여, 요소(elements)에 접근(access)하고 업데이트(update) 한다.
- 범위를 벗어난(out of bounds) 인덱스(index)에 접근(accessing)하지 않도록 주의해야(be wary of) 한다.
'Raywenderlich > Swift Apprentice' 카테고리의 다른 글
Chapter 9: Strings (0) 2022.09.29 Chapter 8: Collection Iteration With Closures (0) 2022.08.29 Chapter 6: Optionals (0) 2021.10.08 Chapter 5: Functions (0) 2021.09.02 Chapter 4: Advanced Control Flow (0) 2021.08.03