본문 바로가기
프로그래밍/Swift

[Swift] Collection Type (Array, Set, Dictionary)

by 별준 2021. 12. 15.

References

Contents

  • Mutability of Collections
  • Arrays
  • Sets
  • Performing Set Operations
  • Dictionaries

Swift는 Arrays(배열), Sets(집합), Dictionaries(딕셔너리), 3가지의 주요 콜렉션 타입을 지원합니다. 배열은 순서가 있는 값들의 콜렉션이고, Set은 유니크한 값들의 순서없는 콜렉션이며, 딕셔너리는 key-value 관계의 값들의 순서가 없는 콜렉션입니다.

이 콜렉션 타입들은 저장할 수 있는 value 또는 key 타입에 대해 항상 명확합니다. 따라서 잘못된 타입의 값을 콜렉션에 실수로 삽입하는 경우가 없고, Set에서 검색할 값의 타입을 항상 신뢰할 수 있습니다.


Mutability of Collections

배열, 집합, 딕셔너리를 만들어 변수에 할당하면, 생성된 콜렉션은 변경이 가능합니다. 콜렉션에서 item을 추가, 제거 또는 변경할 수 있습니다. 상수에 할당하면 해당 콜렉션은 변경할 수 없으며 크기와 내용을 변경할 수 없습니다.

 


Arrays

배열은 같은 타입의 값을 저장하는 순서가 있는 리스트입니다. 배열에서 같은 값은 다른 위치에 여러 번 등장할 수 있습니다.

Array Type Shorthand Syntax

배열 타입은 Array<Element>로 적을 수 있는데, 여기서 Element는 배열에 저장할 값의 타입입니다. 축약하여 [Element]로 적을 수도 있습니다. 이 두 가지 형태는 기능적으로 동일한데, 보통 축약형으로 많이 사용되는 것 같습니다.

 

Creating an Empty Array

initializer 문법을 사용하여 특정 타입의 빈 배열을 생성할 수 있습니다.

var someInts: [Int] = []
print("someInts is of type [Int] with \(someInts.count) items.")
// Prints "someInts is of type [Int] with 0 items."

위 코드에서 someInts 변수의 타입은 Initializer의 타입으로부터 [Int]로 추론됩니다.

 

또는, 함수의 argument나 타입이 있는 변수나 상수처럼 문맥에서 타입 정보가 제공되는 경우에 빈 배열 리터럴([], 빈 대괄호쌍)만을 사용하여 빈 배열을 생성할 수 있습니다.

someInts.append(3)
// someInts now contains 1 value of type Int
someInts = []
// someInts is now an empty array, but is still of type [Int]

 

Creating an Array with a Default Value

Swift의 배열 타입은 같은 값들로 채워진 특정 크기의 배열을 생성하는 이니셜라이저를 제공합니다. 이 이니셜라이저에 적절할 타입의 기본값(called repeating)과 새로운 배열에서 이 값이 반복되는 횟수(called count)를 전달하여 생성할 수 있습니다.

var threeDoubles = Array(repeating: 0.0, count: 3)
// threeDoubles is of type [Double], and equals [0.0, 0.0, 0.0]

 

Creating an Array by Adding Two Arrays Together

이미 존재하는 호환 가능한 타입의 두 배열을 '+' 연산자로 더해서 새로운 배열을 생성할 수 있습니다. 새로운 배열의 타입은 두 배열의 타입에서 추론하여 결정됩니다.

// var threeDoubles = Array(repeating: 0.0, count: 3)
var anotherThreeDoubles = Array(repeating: 2.5, count: 3)
// anotherThreeDoubles is of type [Double], and equals [2.5, 2.5, 2.5]

var sixDoubles = threeDoubles + anotherThreeDoubles
// sixDoubles is inferred as [Double], and equals [0.0, 0.0, 0.0, 2.5, 2.5, 2.5]

 

Creating an Array with an Array Literal

배열 리터럴을 통해서도 초기화를 할 수 있습니다. 배열 리터널의 콤마(,)로 구분되는 값들의 리스트로 작성되고, 대괄호([]) 쌍으로 둘러싸여 있습니다.

