3. Variables and Simple Type (6) - Built-In Simple Types (Range, Tuple)

Seoyoung Lee·2023년 1월 5일
0
post-thumbnail
post-custom-banner

Range

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:) 를 호출해야 한다. 위 코드에서 range1range2.relative(to:s) 는 동일하다. 그러나 범위 리터럴을 사용할 때 부분 범위 리터럴도 사용할 수 있기 때문에 일반적으로 사용하지 않는다. ⇒ 이 부분은 따로 더 공부해봐야겠다!

What Does The relative(to:) Function Actually Do?

Tuple

튜플은 여러 값을 담는 가벼운 컬렉션으로, 사용자가 순서를 정의할 수 있다.

튜플의 타입은 아래와 같은 형식으로 표현한다.

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 메소드로 만들어진 튜플도 offsetelement 라벨을 갖고 있다. 따라서 아래처럼 사용할 수도 있다.

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)
    // ...
}

튜플은 사실 완전한 타입이 아니기 때문에 과다하게 사용하지 않아야 한다. 튜플은 작고 가볍고 일시적이어야 한다.

💡 TIP

함수의 Void는 사실 빈 튜플의 타입 별칭이다. 그래서 () 로도 표현하는 것!

profile
나의 내일은 파래 🐳
post-custom-banner

0개의 댓글