簡體   English   中英

如何在 Swift 中創建范圍?

[英]How to create range in Swift?

在 Objective-c 中,我們使用 NSRange 創建范圍

NSRange range;

那么如何在 Swift 中創建范圍呢?

為 Swift 4 更新

Swift 范圍比NSRange更復雜,而且它們在 Swift 3 中也沒有變得更容易。如果您想嘗試理解某些復雜性背后的原因,請閱讀thisthis 我只會向您展示如何創建它們以及何時可以使用它們。

封閉范圍: a...b

這個范圍運算符創建了一個包含元素a元素b的 Swift 范圍,即使b是某個類型的最大可能值(如Int.max )。 有兩種不同類型的封閉范圍: ClosedRangeCountableClosedRange

1. ClosedRange

Swift 中所有范圍的元素都是可比較的(即,它們符合 Comparable 協議)。 這允許您訪問集合中范圍內的元素。 下面是一個例子:

let myRange: ClosedRange = 1...3

let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]

但是, ClosedRange是不可數的(即,它不符合 Sequence 協議)。 這意味着您不能使用for循環遍歷元素。 為此,您需要CountableClosedRange

2. CountableClosedRange

這與上一個類似,除了現在范圍也可以迭代。

let myRange: CountableClosedRange = 1...3

let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]

for index in myRange {
    print(myArray[index])
}

半開區間: a..<b

此范圍運算符包括元素a包括元素b 像上面一樣,有兩種不同類型的半開范圍: RangeCountableRange

1. Range

ClosedRange ,您可以使用Range訪問集合的元素。 例子:

let myRange: Range = 1..<3

let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]

同樣,您不能迭代Range因為它只是可比較的,而不是可跨越的。

2. CountableRange

CountableRange允許迭代。

let myRange: CountableRange = 1..<3

let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]

for index in myRange {
    print(myArray[index])
}

范圍

您有時(必須)仍然可以在 Swift 中使用NSRange (例如,在制作屬性字符串時),因此知道如何制作它會很有幫助。

let myNSRange = NSRange(location: 3, length: 2)

請注意,這是 location 和length ,而不是開始索引和結束索引。 此處的示例與 Swift 范圍3..<5含義相似。 但是,由於類型不同,它們不能互換。

帶字符串的范圍

.....<范圍運算符是創建范圍的一種簡寫方式。 例如:

let myRange = 1..<3

創建相同范圍的長期方法是

let myRange = CountableRange<Int>(uncheckedBounds: (lower: 1, upper: 3)) // 1..<3

可以看到這里的索引類型是Int 但是,這不適用於String ,因為 Strings 由 Characters 組成,並且並非所有字符的大小都相同。 (閱讀本文了解更多信息。)例如,像 😀 這樣的表情符號比字母“b”占用更多的空間。

NSRange 的問題

嘗試使用帶有表情符號的NSRangeNSString進行試驗,您就會明白我的意思。 頭痛。

let myNSRange = NSRange(location: 1, length: 3)

let myNSString: NSString = "abcde"
myNSString.substring(with: myNSRange) // "bcd"

let myNSString2: NSString = "a😀cde"
myNSString2.substring(with: myNSRange) // "😀c"    Where is the "d"!?

笑臉需要兩個 UTF-16 代碼單元來存儲,所以它給出了不包含“d”的意外結果。

快速解決方案

因此,對於 Swift 字符串,您使用Range<String.Index> ,而不是Range<Int> 字符串索引是基於特定字符串計算的,因此它知道是否有任何表情符號或擴展字素簇。

例子

var myString = "abcde"
let start = myString.index(myString.startIndex, offsetBy: 1)
let end = myString.index(myString.startIndex, offsetBy: 4)
let myRange = start..<end
myString[myRange] // "bcd"

myString = "a😀cde"
let start2 = myString.index(myString.startIndex, offsetBy: 1)
let end2 = myString.index(myString.startIndex, offsetBy: 4)
let myRange2 = start2..<end2
myString[myRange2] // "😀cd"

單邊范圍: a......b..<b

在 Swift 4 中,事情被簡化了一點。 每當可以推斷出范圍的起點或終點時,您都​​可以將其省略。

整數

您可以使用單邊整數范圍來迭代集合。 以下是文檔中的一些示例。

// iterate from index 2 to the end of the array
for name in names[2...] {
    print(name)
}

// iterate from the beginning of the array to index 2
for name in names[...2] {
    print(name)
}

// iterate from the beginning of the array up to but not including index 2
for name in names[..<2] {
    print(name)
}

// the range from negative infinity to 5. You can't iterate forward
// over this because the starting point in unknown.
let range = ...5
range.contains(7)   // false
range.contains(4)   // true
range.contains(-1)  // true

// You can iterate over this but it will be an infinate loop 
// so you have to break out at some point.
let range = 5...

細繩

這也適用於字符串范圍。 如果您在一端使用str.startIndexstr.endIndex創建范圍, str.startIndex可以將其關閉。 編譯器會推斷出來。

給定的

var str = "Hello, playground"
let index = str.index(str.startIndex, offsetBy: 5)