var shoppingList: [String] = ["Eggs", "Milk"]
// shoppingList has been initialized with two initial items

위 예제 코드에서 배열 문자열은 두 개의 String 타입의 값을 포함합니다. 이는 shoppingList 변수의 선언 타입과 일치하므로 이 두 개의 문자열 값을 초기 항목으로 초기화됩니다.

 

Swift의 타입 추론 덕분에, 같은 유형의 값을 포함하는 배열 리터럴로 초기화할 때 배열의 타입을 명시적으로 적을 필요가 없습니다. 배열 리터럴의 모든 값은 동일한 타입이기 때문에, [String]이 올바른 타입이라고 추론할 수 있습니다.

var shoppingList = ["Eggs", "Milk"]

 

Accessing and Modifying an Array

배열의 메소드와 속성을 사용하거나 subscript를 사용하여 배열에 액세스하고 수정할 수 있습니다.

배열의 item 개수를 찾으려면 read-only인 count 속성을 사용하면 됩니다.

print("The shopping list contains \(shoppingList.count) items.")
// Prints "The shopping list contains 2 items."

Boolean 값인 isEmpty 속성은 count 속성이 0임을 체크하는 단축어입니다.

if shoppingList.isEmpty {
    print("The shopping list is empty.")
} else {
    print("The shopping list isn't empty.")
}
// Prints "The shopping list isn't empty."

 

만약 배열의 끝에 새로운 item을 추가하고자 한다면 배열의 append(_:) 메소드를 호출하여 추가할 수 있습니다.

shoppingList.append("Flour")
// shoppingList now contains 3 items, and someone is making pancakes

이외에도 '+=' 연산자를 사용하여 배열에 하나 이상의 item을 추가할 수 있습니다.

shoppingList += ["Baking Powder"]
// shoppingList now contains 4 items
shoppingList += ["Chocolate Spread", "Cheese", "Butter"]
// shoppingList now contains 7 items

 

subscript 문법을 사용하여 배열의 값에 접근할 수 있습니다.

var firstItem = shoppingList[0]
// firstItem is equal to "Eggs"

또한 인덱스로 접근하여 존재하는 값을 변경할 수 있습니다.

shoppingList[0] = "Six eggs"
// the first item in the list is now equal to "Six eggs" rather than "Eggs"

subscript 문법을 사용할 때, 지정하는 인덱스는 유효한 값이어야 합니다. 예를 들어, 'shoppingList[shoppingList.count] = "Salt"'는 런타임 에러를 발생시킵니다.

 

subscript 문법으로 지정한 범위 값들을 한 번에 변경할 수도 있습니다. 이때, 바꾸려는 값의 범위와 바꿀 값의 범위가 달라도 변경이 가능합니다.

shoppingList[4...6] = ["Bananas", "Apples"]
// shoppingList now contains 6 items

위 코드는 기존의 4~6 인덱스에 위치하던 "Chocvolate Spread", "Cheese", "Butter"를 "Bananas", "Apples"로 변경합니다.

 

지정한 인덱스에 새로운 item을 추가하려면 배열의 insert(_:at:) 메소드를 사용하면 됩니다.

shoppingList.insert("Maple Syrup", at: 0)
// shoppingList now contains 7 items
// "Maple Syrup" is now the first item in the list

위 코드는 "Maple Syrup"을 배열의 첫 번째 위치(index: 0)에 삽입합니다.

 

유사하게 특정 인덱스 위치의 item을 제거하려면 remove(at:) 메소드를 사용하면 됩니다. 이 메소드는 지정된 인덱스의 항목을 제거하면서 제거되는 값을 리턴합니다. 만약 필요없다면 무시하면 됩니다.

let mapleSyrup = shoppingList.remove(at: 0)
// the item that was at index 0 has just been removed
// shoppingList now contains 6 items, and no Maple Syrup
// the mapleSyrup constant is now equal to the removed "Maple Syrup" string

 

만약 배열의 마지막 항목을 제거하고 싶다면, removeLast() 메소드를 사용하는 것이 편리합니다. remove(at:) 메소드와 동일하게 removeLast()도 제거되는 항목의 값을 리턴합니다.

