스위프트 컬렉션

데이터를 효율적으로 관리하기 위한 자료형 타입이다.

  • Array : 데이터를 순서대로 저장하는 컬렉션
  • Dictionary : key: value 하나의 쌍으로 관리 (X순서)
  • Set : X순서, X중복

Array 배열

파이썬에서의 리스트 와 많이 유사한 형태이다

// 정식문법
let strArray1: Array<Int> = [1,2,3,4,5]


// 단축문법
let strArray2: [String] = [1,2,3,4,5]
  • 배열의 문법 약속
  • [] 대괄호로 묶는다. 배열의 인덱스의 시작은 0부터 (모든 프로그래밍 언어 공통 사항)
  • 1개의 배열에는 동일한 타입의 데이터만 담을 수 있다.
  • (순서가 있기 때문에) 값은 중복 가능

배열의 기본기능

numsArray.count   //영어단어 그대로...
numsArray.isEmpty


numsArray.contains(1)  //파라미터로 값을 전달
numsArray.contains(8)


numsArray.randomElement()


numsArray.swapAt(0, 1)

배열의 각 요소(element)에 대한 접근

서브스크립트를 이용해서 각 요소에 효율적으로 접근할 수 있다. 이 점 또한 파이썬과 매우 유사한 형태

stringArray.first   // 리턴값 String?  ====> 빈 배열이라면 nil 리턴
stringArray.last


// 배열의 시작 인덱스
stringArray.startIndex

stringArray.endIndex
//stringArray.endIndex.advanced(by: -1)


stringArray[stringArray.startIndex]

stringArray[stringArray.endIndex - 1]

stringArray.firstIndex(of: "iOS")     // 앞에서 부터 찾았을때 "iOS"는 배열의 (앞에서부터) 몇번째

stringArray.lastIndex(of: "iOS")     // 뒤에서 부터 찾았을때 "iOS"는 배열의 (앞에서부터) 몇번째



if let index = stringArray.firstIndex(of: "iOS") {
    print(index)
}

스위프트를 배우면서 제일 많이 느끼는건, 에러처리에 굉장히 쫄아있는(?) 느낌이다.

파이썬에 비하면 정말 과할 정도로 많은 함수를 지원하는데, 공통적으로 모든함수에서, ‘너가 찾으려는 게 없을 수도 있어~‘를 대비하여 옵셔널 처리를 하여 결과값을 보여준다. 물론, 해당값을 생으로 쓰려면 옵셔널 바인딩과 같은 옵셔널을 벗기는 과정을 한번 더 거쳐야하지만, 에러가 나는 것 대신 nil이 나오는 건 나쁘지 않은 것 같다.

배열의 삽입, 교체, 추가 삭제 및 기타기능

이것도 타 언어들과 매우 비슷한 기능인 것 같아서 간단하게만.

var alphabet = ["A", "B", "C", "D", "E", "F", "G"]

alphabet.insert("c", at: 4)
alphabet.insert(contentsOf: ["a","b","c"], at: 0)
alphabet.insert(contentsOf: ["a","b","c"], at: alphabet.endIndex)


// 요소 교체하기
alphabet[0] = "a"
alphabet[0...2] = ["x", "y", "z"]
alphabet[0...1] = []

//교체하기 함수 문법
alphabet.replaceSubrange(0...2, with: ["a", "b", "c"])

//추가하기 
alphabet += ["H"]

alphabet.append("H")   // 맨 마지막에 추가하는 것
alphabet.append(contentsOf: ["H", "I"])
alphabet.append(7)   // 에러 ===> 동일한 자료형만 저장가능함

//삭제(제거하기)
alphabet[0...2] = []   //빈배열 전달하면 해당 범위가 삭제


// 요소 한개 삭제
alphabet.remove(at: 2)  // 삭제하고, 삭제된 요소 리턴
alphabet.remove(at: 8)    // 잘못된 인덱스 전달 ====> 에러발생


// 요소 범위 삭제
alphabet.removeSubrange(0...2)
alphabet.removeFirst()   // 맨 앞에 요소 삭제하고 삭제된 요소 리턴 (리턴형 String)
alphabet.removeFirst(2)   // 앞의 두개의 요소 삭제 ===> 리턴은 안함
alphabet.removeLast()   // 맨 뒤에 요소 삭제하고 삭제된 요소 리턴 (리턴형 String)
alphabet.removeLast(2)

//배열이 비어있는지도 잘 확인해보고 삭제(제거)해야함 =======> 에러
alphabet.removeFirst()    // 리턴형 String
alphabet.removeLast()     // 리턴형 String