let myRange = ..<index    // Hello

您可以使用...從索引轉到 str.endIndex

var str = "Hello, playground"
let index = str.index(str.endIndex, offsetBy: -10)
let myRange = index...        // playground

也可以看看:

筆記

  • 您不能在不同的字符串上使用通過一個字符串創建的范圍。
  • 如您所見,字符串范圍在 Swift 中很麻煩,但它們確實可以更好地處理表情符號和其他 Unicode 標量。

進一步研究

Xcode 8 測試版 2 • Swift 3

let myString = "Hello World"
let myRange = myString.startIndex..<myString.index(myString.startIndex, offsetBy: 5)
let mySubString = myString.substring(with: myRange)   // Hello

Xcode 7 • Swift 2.0

let myString = "Hello World"
let myRange = Range<String.Index>(start: myString.startIndex, end: myString.startIndex.advancedBy(5))

let mySubString = myString.substringWithRange(myRange)   // Hello

或者干脆

let myString = "Hello World"
let myRange = myString.startIndex..<myString.startIndex.advancedBy(5)
let mySubString = myString.substringWithRange(myRange)   // Hello

(1..<10)

返回...

范圍 = 1..<10

我感到驚訝的是,即使在 Swift 4 中,仍然沒有簡單的本地方式來使用 Int 來表達 String 范圍。 唯一允許您提供 Int 作為按范圍獲取子字符串的方法的 String 方法是prefixsuffix

手頭有一些轉換實用程序很有用,這樣我們就可以在與 String 交談時像 NSRange 一樣交談。 這是一個實用程序,它接受位置和長度,就像 NSRange 一樣,並返回Range<String.Index>

func range(_ start:Int, _ length:Int) -> Range<String.Index> {
    let i = self.index(start >= 0 ? self.startIndex : self.endIndex,
        offsetBy: start)
    let j = self.index(i, offsetBy: length)
    return i..<j
}

例如, "hello".range(0,1)"是包含"hello"的第一個字符的Range<String.Index> 。作為獎勵,我允許負數位置: "hello".range(-1,1)"是包含"hello"的最后一個字符的Range<String.Index>

Range<String.Index>轉換為 NSRange 也很有用,對於那些必須與 Cocoa 交談的時刻(例如,在處理 NSAttributedString 屬性范圍時)。 Swift 4 提供了一種本地方式來做到這一點:

let nsrange = NSRange(range, in:s) // where s is the string

因此,我們可以編寫另一個實用程序,直接從 String 位置和長度轉到 NSRange:

extension String {
    func nsRange(_ start:Int, _ length:Int) -> NSRange {
        return NSRange(self.range(start,length), in:self)
    }
}

像這樣使用

var start = str.startIndex // Start at the string's start index
var end = advance(str.startIndex, 5) // Take start index and advance 5 characters forward
var range: Range<String.Index> = Range<String.Index>(start: start,end: end)

let firstFiveDigit =  str.substringWithRange(range)

print(firstFiveDigit)

輸出:你好

func replace(input: String, start: Int,lenght: Int, newChar: Character) -> String {
    var chars = Array(input.characters)

    for i in start...lenght {
        guard i < input.characters.count else{
            break
        }
        chars[i] = newChar
    }
    return String(chars)
}

如果有人想創建 NSRange 對象可以創建為:

let range: NSRange = NSRange.init(location: 0, length: 5)

這將創建位置為 0 和長度為 5 的范圍

你可以這樣使用

let nsRange = NSRange(location: someInt, length: someInt)

let myNSString = bigTOTPCode as NSString //12345678
let firstDigit = myNSString.substringWithRange(NSRange(location: 0, length: 1)) //1
let secondDigit = myNSString.substringWithRange(NSRange(location: 1, length: 1)) //2
let thirdDigit = myNSString.substringWithRange(NSRange(location: 2, length: 4)) //3456

我創建了以下擴展:

extension String {
    func substring(from from:Int, to:Int) -> String? {
        if from<to && from>=0 && to<self.characters.count {
            let rng = self.startIndex.advancedBy(from)..<self.startIndex.advancedBy(to)
            return self.substringWithRange(rng)
        } else {
            return nil
        }
    }
}

使用示例:

print("abcde".substring(from: 1, to: 10)) //nil
print("abcde".substring(from: 2, to: 4))  //Optional("cd")
print("abcde".substring(from: 1, to: 0))  //nil
print("abcde".substring(from: 1, to: 1))  //nil
print("abcde".substring(from: -1, to: 1)) //nil

我想做這個:

print("Hello"[1...3])
// out: Error

但不幸的是,我不能自己寫下標,因為討厭的下標占用了名稱空間。

但是我們可以這樣做:

print("Hello"[range: 1...3])
// out: ell 

只需將其添加到您的項目中:

extension String {
    subscript(range: ClosedRange<Int>) -> String {
        get {
            let start = String.Index(utf16Offset: range.lowerBound, in: self)
            let end = String.Index(utf16Offset: range.upperBound, in: self)
            return String(self[start...end])
        }
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM