Câu trả lời:
Đã cập nhật cho Swift 4
Phạm vi Swift phức tạp hơn NSRange
và chúng không dễ dàng hơn trong Swift 3. Nếu bạn muốn cố gắng hiểu lý do đằng sau một số phức tạp này, hãy đọc phần này và phần này . Tôi sẽ chỉ cho bạn cách tạo chúng và khi nào bạn có thể sử dụng chúng.
a...b
Toán tử phạm vi này tạo ra một phạm vi Swift bao gồm cả phần tử a
và phần tử b
, ngay cả khi b
là giá trị lớn nhất có thể cho một kiểu (như Int.max
). Có hai loại phạm vi đóng khác nhau: ClosedRange
và CountableClosedRange
.
ClosedRange
Các phần tử của tất cả các phạm vi trong Swift đều có thể so sánh được (tức là chúng tuân theo giao thức có thể so sánh được). Điều đó cho phép bạn truy cập các phần tử trong phạm vi từ một tập hợp. Đây là một ví dụ:
let myRange: ClosedRange = 1...3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]
Tuy nhiên, a ClosedRange
không thể đếm được (tức là nó không tuân theo giao thức Trình tự). Điều đó có nghĩa là bạn không thể lặp lại các phần tử bằng một for
vòng lặp. Đối với điều đó bạn cần CountableClosedRange
.
CountableClosedRange
Điều này tương tự như lần cuối cùng ngoại trừ bây giờ phạm vi cũng có thể được lặp lại.
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
Toán tử phạm vi này bao gồm phần tử a
nhưng không bao gồm phần tử b
. Giống như trên, có hai loại phạm vi bán mở khác nhau: Range
và CountableRange
.
Range
Tương tự như vậy ClosedRange
, bạn có thể truy cập các phần tử của một tập hợp bằng Range
. Thí dụ:
let myRange: Range = 1..<3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]
Tuy nhiên, một lần nữa, bạn không thể lặp lại a Range
bởi vì nó chỉ có thể so sánh được, không thể sắp xếp được.
CountableRange
A CountableRange
cho phép lặp lại.
let myRange: CountableRange = 1..<3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]
for index in myRange {
print(myArray[index])
}
NSRange
Đôi khi bạn vẫn có thể (phải) sử dụng Swift ( ví dụ: khi tạo các chuỗi phân bổ ), vì vậy sẽ rất hữu ích nếu bạn biết cách tạo một chuỗi .
let myNSRange = NSRange(location: 3, length: 2)
Lưu ý rằng đây là vị trí và độ dài , không phải chỉ mục bắt đầu và chỉ mục kết thúc. Ví dụ ở đây có ý nghĩa tương tự với phạm vi Swift 3..<5
. Tuy nhiên, vì các loại khác nhau, chúng không thể thay thế cho nhau.
Các ...
và ..<
khai thác phạm vi là một cách viết tắt của việc tạo ra dãy. Ví dụ:
let myRange = 1..<3
Cách dài để tạo ra cùng một phạm vi sẽ là
let myRange = CountableRange<Int>(uncheckedBounds: (lower: 1, upper: 3)) // 1..<3
Bạn có thể thấy rằng loại chỉ mục ở đây là Int
. Tuy nhiên, điều đó không hiệu quả String
vì Chuỗi được tạo từ các Ký tự và không phải tất cả các ký tự đều có cùng kích thước. (Đọc phần này để biết thêm thông tin.) Ví dụ: một biểu tượng cảm xúc như 😀, chiếm nhiều không gian hơn chữ "b".
Sự cố với NSRange
Hãy thử thử NSRange
và một NSString
biểu tượng cảm xúc và bạn sẽ thấy ý tôi. Đau đầu.
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"!?
Mặt cười sử dụng hai đơn vị mã UTF-16 để lưu trữ, vì vậy nó cho kết quả không mong muốn là không bao gồm chữ "d".
Giải pháp Swift
Bởi vì điều này, với Swift Strings bạn sử dụng Range<String.Index>
, không Range<Int>
. Chỉ số chuỗi được tính toán dựa trên một chuỗi cụ thể để nó biết liệu có bất kỳ biểu tượng cảm xúc hoặc cụm grapheme mở rộng nào hay không.
Thí dụ
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...
và ...b
và..<b
Trong Swift 4 mọi thứ đã được đơn giản hóa một chút. Bất cứ khi nào điểm bắt đầu hoặc điểm kết thúc của một phạm vi có thể được suy ra, bạn có thể bỏ qua.
Int
Bạn có thể sử dụng phạm vi số nguyên một phía để lặp qua các tập hợp. Dưới đây là một số ví dụ từ tài liệu .
// 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...
Chuỗi
Điều này cũng hoạt động với phạm vi Chuỗi. Nếu bạn đang tạo một phạm vi bằng str.startIndex
hoặc str.endIndex
ở một đầu, bạn có thể bỏ qua. Trình biên dịch sẽ suy ra nó.
Được
var str = "Hello, playground"
let index = str.index(str.startIndex, offsetBy: 5)
let myRange = ..<index // Hello
Bạn có thể chuyển từ chỉ mục đến str.endIndex bằng cách sử dụng ...
var str = "Hello, playground"
let index = str.index(str.endIndex, offsetBy: -10)
let myRange = index... // playground
Ghi chú
NSString
lưu trữ nội bộ các ký tự của nó ở dạng mã hóa UTF-16. Một vô hướng unicode đầy đủ là 21-bit. Ký tự khuôn mặt cười ( U+1F600
) không thể được lưu trữ trong một đơn vị mã 16 bit duy nhất, vì vậy nó trải rộng trên 2. NSRange
số lượng dựa trên đơn vị mã 16 bit. Trong ví dụ này, 3 đơn vị mã xảy ra chỉ đại diện cho 2 ký tự
Xcode 8 beta 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
hoặc đơn giản
let myString = "Hello World"
let myRange = myString.startIndex..<myString.startIndex.advancedBy(5)
let mySubString = myString.substringWithRange(myRange) // Hello
Sử dụng như thế này
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)
Đầu ra: Xin chào
Tôi thấy ngạc nhiên rằng, ngay cả trong Swift 4, vẫn không có cách đơn giản nào để thể hiện một dải Chuỗi bằng Int. Các phương thức Chuỗi duy nhất cho phép bạn cung cấp Int như một cách để lấy chuỗi con theo phạm vi là prefix
và suffix
.
Sẽ rất hữu ích nếu bạn có trong tay một số tiện ích chuyển đổi để chúng ta có thể nói chuyện như NSRange khi nói chuyện với một Chuỗi. Đây là một tiện ích lấy vị trí và độ dài, giống như NSRange và trả về 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
}
Ví dụ, "hello".range(0,1)"
là Range<String.Index>
ôm ký tự đầu tiên của "hello"
. Như một phần thưởng, tôi đã cho phép các vị trí phủ định: "hello".range(-1,1)"
là Range<String.Index>
ký tự cuối cùng bao trùm "hello"
.
Nó cũng hữu ích khi chuyển đổi a Range<String.Index>
thành NSRange, đối với những thời điểm bạn phải nói chuyện với Cocoa (ví dụ: trong việc xử lý các phạm vi thuộc tính NSAttributedString). Swift 4 cung cấp một cách gốc để làm điều đó:
let nsrange = NSRange(range, in:s) // where s is the string
Do đó, chúng tôi có thể viết một tiện ích khác, nơi chúng tôi đi trực tiếp từ vị trí và độ dài chuỗi đến một NSRange:
extension String {
func nsRange(_ start:Int, _ length:Int) -> NSRange {
return NSRange(self.range(start,length), in:self)
}
}
Tôi đã tạo tiện ích mở rộng sau:
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
}
}
}
ví dụ sử dụng:
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
Bạn có thể sử dụng như thế này
let nsRange = NSRange(location: someInt, length: someInt)
như trong
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
Tôi muốn làm điều này:
print("Hello"[1...3])
// out: Error
Nhưng thật không may, tôi không thể viết một chỉ số con của riêng mình vì chỉ số bị ghét chiếm không gian tên.
Tuy nhiên, chúng tôi có thể làm điều này:
print("Hello"[range: 1...3])
// out: ell
Chỉ cần thêm cái này vào dự án của bạn:
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])
}
}
}
Range<String.Index>
, nhưng đôi khi cần phải làm việc vớiNSString
vàNSRange
, vì vậy một số ngữ cảnh khác sẽ hữu ích. - Nhưng hãy xem stackoverflow.com/questions/24092884/… .