let apples = shoppingList.removeLast()
// the last item in the array has just been removed
// shoppingList now contains 5 items, and no apples
// the apples constant is now equal to the removed "Apples" string

 

Iterating Over an Array

for-in 루프를 사용하여 배열 값들의 집합을 반복할 수 있습니다.

for item in shoppingList {
    print(item)
}
// Six eggs
// Milk
// Flour
// Baking Powder
// Bananas

만약 배열의 값뿐만 아니라 각 항목의 인덱스도 필요하다면 배열 대신 배열의 enumerated() 메소드를 반복에 사용하면 됩니다. 배열의 각 항목에 enumerated() 메소드는 정수와 각 항목으로 구성된 튜플(tuple)을 반환합니다. 정수는 0부터 시작하여 각 항목별로 하나씩 카운트하고, 전체 배열을 걸쳐 열거하면 이 정수 값은 항목의 인덱스 값과 일치합니다. 튜플을 임시 상수 또는 변수로 분해하여 사용할 수도 있습니다.

for (index, value) in shoppingList.enumerated() {
    print("Item \(index + 1): \(value)")
}
// Item 1: Six eggs
// Item 2: Milk
// Item 3: Flour
// Item 4: Baking Powder
// Item 5: Bananas

Sets

Set(집합)은 동일한 타입의 고유한 값을 저장하는 순서가 없는 콜렉션입니다. 항목의 순서가 중요하지 않고, 각 항목이 한 번만 나타나도록 해야하는 경우 배열 대신 집합을 사용하는 것이 좋습니다.

 

Hash Values for Set Types

Set에 저장하려면 타입은 반드시 해시 가능해야합니다. 즉, 타입이 해시 값을 계산할 수 있는 방법을 제공해야 합니다. Swift의 기본 타입(ex, String, Int, Double, Bool)은 기본적으로 해시가 가능하며 Set의 value 타입과 Dictionary의 key 타입에 사용할 수 있습니다. 연관된 값이 없는 Enumeration case 값도 기본적으로 해시가 가능합니다(Enumeration에서 다시 설명할듯).

 

Set Type Syntax

Set은 Set<Element>로 작성하고, Element는 Set에 저장할 타입입니다. 배열과는 다르게 set은 단축어를 제공하지 않습니다.

 

Creating and Initializing an Empty Set

이니셜라이저를 사용하여 특정 타입의 빈 Set을 생성할 수 있습니다.

var letters = Set<Character>()
print("letters is of type Set<Character> with \(letters.count) items.")
// Prints "letters is of type Set<Character> with 0 items."

또는, 문맥상 타입 정보가 이미 주어졌다면, 배열과 마찬가지로 빈 배열 리터럴을 사용하여 빈 Set을 생성할 수 있습니다.

letters.insert("a")
// letters now contains 1 value of type Character
letters = []
// letters is now an empty set, but is still of type Set<Character>

 

Creating a Set with an Array Literal

하나 이상의 값이 있는 배열 리터럴을 사용하여 Set을 초기화할 수 있습니다.

var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
// favoriteGenres has been initialized with three initial items

 

set 타입은 배열 리터럴만으로는 추론할 수 없기 때문에 Set이 반드시 명시적으로 선언되어야 합니다. 그러나 Swift 타입 추론으로 만약 하나의 타입으로만 구성된 배열 리터럴로 초기화한다면 Set 요소의 타입은 적을 필요가 없습니다. 따라서 위의 favoriteGenres는 아래처럼 더 짧게 초기화할 수 있습니다.

var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]

 

Accessing and Modifying a Set

Set의 메소드와 속성을 통해 set에 액세스하고 수정할 수 있습니다.

배열과 동일하게 (read-only) count 속성으로 set에 존재하는 항목의 개수를 알아낼 수 있습니다.

print("I have \(favoriteGenres.count) favorite music genres.")
// Prints "I have 3 favorite music genres."

또한, isEmpty 속성으로 count가 0인지를 확인할 수 있습니다.

if favoriteGenres.isEmpty {
    print("As far as music goes, I'm not picky.")
} else {
    print("I have particular music preferences.")
}
// Prints "I have particular music preferences."

 

set에 새로운 항목을 추가하려면 insert(_:) 메소드를 사용하여 추가할 수 있습니다.

