Range 오브젝트 타입 (구조체)은 엔드포인트의 쌍을 나타낸다.
Range 리터럴을 형성하는 operator는 두 가지가 있다.
...
(closed range operator)a...b
는 a부터 b까지의 범위를 의미한다. (b 포함)..<
(half-open range operator)a..<b
는 a부터 b 이전까지의 범위를 의미한다. (b 포함 X)대부분의 Range 엔드포인트는 숫자이다.
let r = 1...3
끝 값이 음수인 경우 괄호로 묶거나 앞에 공백을 넣어줘야 한다.
let r = -1000 ... -1
Range의 시작 값은 끝 값보다 크면 안 된다. 큰 값에서 작은 값으로 반복하기 위해서 Range의 reversed()
메소드를 사용할 수 있다.
for ix in (1...3).reversed() {
print(ix) // 3, then 2, then 1
}
또 다른 활용법은 시퀀스로 인덱싱하는 것이다.
let s = "hello"
let arr = Array(s)
let result = arr[1...3]
let s2 = String(result) // "ell"
String은 문자열 시퀀스이기 때문에 Range를 사용해서 인덱싱을 할 수 있다. 단, String.Index의 Range여야 한다. String.Index 값을 조작해서 알맞은 타입의 Range를 생성하고, 서브스크립트를 사용해서 substring을 추출할 수 있다.
let s = "hello"
let ix1 = s.index(s.startIndex, offsetBy: 1)
let ix2 = s.index(ix1, offsetBy: 2)
let s2 = s[ix1...ix2] // "ell"
replaceSubrange(_:with:)
메소드는 범위로 분할해서 문자열을 수정한다.
var s = "hello"
let ix = s.startIndex
let r = s.index(ix, offsetBy:1)...s.index(ix, offsetBy: 3)
s.replaceSubrange(r, with: "ipp") // s == "hippo"
removeSubrange(_:)
메소드를 사용해서 문자열의 일부를 제거할 수도 있다.
Range 리터럴 엔드포인트 하나를 생략하는 것도 가능하다. 부분 범위를 나타내는 Range-like struct에는 세 가지 종류가 있다.
let range1 = s.startIndex..<s.endIndex
let range2 = ..<s.endIndex
let range3 = ...s.index(before: s.endIndex)
let range5 = s.startIndex...
위의 네 가지 방법으로 문자열 s
전체 범위를 표현할 수 있다.
부분 범위를 범위로 변환하기 위해서는 relative(to:)
를 호출해야 한다. 위 코드에서 range1
과 range2.relative(to:s)
는 동일하다. 그러나 범위 리터럴을 사용할 때 부분 범위 리터럴도 사용할 수 있기 때문에 일반적으로 사용하지 않는다. ⇒ 이 부분은 따로 더 공부해봐야겠다!
What Does The relative(to:) Function Actually Do?
튜플은 여러 값을 담는 가벼운 컬렉션으로, 사용자가 순서를 정의할 수 있다.
튜플의 타입은 아래와 같은 형식으로 표현한다.
var pair: (Int, String)
튜플은 순수 스위프트 언어의 기능이기 때문에 Cocoa 및 Objective-C와는 호환이 되지 않는다. 따라서 Cocoa와 관련이 없는 값에서만 사용해야 한다.
스위프트에서 튜플은 활용도가 높다. 예를 들어 한 가지 값만 리턴하는 함수에서 여러 값을 리턴 받고 싶을 때 튜플을 사용할 수 있다.
여러 변수들에 동시에 값을 할당하기 위해 튜플을 사용할 수도 있다.
let ix: Int
let s: String
(ix, s) = (1, "two")
let (ix, s) = (1, "Two") // 한 번에 변수 선언 및 값 할당 가능
할당된 값 중 하나를 무시하려면 _
를 사용한다.
let pair = (1, "Two")
let (_, s) = pair // s == "Two"
튜플을 사용하면 안전하게 두 변수의 값을 교환할 수 있다.
var s1 = "hello"
var s2 = "world"
(s1, s2) = (s2, s1) // s1 == "world", sw == "hello"
for...in
에서 enumerated
메소드를 사용하면 원소의 인덱스 번호와 원소 값에 쉽게 접근할 수 있다. 이때도 역시 튜플이 사용된다.
let s = "hello"
for (ix, c) in s.enumerated() {
print("character \(ix) is \(c)")
}
인덱스 번호를 통해 튜플의 값에 접근할 수 있다.
let pair = (1, "Two")
let ix = pair.0 // ix == 1
튜플이 var
로 선언되었다면 같은 방법으로 새로운 값을 할당할 수 있다.
var pair = (1, "Two")
pair.0 = 2 // pair == (2, "Two")
튜플의 값에 접근하는 또 다른 방법은 각 원소들에 라벨을 붙이는 것이다.
// 1
let pair: (first: Int, second: String) = (1, "Two")
// 2
let pair = (first: 1, second: "Two")
라벨은 튜플 값이 가지는 타입 중 하나이다. 라벨은 숫자 리터럴처럼 리터럴 메시지로 사용할 수 있다.
var pair = (first: 1, second: "Two")
let x = pair.first // 1
pair.first = 2
let y = pair.0 // 2
enumerated
메소드로 만들어진 튜플도 offset
과 element
라벨을 갖고 있다. 따라서 아래처럼 사용할 수도 있다.
let s = "hello"
for t in s.enumerated() {
print("character \(t.offset) is \(t.element)")
}
라벨이 없는 튜플에 라벨이 있는 튜플을 할당할 수도 있고, 반대로도 가능하다.
또한 함수에서도 라벨이 없는 튜플을 전달하거나 반환받을 수 있다.
func tupleMaker() -> (first: Int, second: String) {
return (1, "Two")
}
let ix = tupleMaker().first
특정한 타입을 가지는 튜플을 자주 사용하는 경우 typealias를 사용해서 타입 이름을 정해주면 좋다.
class Board {
typealias Point = (x: Int, y: Int)
// ...
}
튜플은 사실 완전한 타입이 아니기 때문에 과다하게 사용하지 않아야 한다. 튜플은 작고 가볍고 일시적이어야 한다.
함수의 Void는 사실 빈 튜플의 타입 별칭이다. 그래서 ()
로도 표현하는 것!