Swift/Swift 공식 문서

[Swift] 콜렉션 타입 (Collection Types)

hyunjuntyler 2023. 5. 19. 14:01

Swift 에서 배열(array), 집합(set), 딕셔너리(dictionary)를 사용하여 데이터를 어떻게 구성하는지 알아보자. 이를 콜렉션 타입이라 부른다. 배열은 순서를 가지고 있고, 집합은 반복되지 않은 값에 순서가 없다. 딕셔너리는 키-값 쌍의 순서가 없는 콜렉션 타입이다. 배열만 순서를 가지고 있다! 배열을 지정해줄때 타입을 명확하게 지정해줘야 한다.

콜렉션의 가변성 (Mutablility of Collections)

배열, 집합, 딕셔너리는 변경이 가능하다. 변수로 생성된 후에 추가하거나 삭제, 수정이 가능하다. 물론 상수로 선언해 주면 변경이 불가능하다.

Note: 콜렉션을 변경할 필요가 없다면 상수로 선언해 주는 것이 좋다. Swift 컴파일러가 생성한 콜렉션의 성능을 최적화할 수 있다.

배열 (Arrays)

배열 (array) 는 순서대로 같은 타입의 값을 저장한다. 같은 값은 배열에 다른 순서로 있을 수 있다.

배열 타입 구문 (Array Type Shorthand Syntax)

배열의 타입은 Array<Element> 로 작성한다. 짧게는 [Element] 로 작성 가능하다. 보통은 짧게 표현하는 편이다.

빈 배열 생성 (Creating an Empty Array)

초기선언 구문을 사용하여 빈 배열을 생성할 수 있다.

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

someInts 의 경우 선언할 때 [Int] 로 타입을 지정해 주었다. 혹은 이미 타입이 있는 배열일 경우 빈배열을 만들지라도 타입이 남아있다. 아래의 예시를 보면 이해 가능하다.

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)

아래와 같이 배열을 생성해 줄 수도 있고, 그 배열들을 합칠 수 도 있다.

var threeDoubles = Array(repeating: 0.0, count: 3)
// threeDoubles is of type [Double], and equals [0.0, 0.0, 0.0]
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)

아래와 같이 String 값을 저장하는 shoppingList 배열을 생성 할 수 있다.

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

Swift 에서는 타입 추론이 가능하여 배열의 타입을 String 으로 명시하지 않아도 타입을 알아서 지정해 준다.

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

배열의 접근, 수정 (Accessing and Modifying an Array)

count 프로퍼티로 배열의 갯수를 알 수 있다.

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

isEmpty 프로퍼티로 배열의 count 값이 0 인지 아닌지 알 수 있다. 여기서는 2 이기 때문에 첫번째 문장이 출력된다.

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

append(_:) 메서드를 호출하여 배열의 끝에 새로운 값을 추가할 수 있다.

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

배열은 덧셈 대입 연산자 ( += ) 도 쓸 수 있다.

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

배열에 대괄호 [] 를 붙이고 인덱스를 넣어 해당 값을 가져올 수 있다. 여기서 배열 안의 첫 번째 값의 인덱스는 1 이아닌 0 이다.

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"
shoppingList[4...6] = ["Bananas", "Apples"]
// shoppingList now contains 6 items

insert(_:at:) 메서드를 사용하면 특정 인덱스에 값을 추가할 수 있다.

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

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

mapleSyrup 은 지워진 shoppingList 의 0 번째 값을 가져온다. (반환된 값이 필요 없으면 무시해도 된다) 배열의 마지막 값을 삭제하고 싶으면 배열의 count 프로퍼티의 사용을 피하기 위해 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() 메서드를 사용하게 되면 정수와 아이템을 조합하여 튜플로 반환한다. 정수는 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)

집합에 값을 저장하기 위해서는 hashable 여야 한다. 즉 hash value 인 고유한 식별값을 가지고 있어야 한다. a == b 라면 abhash value 는 같다. Swift 의 모든 기본 타입 ( String, Int, Double, Bool ) 은 기본적으로 hashable 이고 집합의 값 타입 또는 딕셔너리의 키 타입으로 사용 가능하다. 

Note : 알쏭달송한 hash value 의 세계... 좀더 공부를 해봐야 겠다. 지금 이해한 것은 hashable 이라는 것은 고유한 식별값을 가지고 있는 타입을 말하는 것이라는 것 정도 이해했다.

