ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Chapter 7: Arrays, Dictionaries & Sets
    Raywenderlich/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)에 사용한 IntString 같은 유형(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) 있으면 firstnil을 반환(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), firstmin()이 항상(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: firstlast 속성(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)가 발생하는 이유는 upcomingPlayersSliceplayers와 저장소(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)에는 모두 isEmptycount 속성(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)하는 firstlast 속성(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)를 작성한다. 이 값을 직접 계산해야 하며, minmax 메서드(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
Designed by Tistory.