favoriteGenres.insert("Jazz")
// favoriteGenres now contains 4 items

또한, remove(_:) 메소드를 사용하여 set의 항목을 제거할 수 있는데, 이 메소드는 제거되는 항목의 값을 리턴하거나, 제거하려는 값이 set에 존재하지 않는다면 nil을 리턴합니다. 또한, set의 모든 항목을 제거하려면 removeAll() 메소드를 사용할 수 있습니다.

if let removedGenre = favoriteGenres.remove("Rock") {
    print("\(removedGenre)? I'm over it.")
} else {
    print("I never much cared for that.")
}
// Prints "Rock? I'm over it."

특정 항목이 포함되어 있는지 확인하려면, contains(_:) 메소드를 사용하면 됩니다.

if favoriteGenres.contains("Funk") {
    print("I get up on the good foot.")
} else {
    print("It's too funky in here.")
}
// Prints "It's too funky in here."

 

Iterating Over a Set

배열처럼 for-in 루프를 사용하여 set의 값들을 반복할 수 있습니다.

for genre in favoriteGenres {
    print("\(genre)")
}
// Classical
// Jazz
// Hip hop

 

Swift의 Set 타입은 정해진 순서가 없습니다. 특정 순서로 set의 값들을 반복하려면 sorted() 메소드를 사용하면 되는데, '<' 연산자를 사용하여 정렬(오름차순)된 배열로 Set의 요소들을 리턴합니다.

for genre in favoriteGenres.sorted() {
    print("\(genre)")
}
// Classical
// Hip hop
// Jazz

Performing Set Operations

집합을 결합하거나, 공통인 부분을 찾는 등의 편리한 Set 명령들이 있습니다.

Fundamental Set Operations

Set(a와 b)의 다양한 명령어로 구할 수 있는 두 개의 Set의 교집합, 합집합 등을 보여주고 있습니다.

  • intersection(_:) : 두 집합의 교집합을 구합니다.
  • symmetricDifference(_:) : 대칭차집합을 구합니다. 각 집합에는 속하지만 두 집합 모두에는 속하지 않는 값들의 모임입니다.
  • union(_:) : 두 집합의 합집합을 구합니다.
  • subtracting(_:) : 차집합을 구합니다.
let oddDigits: Set = [1, 3, 5, 7, 9]
let evenDigits: Set = [0, 2, 4, 6, 8]
let singleDigitPrimeNumbers: Set = [2, 3, 5, 7]

oddDigits.union(evenDigits).sorted()
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
oddDigits.intersection(evenDigits).sorted()
// []
oddDigits.subtracting(singleDigitPrimeNumbers).sorted()
// [1, 9]
oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted()
// [1, 2, 9]

Set Membership and Equality

위 그림은 세 집합(a, b, c)를 보여주는데, 집합들 간에 공유되는 요소들이 나타내는 영역들을 보여줍니다. 집합 a는 집합 b의 모든 요소들을 포함하므로, a는 b의 superset입니다. 반대로 b는 a의 subset이 됩니다. 그리고 집합 b와 c는 서로 공유하는 부분이 없기 때문에 서로 분리(disjoint)가 가능합니다.

 

  • 동등연산자(=)를 사용하면 두 집합이 모두 동일한 값을 포함하는지의 여부를 확인할 수 있습니다.
  • isSubset(of:) 메소드는 집합의 모든 값이 지정된 집합에 속하는지 확인할 수 있습니다.
  • isSuperset(of:) 메소드를 사용하면 해당 집합(호출 주체)에 지정된 집합의 모든 값이 포함되어 있는지 확인합니다.
  • isStrictSubset(:), isStrictSuperset(:) 메소드를 통해 집합이 지정된 집합과 같지 않지만 subset인지 superset인지 확인합니다.
  • isDisjoint(with:) 메소드로 두 집합에 공통 부분의 존재 여부를 확인할 수 있습니다.
let houseAnimals: Set = ["🐶", "🐱"]
let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"]
let cityAnimals: Set = ["🐦", "🐭"]

