[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 CollectionFoundation Collection
구조체 / Value Type클래스 / Object Type
let, var로 불변, 가변 결정생성시에 불변, 가변 여부 결정
Array, Dictionary, SetNSArray, NSDictionary, NSSet
객체, 값 모두 저장 가능객체 형식의 데이터만 저장 가능
하나의 컬렉션 안에 동일한 타입만 저장 가능하나의 컬렉션 안에 동일한 타입만 저장할 필요 없음

KeyValuePairs

  • 딕셔너리와 비슷한 형태지만, 순서가 있는 컬렉션
  • key 값이 해셔블(hashable)일 필요 없음 -> 검색 알고리즘상 빠르지 않음
  • key 값이 동일한 것도 가능

Copy-On-Write 최적화

  • 코드상에서 값을 복사해서 담든다 하더라도, 실제 값이 바뀌기 전까지는 그냥 하나의 메모리 값을 공유해서 사용
  • 메모리를 적게 차지하기 위해서 스위프트 언어가 알아서 내부에서 처리하는 메커니즘

참고

Ellen Swift Class






© 2021. by hminkim