[Swift] 스위프트 프로그래밍 - Part1 - 3 - 1 : 데이터 타입 고급 (Tuple, Array, Dictionary, Set)
야곰님의 스위프트 프로그래밍 책으로 공부한 내용을 잊어버리지 않게 간단하게 정리한 글입니다.
관련 포스트
[Swift] 스위프트 프로그래밍 - Part1 - 1 : 스위프트? / 스위프트 장점 / 특징 / 명명 규칙 / 콘솔 로그 / 주석
[Swift] 스위프트 프로그래밍 - Part1 - 2 : 변수 / 상수 / 데이터 타입 기본 (Int, Bool, Float, Character, String, Any, AnyObject, nil)
[Swift] 스위프트 프로그래밍 - Part1 - 3 - 1 : 데이터 타입 고급 (Tuple, Array, Dictionary, Set)
[Swift] 스위프트 프로그래밍 - Part1 - 3 - 2 : 데이터 타입 고급(열거형 enum)
책 범위 : Part 1. 스위프트 기초 (4. 데이터 타입 고급)
1. 데이터 타입 안심
스위프트의 특징 중 안전성(safe)이 가장 뚜렷하게 나타나는 부분.
스위프트는 데이터 타입을 안심하고 사용할 수 있는 Type safe 언어 -> 그만큼 실수를 줄일 수 있다는 의미
1-1. 타입 확인
스위프트가 컴파일 시 타입을 확인하는 것
var num: Int = "minha" // 타입 확인 -> 컴파일 오류
1-2. 타입 추론
변수나 상수를 선언할 때 특정 타입을 명시하지 않아도 컴파일러가 할당된 값을 기준으로 변수나 상수의 타입을 결정하는 것
var name = "minha" // 타입 추론에 의해 name은 String 타입으로 선언
name = 100 // String 변수에 정수할당 시 오류 발생
2. 타입 별칭
이미 존재하는 데이터 타입에 임의로 다른 이름(별칭)을 부여할 수 있는 것
기본 타입 이름과 이후에 추가한 별칭 모두 사용 가능
typealias MyInt = Int
typealias yourInt = Int
let age: MyInt = 26
var year: yourInt = 2020
var month: Int = 12
year = age //MyInt, yourInt 모두 Int로 같은 취급
print("현재는 \(year)년 \(month)월, 나이는 \(age)살 입니다.") //현재는 26년 12월, 나이는 26살 입니다.
3. 튜플 (Tuple)
- 타입의 이름이 따로 지정되어 있지 않은, 프로그래머 마음대로 만드는 타입
- 지정된 데이터의 묶음
- ex. C언어의 원시 구조체 형태와 비슷
- 인덱스를 통해 값 추출 및 할당이 가능
var person: (String, Int, Double) = ("minha", 100, 180.5)
// 인덱스를 통해 값 추출 가능
print("이름 : \(person.0), 나이 : \(person.1), 키 : \(person.2)")
// 인덱스를 통해 값 할당 가능
person.1 = 44
person.2 = 165.4
print("이름 : \(person.0), 나이 : \(person.1), 키 : \(person.2)")
출력
이름 : minha, 나이 : 100, 키 : 180.5
이름 : minha, 나이 : 44, 키 : 165.4
- 튜플의 요소마다 이름을 붙여줄 수도 있음
// 튜플의 각 요소에 이름 부여
var person2: (name: String, age: Int, height: Double) = ("minha", 99, 190.5)
// 요소 이름을 통해 값 추출 가능
print("이름 : \(person2.name), 나이 : \(person2.age), 키 : \(person2.height)")
// 요소 이름을 통해 값 할당 가능
person2.age = 88
person2.2 = 110.12
// 요소 이름을 통해 값 추출 가능
print("이름 : \(person2.0), 나이 : \(person2.1), 키 : \(person2.height)")
출력
이름 : minha, 나이 : 99, 키 : 190.5
이름 : minha, 나이 : 88, 키 : 110.12
- 타입 별칭을 사용해 매번 같고, 긴 튜플 타입을 모두 선언할 필요 없이 깔끔하고 안전하게 코드 작성 가능
// 튜플 별칭 지정
typealias PersonTuple = (name: String, age: Int, height: Double)
let minha: PersonTuple = ("minha", 100, 154.5)
let kwon: PersonTuple = ("kwon", 35, 175.3)
출력
이름 : minha, 나이 : 100, 키 : 154.5
이름 : kwon, 나이 : 35, 키 : 175.3
4. 컬렉션형
스위프트는 튜플 외에도 많은 수의 데이터를 묶어서 저장하고 관리할 수 있는 컬렉션 타입을 제공한다.
배열(Array), 딕셔너리(Dictionary), 세트(Set) 등이 있다.
5. 배열 (Array)
- 같은 타입의 데이터를 일렬로 나열한 후 순서대로 저장하는 형태의 컬렉션 타입
- 값 중복 가능
- let 키워드를 사용해 상수로 선언하면 변경할 수 없는 배열, var 키워드를 사용해 변수로 선언해주면 변경 가능한 배열
- Array 키워드와 타입 이름의 조합으로 사용
- 대괄호로 값을 묶어 배열 타입 표현 가능
var names: Array<String> = ["minha", "kwon", "okju", "bokchi"]
//[String]은 Array<String>의 축약 표현
let names2: [String] = ["minha", "kwon", "okju", "bokchi"]
- 빈 배열은 이니셜 라이저 또는 리터럴 문법을 통해 생성해 줄 수도 있음
var emptyArray: [Any] = [Any](); // Any 데이터를 요소로 갖는 빈 배열 생성
var emptyArray2: [Any] = Array<Any>(); // 위와 같은 코드
var emptyArray3: [Any] = [] // 배열의 타입을 정확히 명시했다면 []만으로도 빈 배열 생성 가능
- isEmpty 프로퍼티 : 빈 배열인지 확인
- count 프로퍼티 : 배열에 몇 개의 요소가 존재하는지 확인
print(emptyArray.isEmpty) // true
print(names.isEmpty) // false
print(names.count) // 4
-
각 요소에 인덱스를 통해 접근 가능
-
인덱스는 0부터 시작
-
잘못된 인덱스에 접근하면 익셉션 오류(Exception Error) 발생
- C언어의 배열처럼 한 번 선언하면 크기가 고정되는 버퍼가 아니라, 필요에 따라 자동으로 버퍼의 크기를 조절해 요소의 삽입 및 삭제가 자유로움
-
맨 처음과 맨 마지막 요소는 first와 last 프로퍼티를 통해 가져올 수 있음
-
firstIndex(of:) 메서드 : 해당 요소의 인덱스를 알아낼 수 있음 (중복 시 제일 먼저 발견된 요소의 인덱스 반환)
-
append(_:) 메서드 : 맨 뒤에 요소를 추가할 때 사용
-
insert(_:at:) 메서드 : 중간에 요소를 삽입할 때 사용
-
remove(_:) 메서드 : 요소를 삭제할 때 사용 (삭제된 후 반환됨)
- array[1 ... 3] : 범위 연산자를 사용해 array 배열의 일부만 가져온 것 / array[1 ... 3] = ["A", "B", "C"]와 같이 요소 변경도 가능
print(names[2])
names[2] = "juok"
print(names[2])
// print(names[4]) // 인덱스의 범위를 벗어나 익셉션 오류 발생
// names[4] = "elsa" // 오류
names.append("Elsa")
names.append(contentsOf: ["Anna", "Olaf"])
names.insert("Chris", at: 3)
names.insert(contentsOf: ["Sven", "Frozen"], at: 5)
print(names[0 ... names.count-1])
print(names.firstIndex(of: "minha"))
print(names.firstIndex(of: "mickey"))
print(names.first)
print(names.last)
let firstItem: String = names.removeFirst()
let lastItem: String = names.removeLast()
let indexZeroItem: String = names.remove(at: 0)
print(firstItem)
print(lastItem)
print(indexZeroItem)
print(names[1 ... 3])
출력
okju
juok
["minha", "kwon", "juok", "Chris", "bokchi", "Sven", "Frozen", "Elsa", "Anna", "Olaf"]
Optional(0)
nil
Optional("minha")
Optional("Olaf")
minha
Olaf
kwon
["Chris", "bokchi", "Sven"]
6. 딕셔너리 (Dictionary)
-
요소들이 순서 없이 키와 값의 쌍으로 구성되는 컬렉션 타입
-
키는 하나이거나 여러개일 수 있음
-
키는 같은 이름을 중복해서 사용할 수 없음 (값을 대변하는 유일한 식별자)
-
각 값에 키로 접근 가능
-
키는 유일 / 값은 유일하지 않음
-
배열과 달리 딕셔너리 내부에 없는 키로 접근해도 오류 발생 안 함 -> nil 반환
// typealias를 통해 단순하게 표현 가능
typealias StringIntDictionary = [String: Int]
// 키는 String, 값은 Int 타입인 빈 딕셔너리 생성
var numberForName: Dictionary<String, Int> = Dictionary<String, Int>()
// 위 선언과 같은 표현 / [String: Int] 는 Dictionary<String, Int>의 축약 표현
var numberForName2: [String: Int] = [String: Int]()
// 위 코드와 같은 동작
var numberForName3 : StringIntDictionary = StringIntDictionary()
- Dictionary 키워드와 키의 타입, 값의 타입 이름의 조합으로 사용 var dic: Dictionary<String, Int> = Dictionary<String, Int>()
- 대괄호로 키와 값의 타입 이름의 쌍을 묶어 딕셔너리 타입임을 표현 var dic: [String, Int] = [String: Int]()
// 딕셔너리의 키와 값 타입을 정확히 명시해줬다면 [:]만으로도 빈 딕셔너리 생성 가능
var numberForName4: [String: Int] = [:]
// 초기값을 주어 생성
var numberForName5: [String: Int] = ["minha": 100, "elsa": 200, "anna": 300]
- let 키워드를 사용해 상수로 선언하면 변경 불가능한 딕셔너리, var 키워드를 사용해 변수로 선언하면 변경 가능한 딕셔너리
- 빈 딕셔너리는 이니셜라이저 또는 리터럴 문법을 통해 생성 가능
- isEmpty 프로퍼티 : 빈 딕셔너리인지 확인
- count : 딕셔너리 요소의 개수 확인
- RemoveValue(forKey:) 메서드 : 특정 키에 해당하는 값 제거(제거 후 반환)
print(numberForName5["minha"])
print(numberForName5["olaf"])
numberForName5["minha"] = 400
print(numberForName5["minha"])
numberForName5["olaf"] = 999 // olaf라는 키로 999라는 값을 추가
print(numberForName5["olaf"])
print(numberForName5.removeValue(forKey: "anna"))
print(numberForName5.removeValue(forKey: "anna")) // 위에서 이미 anna에 해당하는 값이 삭제되었으므로 nil 반환
print(numberForName5["anna", default: 0]) // 키에 해당하는 값이 없으면 기본으로 0 반환
출력
Optional(100)
nil
Optional(400)
Optional(999)
Optional(300)
nil
0
7. 세트 (Set)
-
같은 타입의 데이터를 순서 없이 하나의 묶음으로 저장하는 형태의 컬렉션 타입
-
Set내의 같은 모두 유일한 값, 중복된 값 존재 X
-
순서가 중요하지 않거나 각 요소가 유일한 값이어야 하는 경우 사용
-
Set의 요소는 해시 가능한 값이 여야 한다. (Hashable 프로토콜을 다른다는 것의 의미. 기본 데이터 타입은 모두 해시 가능한 값)
var frozen: Set<String> = Set<String>() // 빈 Set 생성
var frozen2: Set<String> = [] // 빈 Set 생성
frozen = ["elas", "anna", "olaf", "chris", "sven"] // 배열과 마찬가지로 대괄호 사용
var numbers = [100, 200, 300] // 따라서 타입 추론을 사용할 경우, 컴파일러는 Set이 아닌 Array로 타입을 지정함
-
Set 키워드와 타입 이름의 조합으로 사용 var set: Set<String> = Set<String>()
-
대괄호로 값들을 묶어 Set 타입임을 표현 var set: Set<String> = []
-
배열과 달리 축약형이 없다. (Array<Int> 를 [Int]로 축약하는 것)
-
let 키워드를 사용해 상수로 선언하면 변경 불가능한 Set, var 키워드를 사용해 변수로 선언하면 변경 가능한 Set
-
빈 Set는 이니셜 라이저 또는 리터럴 문법을 통해 생성할 수 있음
- isEmpty 프로퍼티 : 빈 세트인지 혹인
- count 프로퍼티 : 세트의 요소 개수 확인
- insert(_:) 메서드 : 요소 추가
- remove(_:) 메서드 : 요소 삭제 (삭제된 후 반환)
print(frozen.isEmpty)
print(frozen.count)
frozen.insert("iduna")
print(frozen.remove("chris"))
print(frozen.remove("mickey"))
출력
false
5
Optional("chris")
nil
- 집합 관계를 표현하고자 할 때 유용하게 사용됨 (두 Set의 교집합, 합집합 연산 등)
- sorted() 메서드 : 정렬된 배열 반환
- 포함 관계를 연산할 수 있는 메서드로 구현되어 있다.
let frozenCharacters: Set<String> = ["elas", "anna", "olaf", "chirs", "sven"]
let disneyCharacters: Set<String> = ["elas", "anna", "olaf", "woody", "rapunzel", "buzz"]
// 교집합
let intersectSet: Set<String> = frozenCharacters.intersection(disneyCharacters)
print(intersectSet)
// 여집합의 합 (배타적 논리합)
let symmetricDiffSet: Set<String> = frozenCharacters.symmetricDifference(disneyCharacters)
print(symmetricDiffSet)
// 합집합
let unionSet: Set<String> = frozenCharacters.union(disneyCharacters)
print(unionSet)
// 차집합
let subtractSet: Set<String> = frozenCharacters.subtracting(disneyCharacters)
print(subtractSet)
// 합집합 정렬
print(unionSet.sorted())
let 새: Set<String> = ["까마귀", "닭", "학"]
let 포유류: Set<String> = ["사자", "곰", "호랑이"]
let 동물: Set<String> = 새.union(포유류)
print(새.isDisjoint(with: 포유류)) // 새로 배타적인지 - true
print(새.isSubset(of: 포유류)) // 새가 동물의 부분집한인지? - true
print(동물.isSuperset(of: 새))// 동물은 포유류의 전체집합인지? - true
print(동물.isSuperset(of: 포유류))// 동물의 새의 전체집합인지? - true
출력
["elas", "olaf", "anna"]
["buzz", "sven", "rapunzel", "chirs", "woody"]
["chirs", "sven", "olaf", "anna", "rapunzel", "elas", "buzz", "woody"]
["sven", "chirs"]
["anna", "buzz", "chirs", "elas", "olaf", "rapunzel", "sven", "woody"]
true
false
true
true
(번외) 컬렉션에서 임의의 요소 추출과 뒤섞기
스위프트 4.2 버전부터 컬렉션에서 임의의 요소를 추출하고 뒤섞응 메서드가 추가되었다.
-
randomElement() 메서드 : 컬렉션에서 임의의 요소 추출
-
shuffle() 메서드 : 컬렉션의 요소를 임의로 뒤섞음
-
shuffled() 메서드 : 자신의 요소는 그대로 둔 채 새로운 컬렉션에 임의의 순서로 섞어서 반환
var array: [Int] = [0, 1, 2, 3, 4]
var set: Set<Int> = [0, 1, 2, 3, 4]
var dictionary: [String: Int] = ["a": 1, "b": 2, "c": 3]
var string: String = "string"
print(array.randomElement()) // 임의의 요소
print(array.shuffled()) // 뒤죽박죽된 배열 - 기존 array 내부의 요소는 그대로 있음
print(array)
array.shuffle() // array 자체를 뒤죽박죽으로 뒤섞음
print(array)
print(set.shuffled()) // Set을 뒤섞으면 배열로 반환해줌
// set.shuffle() // Set은 순서가 없기 때문에 스스로 뒤섞을 수 없음
print(dictionary.shuffled()) // Dictionary를 섞으면 (키, 값)이 쌍을 이룬 튜플의 배열로 반환해줌
print(string.shuffled()) // String도 컬렉션임
Optional(0)
[3, 2, 0, 1, 4]
[0, 1, 2, 3, 4]
[3, 1, 4, 0, 2]
[4, 1, 0, 2, 3]
[(key: "b", value: 2), (key: "a", value: 1), (key: "c", value: 3)]
["g", "r", "n", "t", "i", "s"]
전체 코드
전체 예제 코드를 확인하고 싶다면 '더보기'를 눌러주세요.
print("4.2 타입 별칭 -----------------------\n")
typealias MyInt = Int
typealias yourInt = Int
let age: MyInt = 26
var year: yourInt = 2020
var month: Int = 12
year = age //MyInt, yourInt 모두 Int로 같은 취급
print("현재는 \(year)년 \(month)월, 나이는 \(age)살 입니다.")
print("\n4.3 튜플 -----------------------\n")
var person: (String, Int, Double) = ("minha", 100, 180.5)
// 인덱스를 통해 값 추출 가능
print("이름 : \(person.0), 나이 : \(person.1), 키 : \(person.2)")
// 인덱스를 통해 값 할당 가능
person.1 = 44
person.2 = 165.4
print("이름 : \(person.0), 나이 : \(person.1), 키 : \(person.2)")
// 튜플의 각 요소에 이름 부여
var person2: (name: String, age: Int, height: Double) = ("minha", 99, 190.5)
// 요소 이름을 통해 값 추출 가능
print("이름 : \(person2.name), 나이 : \(person2.age), 키 : \(person2.height)")
// 요소 이름을 통해 값 할당 가능
person2.age = 88
person2.2 = 110.12
// 요소 이름을 통해 값 추출 가능
print("이름 : \(person2.0), 나이 : \(person2.1), 키 : \(person2.height)")
// 튜플 별칭 지정
typealias PersonTuple = (name: String, age: Int, height: Double)
let minha: PersonTuple = ("minha", 100, 154.5)
let kwon: PersonTuple = ("kwon", 35, 175.3)
print("이름 : \(minha.0), 나이 : \(minha.1), 키 : \(minha.2)")
print("이름 : \(kwon.name), 나이 : \(kwon.age), 키 : \(kwon.height)")
print("\n4.5 배열 -----------------------\n")
var names: Array<String> = ["minha", "kwon", "okju", "bokchi"]
//[String]은 Array<String>의 축약 표현
let names2: [String] = ["minha", "kwon", "okju", "bokchi"]
var emptyArray: [Any] = [Any](); // Any 데이터를 요소로 갖는 빈 배열 생성
var emptyArray2: [Any] = Array<Any>(); // 위와 같은 코드
var emptyArray3: [Any] = [] // 배열의 타입을 정확히 명시했다면 []만으로도 빈 배열 생성 가능
print(emptyArray.isEmpty)
print(names.isEmpty)
print(names.count)
print("\n4.5 배열의 사용 -----------------------\n")
print(names[2])
names[2] = "juok"
print(names[2])
// print(names[4]) // 인덱스의 범위를 벗어나 익셉션 오류 발생
// names[4] = "elsa" // 오류
names.append("Elsa")
names.append(contentsOf: ["Anna", "Olaf"])
names.insert("Chris", at: 3)
names.insert(contentsOf: ["Sven", "Frozen"], at: 5)
print(names[0 ... names.count-1])
print(names.firstIndex(of: "minha"))
print(names.firstIndex(of: "mickey"))
print(names.first)
print(names.last)
let firstItem: String = names.removeFirst()
let lastItem: String = names.removeLast()
let indexZeroItem: String = names.remove(at: 0)
print(firstItem)
print(lastItem)
print(indexZeroItem)
print(names[1 ... 3])
print("\n4.6 딕셔너리 -----------------------\n")
// typealias를 통해 단순하게 표현 가능
typealias StringIntDictionary = [String: Int]
// 키는 String, 값은 Int 타입인 빈 딕셔너리 생성
var numberForName: Dictionary<String, Int> = Dictionary<String, Int>()
// 위 선언과 같은 표현 / [String: Int] 는 Dictionary<String, Int>의 축약 표현
var numberForName2: [String: Int] = [String: Int]()
// 위 코드와 같은 동작
var numberForName3 : StringIntDictionary = StringIntDictionary()
// 딕셔너리의 키와 값 타입을 정확히 명시해줬다면 [:]만으로도 빈 딕셔너리 생성 가능
var numberForName4: [String: Int] = [:]
// 초기값을 주어 생성
var numberForName5: [String: Int] = ["minha": 100, "elsa": 200, "anna": 300]
print(numberForName.isEmpty)
print(numberForName5.count)
print("\n4.6 딕셔너리의 사용 -----------------------\n")
print(numberForName5["minha"])
print(numberForName5["olaf"])
numberForName5["minha"] = 400
print(numberForName5["minha"])
numberForName5["olaf"] = 999 // olaf라는 키로 999라는 값을 추가
print(numberForName5["olaf"])
print(numberForName5.removeValue(forKey: "anna"))
print(numberForName5.removeValue(forKey: "anna")) // 위에서 이미 anna에 해당하는 값이 삭제되었으므로 nil 반환
print(numberForName5["anna", default: 0]) // 키에 해당하는 값이 없으면 기본으로 0 반환
print("\n4.7 세트 -----------------------\n")
var frozen: Set<String> = Set<String>() // 빈 Set 생성
var frozen2: Set<String> = [] // 빈 Set 생성
frozen = ["elas", "anna", "olaf", "chris", "sven"] // 배열과 마찬가지로 대괄호 사용
var numbers = [100, 200, 300] // 따라서 타입 추론을 사용할 경우, 컴파일러는 Set이 아닌 Array로 타입을 지정함
print(type(of: numbers)) // Array<Int>
print("\n4.7 세트의 사용 -----------------------\n")
print(frozen.isEmpty)
print(frozen.count)
frozen.insert("iduna")
print(frozen.remove("chris"))
print(frozen.remove("mickey"))
let frozenCharacters: Set<String> = ["elas", "anna", "olaf", "chirs", "sven"]
let disneyCharacters: Set<String> = ["elas", "anna", "olaf", "woody", "rapunzel", "buzz"]
// 교집합
let intersectSet: Set<String> = frozenCharacters.intersection(disneyCharacters)
print(intersectSet)
// 여집합의 합 (배타적 논리합)
let symmetricDiffSet: Set<String> = frozenCharacters.symmetricDifference(disneyCharacters)
print(symmetricDiffSet)
// 합집합
let unionSet: Set<String> = frozenCharacters.union(disneyCharacters)
print(unionSet)
// 차집합
let subtractSet: Set<String> = frozenCharacters.subtracting(disneyCharacters)
print(subtractSet)
// 합집합 정렬
print(unionSet.sorted())
let 새: Set<String> = ["까마귀", "닭", "학"]
let 포유류: Set<String> = ["사자", "곰", "호랑이"]
let 동물: Set<String> = 새.union(포유류)
print(새.isDisjoint(with: 포유류)) // 새로 배타적인지 - true
print(새.isSubset(of: 포유류)) // 새가 동물의 부분집한인지? - true
print(동물.isSuperset(of: 새))// 동물은 포유류의 전체집합인지? - true
print(동물.isSuperset(of: 포유류))// 동물의 새의 전체집합인지? - true
print("\n번외. 컬렉션에서 임의의 요소 추출과 뒤섞기 -----------------------\n")
var array: [Int] = [0, 1, 2, 3, 4]
var set: Set<Int> = [0, 1, 2, 3, 4]
var dictionary: [String: Int] = ["a": 1, "b": 2, "c": 3]
var string: String = "string"
print(array.randomElement()) // 임의의 요소
print(array.shuffled()) // 뒤죽박죽된 배열 - 기존 array 내부의 요소는 그대로 있음
print(array)
array.shuffle() // array 자체를 뒤죽박죽으로 뒤섞음
print(array)
print(set.shuffled()) // Set을 뒤섞으면 배열로 반환해줌
// set.shuffle() // Set은 순서가 없기 때문에 스스로 뒤섞을 수 없음
print(dictionary.shuffled()) // Dictionary를 섞으면 (키, 값)이 쌍을 이룬 튜플의 배열로 반환해줌
print(string.shuffled()) // String도 컬렉션임
참고
아래를 참고해 정리한 내용입니다.
스위프트 프로그래밍 3판(야곰) - 한빛미디어