houseAnimals.isSubset(of: farmAnimals)
// true
farmAnimals.isSuperset(of: houseAnimals)
// true
farmAnimals.isDisjoint(with: cityAnimals)
// true

 


Dictionaries

딕셔너리는 동일한 타입의 key와 동일한 타입의 value를 함께 저장하는 순서가 없는 콜렉션입니다. 각 값은 유일한 key를 가지고 이는 딕셔너리 내에서 식별자 역할을 합니다. 배열과는 다르게 딕셔너리의 항목에는 지정된 순서가 없습니다. 실제 사전에서 특정 단어의 정의를 찾는 데 사용되는 것과 같은 방식으로 식별자를 기준으로 값을 검색해야할 때 딕셔너리를 사용합니다.

 

Dictionary Type Shorthand Syntax

딕셔너리는 Dictionary<Key, Value>로 작성하여 사용할 수 있는데, 여기서 Key는 딕셔너리 키로 사용되는 값의 타입이고, value는 그 딕셔너리 키에 저장되는 값의 타입입니다.

또한, 축약하여 [Key: Value]로 작성할 수도 있습니다.

두 형태는 기능적으로 동일합니다.

 

Creating an Empty Dictionary

배열처럼 이니셜라이저를 사용하여 특정 타입의 빈 딕셔너리를 생성할 수 있습니다.

var namesOfIntegers: [Int: String] = [:]
// namesOfIntegers is an empty [Int: String] dictionary

이 예제 코드는 [Int: String] 타입의 빈 딕셔너리를 생성하고 있습니다. 이 딕셔너리의 키는 Int 타입이고, 값은 String 타입입니다.

 

만약 문맥상 타입의 정보가 이미 주어졌다면, 빈 딕셔너리 리터럴([:])을 사용하여 빈 딕셔너리를 생성할 수도 있습니다.

namesOfIntegers[16] = "sixteen"
// namesOfIntegers now contains 1 key-value pair
namesOfIntegers = [:]
// namesOfIntegers is once again an empty dictionary of type [Int: String]

 

Creating a Dictionary with a Dictionary Literal

딕셔너리 리터럴(Dictionary Literal)을 사용하여 딕셔너리를 초기화할 수도 있습니다. 딕셔너리 리터럴은 하나 이상의 key-value 쌍으로 구성됩니다. key-value 쌍은 key와 value의 조합이며, 딕셔너리 리터럴에서 key와 value는 콜론(:)으로 구분됩니다.

var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]

위 예제 코드에서 airports는 [String: String] 타입으로 (명시적)선언되었습니다.

하지만 배열처럼 딕셔너리 리터럴에 동일한 타입의 key와 value를 가지고 있으므로, 딕셔너리의 타입을 명시적으로 나타낼 필요는 없습니다. 따라서 airports는 축약하여 아래처럼 선언하고 초기화될 수 있습니다.

var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]

 

Accessing and Modifying a Dictionary

딕셔너리의 메소드와 속성이나 subscript 문법을 사용하여 딕셔너리에 액세스하고 수정할 수 있습니다.

 

먼저 배열과 같이 count 속성으로 딕셔너리 내에 존재하는 항목의 수를 구할 수 있습니다.

print("The airports dictionary contains \(airports.count) items.")
// Prints "The airports dictionary contains 2 items."

isEmpty 속성은 count 속성이 0인지 확인할 수 있는 축약형입니다.

if airports.isEmpty {
    print("The airports dictionary is empty.")
} else {
    print("The airports dictionary isn't empty.")
}
// Prints "The airports dictionary isn't empty."

 

subscript 문법을 사용하여 딕셔너리에 새로운 항목을 추가할 수 있습니다. 적절한 타입의 새로운 key를 subscript 인덱스로 사용하고 새로운 value를 할당하면 됩니다.

airports["LHR"] = "London"
// the airports dictionary now contains 3 items

subscript로 특정 key의 value를 변경할 수도 있습니다.

airports["LHR"] = "London Heathrow"
// the value for "LHR" has been changed to "London Heathrow"

 

subscript 대신, 딕셔너리의 updateValue(_:forKey:) 메소드를 사용하여 특정 key와 value를 추가하거나 수정할 수 있습니다. 다만, subscript와는 다르게 updateValue는 값을 업데이트하면서 이전 값을 리턴합니다.

