繁体   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