집합 타입 구문 (Set Type Syntax)

집합은 Set<Element> 로 작성된다. 집합은 짧은 형식이 없다. 아래와 같이 빈 집합을 생성 할 수 있다.

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."

한번 선언이 되었다면 다시 빈 집합을 만들더라도 집합 타입으로 남는다.

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)

아래와 같이 String 집합을 생성할 수 있다.

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

아래와 같이 Swift 에서는 집합을 생성할때 하나의 타입의 값이 포함되어 있다면 집합 요소의 타입을 정해주지 않아도 알아서 추론해준다.

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

집합 접근과 수정 (Accessing and Modifying a Set)

배열과 마찬가지로 count, isEmpty 프로퍼티가 사용 가능하다.

print("I have \(favoriteGenres.count) favorite music genres.")
// Prints "I have 3 favorite music genres."
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."

insert(_:), remove(_:), removeAll() 메서드를 사용하여 추가, 삭제가 가능하다.

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

remove(_:) 메서드를 사용할때 집합에 값이 없는 경우 nil을 반환하고, 값이 있다면 삭제된 값을 반환한다.

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."

contain(_:) 메서드를 사용하여 집합에 특정 값이 포함되어 있는지 알 수 있다.

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 루프도 사용 가능하다.

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

Swift 에서 Set 타입은 정해진 순서가 없기 때문에 특정 순서로 값을 반환하고 싶다면 집합의 요소를 < 연산자를 사용하여 정렬하여 반환하는 sorted() 메서드를 사용한다. C < H < J 이기 때문에 아래와 같이 반환된다.

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

집합 연산 수행 (Performing Set Operations)

두 집합 ab 의 여러가지 집합 연산이 아래 그림에 나와있다. 진한색이 결과값으로 반환된다고 생각하면 된다.

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 Mombership and Equality)

두 집합이 같은 값을 모두 포함하고 있는지 판단하려면 같음 연산자 ( == ) 를 사용한다. inSubset(of:) 메서드는 of 뒤의 집합이 포함되어 있는지, isSuperSet(of:) 는 of 뒤의 집합을 포함하고 있는지, 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 와 같은 타입의 값을 저장한다.

딕셔너리 타입 짧은 구문 (Dictionary Type Shorthand Syntax)

딕셔너리 타입은 Dictionary<Key, Value> 로 적을 수 있다. Key, Value 위치에 타입을 넣어야 한다. [Key: Value] 와 같이 짧은 형식으로도 작성이 가능하다. 아래와 같이 빈 딕셔너리 생성이 가능하다.

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

아래와 같이 [String: String] 타입의 딕셔너리도 생성이 가능하다.

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

배열과 마찬가지로 초기값이 있다면 타입을 굳이 명시하지 않더라도 아래와 같은 짧은 표현식으로도 작성이 가능하다.

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

또한 값을 불러오고 싶을 때 서브 스크립트 구문을 사용할 수 있다. airports["DUB"] 를 사용하게 되면 DUB 해당하는 값Dublin 을 가져오게 된다.

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

딕셔너리 접근과 수정 (Accessing and Modifying a Dictionary)

count, isEmpty 메서드가 사용 가능하다. 새로운 아이템을 추가할 때에는 아래와 같이 추가해주면 된다.

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

값을 변경하는 것도 아래와 같이 변경해 줄 수 있다.

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

또한 딕셔너리는 updateValue(_:forKey:) 메서드를 사용하여 특정 키값을 설정하거나 업데이트가 가능하다. 만약 해당 키에 값이 없다면 값을 넣어주게 되고, 해당 키에 값이 있었다면 업데이트되기 이전 값을 반환한다.

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."

해당 키에 nil 값을 넣어줘어 삭제도 가능하다.

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

removeValue(forKey:) 메서드를 사용하여 키-값 쌍을 삭제 할 수 있다. 배열과 마찬가지로 삭제가 되었다면 삭제된 값을 반환하고, 삭제할 것이 없다면 nil 을 반환한다.

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

딕셔너리 반복 (Iterating Over a Dictionary)

for-in 루프로 딕셔너리 키-값 쌍을 반복할 수 있다. 딕셔너리의 각 아이템은 (key, value) 튜플로 반환된다.

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

아래와 같이 in 뒤에 airports.keys, airports.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
let airportCodes = [String](airports.keys)
// airportCodes is ["LHR", "YYZ"]

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