Swift 中范圍和區間如何使用?


雖然現在swift語言已經發展到了2.0版了,但是相信很多學習iOS開發的童鞋仍對swift語言存在各種各樣的疑問,今天小編將為大家詳細介紹swift中的范圍和區間,下面我們一起來看看吧。

 

Ranges

在swift語言中,范圍是用 Range 類型表達的,一個范圍就是一個索引集合。

其中,值得注意的是Range在標准庫中使用很頻繁,特別是處在集合的上下文當中時。當我們查看 Range 定義時,范圍和集合之間的緊密關系一目了然:

struct Range<Element : ForwardIndexType> : CollectionType, Indexable, ... {

    ...

}

在一個范圍中的元素必需遵守 ForwardIndexType 協議,同時 CollecitonType 協議中的大量功能也是基於它實現的。有一個特殊的類型用來表示集合索引的范圍,對於獲取一個集合的子集是相當有意義的。例如,我們可以使用范圍獲取一個數組的部分:

let numbers = [1,2,3,4,5,6,7,8,9]

// 1..<5 等價於 Range(start: 1, end: 5)

numbers[1..<5] // [2,3,4,5]

正如類型定義中所看到的, Range 自身遵循 CollectionType 協議,所以幾乎所有數組可以做的事情,范圍也能夠適用。比如用 for 循環遍歷元素,或者使用 contains(_:) 檢查一個值是否在這個范圍內。

雖然范圍主要適用於與其他集合配合使用,但誰也無法阻止你創建一個用於表示數字區間的 Range<Int> 。畢竟 Int 已經實現了 ForwardIndexType 協議。現在回到模式匹配問題。

我們可以用一個范圍 (Int.min..<0).contains(x) 表示 x < 0 的情況,這是完全等價的,不過執行速度巨慢。畢竟默認需要遍歷整個集合,最糟糕的情況下,將執行 9,223,372,036,854,775,808次 ,這相當耗費資源。我們可以為 Comparable (比如 Int )類型的索引提供一個更好實現:

 

extension Range where Element : Comparable {

    func contains(element: Element) -> Bool {

        return element >= startIndex && element < endIndex

    }

}

 

(Int.min..<0).contains(-1) // true

(Int.min..<0).contains(0) // false

(Int.min..<0).contains(1) // false

這是一個非常好的練習,不過在我們案例中可有可無,因為 ~= 操作符為 Range 實現的匹配足夠高效(就像我們的 contains(_:) , Comparable 只是在索引中工作)。所以我們可以這樣的做:

Int.min..<0 ~= -1 // true

Int.min..<0 ~= 0 // false

Int.min..<0 ~= 1 // false

在這基礎上,可以寫一個 switch 語句,使用范圍查詢判斷一個數字是否大於,小於還是等於 0,對嗎?不幸地是,這並不適用。這段代碼會崩潰:

let x = 10

switch x {

case 1...Int.max: // EXC_BAD_INSTRUCTION

    print("positive")

case Int.min..<0:

    print("negative")

case 0:

    print("zero")

default:

    fatalError("Should be unreachable")

}

我們會在 case 1...Int.max 這一行中得到一個 EXC_BAD_INSTRUCTION 錯誤信息表明“fatal error: Range end index has no valid successor”。導致錯誤的原因在於: range 中的 endIndex 總是指向范圍中最后一個元素的后面。這對於半開區間(用 ..< 操作符創建)和閉合區間(用 … 操作符創建)都是一樣的,因為二者的內部實現是一樣的, a...b 事實上就是 a..<b.successor() 。

這里需要提醒大家的是,一個 Range<Int> 永遠都不能有 Int.max,這也意味着 Int.max 永遠都不會成為一個 Range<Int> 的成員,這同樣適用於其他有最大值的類型。這個限制使范圍不能滿足我們所要的需求。所以接下來讓我們來看看區間能不能滿足我們的要求。

 

區間

其實,在swift中,范圍和區間的是基本相同的概念構建的(一個連續元素的系列,有開始有結尾),但使用了不同的方法。范圍基於索引,因此可以是個集合,他們的大多數功能都是基本這個特性的。區間不是集合,他們的實現是依賴 Comparable 協議的。我們只可以為服從 Comparable 協議的類型創建區間類型:

protocol IntervalType {

    typealias Bound : Comparable

    ...

}

有別於范圍的定義,區間使用 IntervalType 協議呈現,這個協議有兩個具體的實現, HalfOpenInterval 和 ClosedInterval 。兩個范圍操作符也為區間提供了重載:..< 創建一個 HalfOpenInterval 和 … 創建一個 ClosedInterval 。由於默認是重載了 Range ,所以你必須明確變量為區間類型(IntervalType):

let int1: HalfOpenInterval = 1..<5

int1.contains(5) // false

let int2: ClosedInterval = 1...5

int2.contains(5) // true

而需要注意的是 ClosedInterval 不可以為空,x…x 總是會包含 x,而 x…(x-1) 會造成運行時錯誤。

然而閉合區間可以包含一個類型的最大值。這意味着我們現在可以寫我們的 switch 語句了。重復一遍,一定要明確類型,告訴編譯器我們想要的是區間而不是范圍:

 

let x = 10