updateValue(_:forKey:) 메소드는 딕셔너리의 value 타입의 optional 값을 리턴합니다. 예를 들어, 딕셔너리의 value가 String 타입이라면 이 메소드는 String?(optional String) 타입의 값을 리턴합니다. 이 값은 만약 업데이트하는 key가 이전에 존재했다면 이전 값을 리턴하고, 존재하지 않은 key라면 nil을 리턴합니다.

if let oldValue = airports.updateValue("Dublin Airport", forKey: "DUB") {
    print("The old value for DUB was \(oldValue).")
}
// Prints "The old value for DUB was Dublin."

 

subscript 문법을 사용하여 딕셔너리에 특정 key의 값을 검색할 수 있습니다. value가 존재하지 않는 key를 사용할 수도 있는데, 딕셔너리의 subscript도 딕셔너리 value 타입의 optional 값을 리턴하기 때문입니다. 만약 요청된 key가 값을 포함하고 있다면 subscript는 해당 key의 존재하는 value의 optional을 리턴하고 그렇지 않다면 nil를 리턴합니다.

if let airportName = airports["DUB"] {
    print("The name of the airport is \(airportName).")
} else {
    print("That airport isn't in the airports dictionary.")
}
// Prints "The name of the airport is Dublin Airport."

 

subscript를 사용하여 nil을 할당함으로써 딕셔너리의 key-value 쌍을 제거할 수 있습니다.

airports["APL"] = "Apple International"
// "Apple International" isn't the real airport for APL, so delete it
airports["APL"] = nil
// APL has now been removed from the dictionary

subscript 대신 removeValue(forKey:) 메소드를 사용하여 key-value 쌍을 제거할 수 있습니다. 이 메소드는 제거하려는 key-value 쌍이 존재한다면 제거되는 값을 리턴하고 그렇지 않다면 nil을 리턴합니다.

if let removedValue = airports.removeValue(forKey: "DUB") {
    print("The removed airport's name is \(removedValue).")
} else {
    print("The airports dictionary doesn't contain a value for DUB.")
}
// Prints "The removed airport's name is Dublin Airport."

 

Iterating Over a Dictionary

for-in 루프를 사용하여 딕셔너리의 key-value 쌍을 반복할 수 있습니다. 딕셔너리의 각 항목은 (key, value) 튜플로 리턴되고 임시 상수 및 변수로 분해하여 각 반복에서 사용할 수 있습니다.

for (airportCode, airportName) in airports {
    print("\(airportCode): \(airportName)")
}
// LHR: London Heathrow
// YYZ: Toronto Pearson

 

딕셔너리의 keys와 values 속성을 사용하여 반복을 할 수도 있습니다. for-in 루프에 딕셔너리 keys 속성으로 반복하면 keys의 값에 대해서 반복을 수행하고, values는 딕셔너리의 values 값에 대해 반복합니다.

for airportCode in airports.keys {
    print("Airport code: \(airportCode)")
}
// Airport code: LHR
// Airport code: YYZ

for airportName in airports.values {
    print("Airport name: \(airportName)")
}
// Airport name: London Heathrow
// Airport name: Toronto Pearson

 

만약 딕셔너리의 key나 value를 배열(Array) 인스턴스로 가져오고 싶다면, keys나 values 속성을 사용해 새로운 배열을 초기화할 수 있습니다.

let airportCodes = [String](airports.keys)
// airportCodes is ["LHR", "YYZ"]

let airportNames = [String](airports.values)
// airportNames is ["London Heathrow", "Toronto Pearson"]

Swift의 딕셔너리는 정의된 순서가 없으므로 만약 특정 순서로 반복하고 싶다면 sorted() 메소드를 사용하면 됩니다.

'프로그래밍 > Swift' 카테고리의 다른 글

[Swift] Functions (함수)  (0) 2021.12.18
[Swift] Control Flow (제어문)  (0) 2021.12.16
[Swift] 문자열과 문자 (Strings and Characters)  (0) 2021.12.14
[Swift] Basic Operators  (0) 2021.12.12
[Swift] Basic 문법  (0) 2021.12.12

댓글