// 배열의 요소 모두 삭제(제거)
alphabet.removeAll()
alphabet.removeAll(keepingCapacity: true)  // 저장공간을 일단은 보관해 둠. (안의 데이터만 일단 날림)

메모리와 관련된 배열함수도 있는게 신기하다.

  1. 배열을 직접정렬하는 메서드 sort (동사)
  2. 정렬된 새로운 배열을 리턴 sorted (동사ing/동사ed)
nums.sort()   // 배열을 직접 정렬 (오름차순) (배열리턴 하지 않음)
nums.sorted()    //오름차순으로 정렬


nums.reverse()   // 요소를 역순으로 정렬  (배열리턴 하지 않음)
nums.reversed()

nums.sorted().reversed()
// 새로운 배열은 생성하지 않고, 배열의 메모리는 공유하면서 역순으로 열거할 수 있는 형식을 리턴

nums.suffle()
nums.suffled()

배열끼리의 비교도 가능하다

let a = ["A", "B", "C"]
let b = ["a", "b", "c"]

// 두 배열을 비교해보기

a == b   // false
a != b   // true

// 개별요소 비교, 저장순서도 비교 하기 때문에

배열에서는 순서 가 가장 중요한 특징이기 때문에, 이 부분도 비교할 요소로 들어간다는 점 알아두어야 할 꺼 같다.

for i in nums {
    print(i)
}
//Python이랑 형태가 매우 동일..

//  enumerate 
// (offset: 0, element: 10)


for tuple in nums.enumerated() {
    //print(tuple)
    print("\(tuple.0) - \(tuple.1)")
}


for tuple in nums.enumerated() {      // 바로 뽑아내기
    print("\(tuple.offset) - \(tuple.element)")
}


for (index, value) in nums.enumerated().reversed() {      // 거꾸로 뒤에서 부터
    print("\(index) - \(value)")
}

파이썬도 enumerated 함수가 있는데, 특이한 건 튜플에 key/value가 offset, element라는 이름으로 붙여져서 리턴되고, 이를 직접 해당명명법으로 호출까지 되는 점이 신기한거 같다. (생각보다 세련된 언어라는 게 느껴지는 부분)


Dictionary 딕셔너리

  • 딕셔너리의 문법 약속
  • [] 대괄호로 묶는다. (쌍을 콜론으로 처리)
  • 키값은 유일해야함 / 중복 불가능(구분하는 요소이기 때문에) 밸류값은 중복 가능
  • 1개의 딕셔너리에는 동일한 자료형 쌍의 데이터만 담을 수 있음
  • 키값은 Hashble 해야함
// 단축문법
var words: [String: String] = [:]

// 정식문법
let words1: Dictionary<Int, String>


var dic = ["A": "Apple", "B": "Banana", "C": "City"]   // 딕셔너리 리터럴로 생성해서 저장
let dic1 = [1: "Apple", 2: "Banana", 3: "City"]    // 내부적으로 순서가 존재하지 않음

Hashable?

해시테이블 알고리즘은, 앞에서부터 탐색하는 완전탐색, 양쪽방향에서 찾는 이진탐색보다 훨씬 빠른 알고리즘이다.

도서관의 책들이 잘 정리되어 있으면 우리는 우리가 원하는 책을 쉽게 열람할 수 있다.(= 해시테이블) 하지만 오래된 책방 뒤죽박죽 되어 있는 책들 중에 우리가 원하는 책을 찾으려면 꽤 시간이 걸릴 것이다. (완전탐색/이진탐색)

근데 이게 천하무적일까? 모든 알고리즘에는 장점이 있다면 단점이 있는 법. Hash Collision 해시충돌이 이 알고리즘의 단점이라고 볼 수 있다.

  • Hash라는 알고리즘은 고유의 key값을 생성해서 단 하나의 자료를 찾는데 쓰인다고 하지만, 고유의 원칙은 참 어렵다고 한다. 즉, 무한히 생성될 수 있는 Key값에 유한한 Hash값을 매칭한다면, 분명 겹치는 hash가 생길 수 밖에 없다는게 이 알고리즘의 단점이다.

  • Hash의 저장 단계의 시간복잡도는 0(1)이다. key는 고유하며 해시함수의 결과로 나온 해시에 매칭되는 value를 찾으면 되기 때문이다. 하지만, 최악의 경우 0(n)이 될 수 있다. 해시 충돌로 인해 모든 bucket의 value들을 찾아봐야 하는 경우를 고려해야하기 때문.

빈 딕셔너리 생성

let emptyDic1: Dictionary<Int, String> = [:]
let emptyDic2 = Dictionary<Int, String>()
let emptyDic3 = [Int: String]()

var dictFromLiteral = [:]    // 타입 정보가 없으면 유추할 수가 없다.

여기서는 타입추론이 안되나봄…

딕셔너리의 여러가지 기능

dic = ["A": "Apple", "B": "Banana", "C": "City"]

dic.count
dic.isEmpty

dic.randomElement()      // Named Tuple 형태로 리턴
// optional(key: "C", value: "City")
//딕셔너리는 기본적으로 서브스크립트[ ]를 이용한 문법을 주로 사용
// 딕셔너리
dic = ["A": "Apple", "B": "Banana", "C": "City"]



dic["A"] + "김동찬"      // nil의 가능성 =====> String?(옵셔널)
//Value of optional type 'String?' must be unwrapped to a value of type 'String'



if let a = dic["A"] {    // 옵셔널 바인딩
    print(a)
} else {
    print("Not found")
}

dic["S", default: "Empty"]       // nil이 발생할 확률이 없음
// 자료가 없으면 기본값을 리턴하는 문법  ===> 리턴 String


dic.keys
dic.values

dic.keys.sorted()
dic.values.sorted()


for key in dic.keys.sorted() {     // 오름차순 정렬  ===> "return 배열" 배열이 됨
    print(key)
}

딕셔너리 업데이트(update) - 삽입하기/교체하기/추가하기

딕셔너리는 append 함수를 제공하지 않는다.

  • append는 순서가 있는 컬렉션의 끝에 추가하는 개념
  • 딕셔너리는 순서가 없기 때문에, update를 통해서 추가
words = [:]

words["A"] = "Apple"   // 애플로 다시 바꾸기

words["B"] = "Banana"  // 동일한 키가 없으면 ===> 추가하기
words["B"] = "Blue"    // 동일한 키가 있으면 ===> 기존 밸류 덮어쓰기

words.updateValue("City", forKey: "C") // ==> nil


// (정식 기능) 함수 문법 (update + insert = upsert)

words.updateValue("City", forKey: "C")   // 새로운 요소가 추가되면 ==> 리턴 nil

words = [:]      // 빈 딕셔너리로 만들기
words = ["A": "A"]   // 전체 교체하기(바꾸기)

딕셔너리 삭제하기

dic = ["A": "Apple", "B": "Banana", "C": "City"]


// 요소 삭제해 보기
dic["B"] = nil    // 해당요소 삭제
dic["E"] = nil   // 존재하지 않는 키/값을 삭제 ======> 아무일이 일어나지 않음(에러아님)
dic


// 함수로 삭제해보기
dic.removeValue(forKey: "A")   // 삭제후, 삭제된 값 리턴
dic.removeValue(forKey: "A")   // 다시 삭제해보기 ===> nil리턴


// 전체 삭제하기
dic.removeAll()
dic.removeAll(keepingCapacity: true) //여기도 있넨..

딕셔너리 기타등등

let a = ["A": "Apple", "B": "Banana", "C": "City"]
let b = ["A": "Apple", "C": "City", "B": "Banana"]


// 비교 연산자
a == b   // true
// 딕셔너리는 원래 순서가 없기 때문에, 순서는 상관없음
// (순서 상관없이 무조건 true나옴 - Hashable하기 때문에, 순서 상관없이 비교가능)

a != b   // false
// 딕셔너리의 중첩 사용

var dict1 = [String: [String]]()


dict1["arr1"] = ["A", "B", "C"]
dict1["arr2"] = ["D", "E", "F"]

//["arr1": ["A", "B", "C"], "arr2": ["D", "E", "F"]]

dict1["arr"] = "A"  // ======> Cannot assign value of type 'String' to subscript of type '[String]'



var dict2 = [String: [String: Int]]()

dict2["dic1"] = ["name": 1, "age": 2]
dict2["dic2"] = ["name": 2, "age": 4]
// ["dic2": ["age": 4, "name": 2], "dic1": ["age": 2, "name": 1]]

얘도 마찬가지로 타입이 정해졌으면 섞이지 말고, 하나로 통일해야 한다.

let dict = ["A": "Apple", "B": "Banana", "C": "City"]

for (key, value) in dict {
    print("\(key): \(value)")
}


for (key, _) in dict {
    print("Key :", key)
}


for (_, value) in dict {
    print("Value :", value)
}

딕셔너리는 열거하지 않아도, Named 튜플 형태로 하나식 꺼내서 전달, 순서가 없으므로, 실행마다 순서가 달라짐!!!

  • 데이터 바구니이기 때문에, 차례대로 하나씩 꺼내서 사용하는 경우가 많을 수 있어서 활용 가능