switch x {

case 1...Int.max as ClosedInterval:

    print("positive")

case Int.min..<0 as HalfOpenInterval:

    print("negative")

case 0:

    print("zero")

default:

    fatalError("Should be unreachable")

}

 

為開區間定制操作符

如果想擺脫 Int.min 和 Int.max怎么辦?這個時候,可以為開區間和閉區間自定義前綴操作符和后綴操作符,用於表示所有小於一個上邊界的值,或者大於一個下邊界的值。這樣不僅在語法上要更友善;理想情況下,這些操作符不僅適用於 Int 類型,也可以適合於其它擁有最小和最大值的類型。實現看起來應該是這個樣子:

switch x {

case 1...: // an interval from 1 to Int.max (inclusive)

    print("positive")

case ..<0: // an interval from Int.min to 0 (exclusive)

    print("negative")

...

}

我們需要為 ..< 和 ... 分別定義前綴和后綴的實現方式 。下面這段代碼基本是基於 Nate Cook 寫的 gist片段 。

首先,我們必須聲明需要解釋的操作符:

prefix operator ..< { }

prefix operator ... { }

postfix operator ..< { }

postfix operator ... { }

 

緊接着,為 Int 實現第一個運算符的方法:

/// Forms a half-open interval from `Int.min` to `upperBound`

prefix func ..< (upperBound: Int) -> HalfOpenInterval<Int> {

    return Int.min..<upperBound

}

還可以讓它更通用。區間要求它的底層類型都遵循 Comparable 協議,所以使用相同的條件約束是一個很自然的選擇。但在這里我們會碰到一個問題:我們需要知道 T 類型的最小值來創建區間,但這並沒有一個通用的方法:

prefix func ..< <T : Comparable>(upperBound: T) -> HalfOpenInterval<T> {

    return T.min..<upperBound // error: type 'T' has no member 'min'

}

甚至是在標准庫中的其他協議都沒有為數字(就比如 IntegerType )提供這些–定義在數字類型中的 min 和 max 屬性。

這個時候,我們可以試試這個解決方案:定義一個 MinMaxType 的自定義協議,這個協議定義了 min 和 max 兩個屬性。因為所有整數類型都有這兩個屬性,讓他們遵守新的協議就不用額外寫代碼了:

 

/// Conforming types provide static `max` and `min` constants.

protocol MinMaxType {

    static var min: Self { get }

    static var max: Self { get }

}

 

// Extend relevant types

extension Int : MinMaxType {}

extension Int8 : MinMaxType {}

extension Int16 : MinMaxType {}

extension Int32 : MinMaxType {}

extension Int64 : MinMaxType {}

extension UInt : MinMaxType {}

extension UInt8 : MinMaxType {}

extension UInt16 : MinMaxType {}

extension UInt32 : MinMaxType {}

extension UInt64 : MinMaxType {}

這里有一個值得牢記的技巧。任何時候,當你有幾個不相關的類型,但它們具有相同類型的一個或多個方法、屬性,你都可以創建一個新的協議給他們提供一個通用接口。

告訴我們的通用類型 T 遵守 MinMaxType 協議以使這個實現可以正常運行:

 

/// Forms a half-open interval from `T.min` to `upperBound`

prefix func ..< <T : Comparable where T : MinMaxType>

    (upperBound: T) -> HalfOpenInterval<T> {

    return T.min..<upperBound

}

這里是其他三個操作符的實現:

 

/// Forms a closed interval from `T.min` to `upperBound`

prefix func ... <T : Comparable where T : MinMaxType>

    (upperBound: T) -> ClosedInterval<T> {

    return T.min...upperBound

}

 

/// Forms a half-open interval from `lowerBound` to `T.max`

postfix func ..< <T : Comparable where T : MinMaxType>

    (lowerBound: T) -> HalfOpenInterval<T> {

    return lowerBound..<T.max

}

 

/// Forms a closed interval from `lowerBound` to `T.max`

postfix func ... <T : Comparable where T : MinMaxType>

    (lowerBound: T) -> ClosedInterval<T> {

    return lowerBound...T.max

}

添加一些測試:

 

(..<0).contains(Int.min) // true

(..<0).contains(-1) // true

(..<0).contains(0) // false

 

(...0).contains(Int.min) // true

(...0).contains(0) // true

(...0).contains(1) // false

 

(0..<).contains(-1) // false

(0..<).contains(0) // true

(0..<).contains(Int.max) // false

(0..<).contains(Int.max - 1) // true

 

(0...).contains(-1) // false

(0...).contains(0) // true

(0...).contains(Int.max) // true

 

回到我們的 switch 語句,現在很好地工作了:

switch x {

case 1...:

    print("positive")

case ..<0:

    print("negative")

case 0:

    print("zero")

default:

    fatalError("Should be unreachable")

}

 

結束語

Swift 中范圍和區間都有相似的目的,但有着不同的實現和泛型約束。范圍基於索引並且經常用於集合上下文中。這意味着范圍不能包含一個類型最大值,這就不適合用在數字的區間上。區間兼容所有的 Comparable 類型,並且沒有最大值的限制。

如果要用swift語言開發iOS應用的話,區間和范圍這兩個概念還是需要理清楚,明白什么時候用范圍、什么時候用區間,提高開發效率,提升代碼質量,一步一步邁入iOS大神行列。

 

相關文章:《Linux系統中CPU使用率查詢常用的5個命令

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM