[Swift] Collection
Swift Collection
컬렉션
데이터를 효율적으로 관리하기 위한 자료형(타입)
- Array (배열)
- 데이터를 순서대로 저장하는 컬렉션
- 앱, 웹에서 데이터를 저장, 표시하는 형태
- Dictionary (딕셔너리)
- 데이터를 키와 값으로 하나의 쌍으로 만들어 관리하는 순서가 없는 컬렉션
- 데이터가 서버에 저장되어 있는 데이터를 받아오는 형태
- Set (집합)
- 수학에서 집합과 비슷한 연산을 제공하는 순서가 없는 컬렉션
스위프트 함수 이름 규칙
- Mutating : 컬렉션을 직접적으로 변경할 때는 동사 원형으로 사용
- (
sort()
,reverse()
,shuffle()
)
- (
- Non-Mutating : 컬렉션을 변경하지 않고, 리턴형으로 다른 컬렉션을 반환할 때는 분사 형태(-ing, -ed)로 사용
- (
sorted()
,reversed()
,shuffled()
)
- (
Array 배열
// 정식문법
var numsArray: Array<Int> = [1, 2, 3, 4, 5]
// 단축문법
let numsArray: [Int] = [1, 2, 3, 4, 5]
var numsArray = [1, 2, 3, 4, 5]
let stringArray = ["apple", "swift", "iOS"]
let data = [["a", "b"],["c", "d"],["e", "f"]]
- [] 대괄호로 묶는다. 배열의 인덱스의 시작은 0부터 (모든 프로그래밍 언어 공통 사항)
- 1개의 배열에는 동일한 타입의 데이터만 담을 수 있다.
- 순서가 있기 때문에)값은 중복 가능
빈 배열 생성
let emptyArray1: [Int] = []
let emptyArray2 = Array<Int>()
let emptyArray3 = [Int]()
배열의 기본 기능
numsArray.count // 배열의 원소 개수 Int값
numsArray.isEmpty // 배열이 비어있는지에 대한 Bool값
numsArray.contains(1) // 괄호 안의 원소를 가지고 있는지에 대한 Bool값
numsArray.randomElement() // 배열 안의 원소를 랜덤으로 하나 추출
numsArray.swapAt(0, 1) // 파라미터의 인덱스 원소 위치를 바꿈
배열 인덱싱
var stringArray = ["apple", "swift", "iOS"]
stringArray[0] // 대괄호 안의 인덱스 원소 값
stringArray[0...1] = [] // 대괄호 안의 인덱스 원소들 여러개를 삭제
stringArray.first // 배열의 첫번째 원소 (빈 배열일 경우가 있을 수 있으니 Optional 타입으로 리턴)
stringArray.last // 배열의 마지막 원소 (마찬가지로 Optional 타입으로 리턴)
// 빈 배열일 경우 nil 리턴
stringArray.startIndex // 배열의 시작 인덱스
stringArray.endIndex // 배열의 마지막 인덱스 + 1
stringArray[stringArray.startIndex] // 배열의 첫번째 원소
stringArray[stringArray.endIndex - 1] // 배열의 마지막 원소
stringArray.firstIndex(of: "iOS") // 앞에서 부터 찾았을때 "iOS"는 배열의 (앞에서부터) 몇번째
stringArray.lastIndex(of: "iOS") // 뒤에서 부터 찾았을때 "iOS"는 배열의 (앞에서부터) 몇번째
배열에서 원소 삽입 (insert), 교체 (replace), 추가(append), 삭제 (remove)
var alphabet = ["A", "B", "C", "D", "E", "F", "G"]
// 원소 하나 삽입
alphabet.insert("c", at: 2)
// 원소 여러개 삽입
alphabet.insert(contentsOf: ["c", "d"], at: 2)
// 원소 하나 교체
alphabet[0] = "c"
// 원소 여러개 교체
alphabet.replaceSubrange(0...2, with: ["a", "b", "c"])
alphabet[0...2] = ["a", "b", "c"]
// 서브 스크립트를 이용한 삽입하기는 없고, 교체하기만 있다.
// 원소 하나 추가
alphabet.append("H")
alphabet += ["H"]
// 원소 여러개 추가
alphabet.append(contentsOf: ["H", "I"])
alphabet.insert(contentsOf: ["H", "I"], at: alphabet.endIndex)
alphabet += ["H", "I"]
// 원소 한개 삭제
alphabet.remove(at: 2) // 삭제하고, 삭제된 원소 리턴
// 원소 여러개 삭제
alphabet.removeSubrange(0...2)
alphabet[0...2] = []
alphabet.removeFirst() // 맨 앞에 요소 삭제하고 삭제된 요소 리턴 (String으로 리턴)
alphabet.removeFirst(2) // 앞의 두개의 원소 삭제 (리턴은 안함)
alphabet.removeLast() // 맨 뒤에 원소 삭제하고 삭제된 요소 리턴 (String으로 리턴)
alphabet.removeLast(2) // 뒤의 두개의 원소 삭제 (리턴은 안함)
// 배열의 원소 모두 삭제
alphabet.removeAll()
alphabet.removeAll(keepingCapacity: true) // 메모리는 보존해 두고 데이터만 날림
배열의 정렬
var num = [1, 7, 3, 9, 4, 8, 2, 6, 5]
nums.sort() // 원소를 오름차순으로 정렬 후 저장
nums.sorted() // 원소를 오름차순으로 정렬 후 배열 리턴
nums.reverse() // 원소를 내림차순으로 정렬 후 저장
nums.reversed() // 원소를 내림차순으로 정렬 후 배열 리턴
nums.shuffle() // 원소의 순서를 랜덤으로 바꾸고 저장
nums.shuffled() // 원소의 순서를 랜덤으로 바꾸고 배열 리턴
배열의 비교
let a = ["a", "B", "c"]
let b = ["A", "b", "C"]
a == b // false
a != b // true
Dictionary 딕셔너리
각 원소를 키와 밸류의 쌍을 콜론으로 처리하여 대괄호로 묶은 형태
// 정식문법
let words1: Dictionary<Int, String> = [1: "Apple", 2:"Banana"]
// 단축문법
var words2: [String: String] = ["A":"Apple", "B":"Banana"]
- 키 값은 유일해야함. 밸류 값은 중복 가능
- 동일한 타입 쌍의 데이터만 담을 수 있음 ( ex) [Int:String], [String:String])
- 딕셔너리의 밸류에 딕셔너리 또는 배열을 사용해서 중첩 사용 가능 ( ex) [String: [String]])
- 딕셔너리의 키 값은 해셔블Hashable 해야 함
- 어떤 타입이 Hashable하다는 뜻은 해당 타입을 해시 함수의 input 값으로 사용 가능 하다는 뜻
- 해시 함수를 사용해 유일한 값으로 변환이 가능한 타입인지의 여부를 묻는 것
- 배열과는 달리 내부적으로 순서가 존재하지 않음
빈 딕셔너리 생성
let emptyDic1: Dictionary<Int, String> = [:]
let emptyDic2 = Dictionary<Int, String>()
let emptyDic3 = [Int: String]()
딕셔너리 기본 기능
dic.count // 딕셔너리의 원소 개수 Int값
dic.isEmpty // 딕셔너리가 비어있는지에 대한 Bool값
dic.randomElement() // 딕셔너리 안의 원소를 랜덤으로 하나 추출 Named Tuple 형태로 리턴 (옵셔널 타입)
dic.keys // 키의 묶음을 배열 형태로 리턴
dic.values // 밸류의 묶음을 배열 형태로 리턴
딕셔너리는 기본적으로 서브스크립트를 활용한 문법을 주로 사용
dic = ["A": "Apple", "B": "Banana"]
dic["A"] // Apple
print(dic["A"]) // Optional("Apple") (옵셔널 바인딩을 통해 벗겨줘야 함)
dic["A", default: "Empty"] // nil의 발생 확률을 없앰으로 옵셔널 바인딩을 해주지 않아도 됨
딕셔너리에서 원소 업데이트 (삽입, 교체, 추가)
word = [:]
word["A"] = "Apple" // "A"라는 키에 밸류를 "Apple"로 삽입
word["B"] = "Banana" // "B"라는 키에 밸류를 "Banana"로 추가
word["B"] = "Blue" // "B"라는 키의 밸류를 "Blue"로 교체
word.updateValue("City", forKey: "C") // "C"라는 키에 밸류를 리턴 (값이 없다면 nil) 후 밸류를 "City"로 교체
딕셔너리는 append()
를 제공하지 않음
append()
는 순서가 있는 컬렉션의 끝에 추가하는 개념이라서 순서가 없는 딕셔너리에서는update()
를 통해 추가
딕셔너리에서 원소 삭제
dic = ["A": "Apple", "B": "Banana", "C": "City"]
dic["B"] = nil // "B"라는 키와 밸류 삭제
dic["D"] = nil // 존재하지 않는 키와 밸류 삭제 -> 아무일도 일어나지 않음 (에러 아님)
dic.removeValue(forKey: "A") // "A"라는 키와 밸류 삭제후, 삭제된 밸류 리턴
dic.removeValue(forKey: "D") // 존재하지 않는 키와 밸류 삭제 -> nil리턴
딕셔너리의 비교
let dic1 = ["A": "Apple", "B": "Banana", "C": "City"]
let dic2 = ["A": "Apple", "C": "City", "B": "Banana"]
a == b // true
a != b // false
// 딕셔너리는 원래 순서가 없기 때문에 (Hashable하기 때문에) 순서 상관없이 비교 가능
Set 집합
대괄호로 묶는 형식. 배열과 구분이 안되기 때문에 반드시 생성시 타입 선언을 해야함
// 단축문법
var set: Set = [1, 1, 2, 3, 4, 4, 5]
// 정식문법
let set:Set<Int> = [1, 1, 2, 3, 4, 4, 5]
- 생성시 타입 선언을 해야함
- 원소 값을 중복으로 넣어도, 셋의 의미 상 원소 중복이 저장되지 않음
- 내부저긍로 값의 검색에 Hashing 알고리즘을 사용하므로 정렬 순서보다 검색 속도가 중요한 경우에 사용
- 합집합, 교집합, 차집합, 대칭차집합을 구할 때 주로 사용
- 값의 중복을 제거하기를 원하는 경우에 사용
빈 셋 생성
let emptySet: Set<Int> = []
let emptySet1 = Set<Int>()
셋 기본 기능
set.count // 셋의 원소 개수 Int값
set.isEmpty // 셋이 비어있는지에 대한 Bool값
set.contains(1) // 괄호 안의 원소를 가지고 있는지에 대한 Bool값
set.randomElement() // 셋 안의 원소를 랜덤으로 하나 추출 Named Tuple 형태로 리턴 (옵셔널 타입)
셋에서 원소 업데이트 (삽입, 교체, 추가)
set.update(with: 1)
// 업데이트가 되면 기존의 셋에 저장이 되고 기존의 셋에 있던 값이 리턴
// 기존의 셋에 없는 새로운 원소가 업데이트가 되면 nil을 리턴
- 셋은 배열과는 다르게 순서가 없기 때문에 서브스크립트 관련 문법이 없음
- 셋은 딕셔너리와 같은 이유로
append()
를 제공하지 않음
셋에서 원소 삭제
var stringSet: Set<String> = ["apple", "swift", "iOS"]
stringSet.remove("apple") // "apple" 삭제한 원소를 리턴
stringSet.remove("banana") // 없는 원소를 삭제하려 하면 nil 리턴 (에러는 발생하지 않음)
셋의 비교
var a: Set = [1, 1, 3, 3, 5, 5, 7, 7, 9]
var b: Set = [1, 3, 5, 7, 9]
var c: Set = [1, 7, 5, 9, 3]
a == b // 셋은 중복된 값은 저장되지 않기 때문에 true
b == c // 셋은 순서가 없기 때문에 true
셋의 집합 관계
a = [1, 2, 3, 4, 5, 6, 7, 8, 9] // 10미만 모든 정수 모음
b = [1, 3, 5, 7, 9] // 정렬된 홀수 모음
c = [2, 4, 6, 8, 10] // 정렬된 짝수 모음
d = [1, 7, 5, 9, 3] // 정렬되지 않은 홀수 모음
// 집합의 관계를 bool 값으로 리턴
// 부분집합 여부를 판단
b.isSubset(of: a) // true 부분집합 여부
b.isStrictSubset(of: a) // false 진부분집합 여부
// 상위집합
a.isSuperset(of: b) // true 상위집합 여부
a.isStrictSuperset(of: b) // false 진상위집합 여부
// 서로소 여부
d.isDisjoint(with: c)
// 합집합
var unionSet = b.union(c) // 셋 unionSet에 셋 b와 셋 c의 합집합을 저장
b.formUnion(c) // 셋 b와 셋 c의 합집합으로 셋 b를 변경 후 저장
// 교집합
var interSet = a.intersection(b)) // 셋 interSet에 셋 a와 셋 b의 교집합을 저장
a.formIntersection(b) // 셋 a와 셋 b의 교집합으로 셋 a를 변경 후 저장
// 차집합
var subSet = a.subtracting(b) // 셋 subSet에 셋 a와 셋 b의 차집합을 저장
a.subtract(b) // 셋 a와 셋 b의 차집합으로 셋 a를 변경 후 저장
//대칭차집합
var symmetricSet = a.symmetricDifference(b) // 셋 symmetricSet에 셋 a와 셋 b의 대칭차집합을 저장
a.formSymmetricDifference(b) // 셋 a와 셋 b의 대칭차집합으로 셋 a를 변경 후 저장
셋의 활용
var newSet: Set = [1, 2, 3, 4, 5]
var newArray: Array = newSet.sorted()
// 정렬은 순서가 필요하기 때문에 셋을 정렬하면 배열로 리턴
스위프트 컬렉션 심화 개념
Foundation Collection
구조체(Value Type)기반의 스위프트 컬렉션이외에 클래스(Object Type)기반의 Foundation 컬렉션이라는 개념도 있다. Object-C에서 넘어온 개념을 스위프트로 포팅하는 과정에서 Foundation 컬렉션을 사용해야 하는 경우도 있을 수 있다.
Swift Collection | Foundation Collection |
---|---|
구조체 / Value Type | 클래스 / Object Type |
let, var로 불변, 가변 결정 | 생성시에 불변, 가변 여부 결정 |
Array, Dictionary, Set | NSArray, NSDictionary, NSSet |
객체, 값 모두 저장 가능 | 객체 형식의 데이터만 저장 가능 |
하나의 컬렉션 안에 동일한 타입만 저장 가능 | 하나의 컬렉션 안에 동일한 타입만 저장할 필요 없음 |
KeyValuePairs
- 딕셔너리와 비슷한 형태지만, 순서가 있는 컬렉션
- key 값이 해셔블(hashable)일 필요 없음 -> 검색 알고리즘상 빠르지 않음
- key 값이 동일한 것도 가능
Copy-On-Write 최적화
- 코드상에서 값을 복사해서 담든다 하더라도, 실제 값이 바뀌기 전까지는 그냥 하나의 메모리 값을 공유해서 사용
- 메모리를 적게 차지하기 위해서 스위프트 언어가 알아서 내부에서 처리하는 메커니즘