Set 세트

수학에서의 집합과 비슷한 연산을 제공하는 컬렉션

  • Set의 문법 약속
  • 생김새는 배열과 같음(따라서, 생성시 타입을 선언 해야함)
  • 수학에서의 집합과 동일하기 때문에 요소는 유일해야함(순서가 존재하지 않음)

Set의 값과 Dictionary의 키값은 Hashable

  1. 정렬순서보다 검색속도가 중요한 경우에 사용
  2. 검색에 내부적으로 Hashing 알고리즘 사용
    (hashing ===> 특정값을 고정된 길이의 값으로 변환하는 기법으로 인덱싱과 암호화에서 자주 사용됨)
  3. 집합의 수학적인 개념(합집합/교집합/차집합/대칭차집합)을 이용할 필요가 있을 때
    (집합을 계산하기 간편한 함수를 내장)
// 단축문법
let set1: Set = [1, 2, 3]

// 정식문법
let set2: Set<Int> = [1, 2, 3]

// 빈 세트 생성
let emptySet: Set<Int> = []
let emptySet1 = Set<Int>()

There is no append func.

Set는 append 함수를 제공하지 않음 => append는 순서가 있는 컬렉션의 끝에 추가하는 개념

  • Set은 순서가 없기 때문에, update를 통해서 추가
  • 당연하게도 서브스크립트 관련 문법 없음!!
set.update(with: 1)     // Int?
set.update(with: 7)     // 새로운 요소가 추가되면 ====> 리턴 nil




var stringSet: Set<String> = ["Apple", "Banana", "City", "Swift"]


// 요소 삭제해 보기
stringSet.remove("Swift")     // "Swift" 삭제한 요소를 리턴
stringSet                     // ["Hello", "Apple"]

// 존재하지 않는 요소를 삭제해보기
stringSet.remove("Steve")       // nil    (에러는 발생하지 않음)

// 전체요소 삭제
stringSet.removeAll()
stringSet.removeAll(keepingCapacity: true)

여기 또 있네 메모리;;

어찌보면 set를 사용하는 근본적인 이유

부분집합 / 상위집합 / 서로소

a = [1, 2, 3, 4, 5, 6, 7, 8, 9]
b = [1, 3, 5, 7, 9]     // 홀수 모음
c = [2, 4, 6, 8, 10]    // 짝수 모음
d = [1, 7, 5, 9, 3]     // 홀수 모음

// 부분집합 여부를 판단
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)
b.formUnion(c)      // 원본변경

  • 교집합
var interSet = a.intersection(b)
a.formIntersection(b)      // 원본변경

  • 차집합
var subSet = a.subtracting(b)
a.subtract(b)       // 원본변경

  • 대칭차집합
var symmetricSet = a.symmetricDifference(b)
a.formSymmetricDifference(b)       // 원본변경

특이한 세트의 특징(?)

Set을 정렬하면, 배열로 리턴함 (정렬은 순서가 필요하기 때문)

var newSet: Set = [1, 2, 3, 4, 5]
var newArray: Array = newSet.sorted() //array 타입으로 반환됨

KeyValuPairs

넌 뭐냐
.
.
.

딕셔너리와 유사한 형태이지만, 배열처럼 순서가 있는 컬렉션..?

  • 스위프트 5.2 버전에 등장
  • 딕셔너리와 비슷한 형태지만, “순서”가 있는 컬렉션
  • key값이 해셔블(hashable)일 필요없음 (검색 알고리즘상 빠르지 않음)
  • key값이 동일한 것도 가능

약간 필요할때만 써라~ 이런 느낌이다. 임시적인 타입 같아보임.

  • 배열처럼, 인덱스로 접근 가능
  • 요소에서는 튜플방식으로 접근
  • append / remove 같은 기능이 없음
let introduce: KeyValuePairs = ["first": "Hello", "second": "My Name", "third":"is"]

introduce.count
introduce.isEmpty

introduce[0]

print("\(introduce[0].key)\(introduce[0].value) 입니다.")
print("\(introduce[1].key)\(introduce[1].value) 입니다.")
print("\(introduce[2].key)\(introduce[2].value) 입니다.")

딕셔너리이지만, 저장된 순서가 중요할 경우, 또는 데이터가 반복될 경우만 임시적/제한적으로 사용

Copy-On-Write 최적화

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

var array = [1,2,3,4,5,6,7]

var subArray = array[0...2]
// 여기서 메모리는 따로 저장되지 않고, Array의 일부 메모리를 참조하는 방식으로 메모리를 사용한다 (추가적인 메모리X)