Відповіді:
Оновлено для Swift 4
Діапазони Swift складніші за них NSRange
, і вони не стали легшими у Swift 3. Якщо ви хочете спробувати зрозуміти міркування, що стоять за деякою цією складністю, прочитайте це та це . Я просто покажу вам, як їх створити і коли ви можете їх використовувати.
a...b
Цей оператор діапазону створює діапазон Swift, який включає як елемент, так a
і елемент b
, навіть якщо b
це максимально можливе значення для типу (наприклад Int.max
). Існує два різних типи закритих діапазонів: ClosedRange
і CountableClosedRange
.
ClosedRange
Елементи всіх діапазонів у Swift порівнянні (тобто вони відповідають Порівнюваному протоколу). Це дозволяє отримати доступ до елементів у асортименті з колекції. Ось приклад:
let myRange: ClosedRange = 1...3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]
Однак a ClosedRange
не враховується (тобто не відповідає протоколу послідовності). Це означає, що ви не можете перебрати елементи з аfor
циклу. Для цього вам потрібна CountableClosedRange
.
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
. Як і вище, існує два різних типи напіввідкритих діапазонів: Range
і CountableRange
.
Range
Як і у випадку ClosedRange
, ви можете отримати доступ до елементів колекції за допомогою Range
. Приклад:
let myRange: Range = 1..<3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]
Знову ж таки, ви не можете повторити над Range
оскільки воно лише порівнянне, а не строгі.
CountableRange
A CountableRange
дозволяє ітерацію.
let myRange: CountableRange = 1..<3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]
for index in myRange {
print(myArray[index])
}
Ви все ще можете (обов'язково) користуватися NSRange
часом у Swift (наприклад, при створенні атрибутивних рядків ), тому корисно знати, як його зробити.
let myNSRange = NSRange(location: 3, length: 2)
Зауважте, що це розташування та довжина , а не індекс початку та індекс кінця. Приклад тут подібний за значенням до діапазону Swift 3..<5
. Однак, оскільки типи різні, вони не взаємозамінні.
...
І ..<
оператори дальності є скорочений спосіб створення діапазонів. Наприклад:
let myRange = 1..<3
Довгий спосіб створити такий же діапазон
let myRange = CountableRange<Int>(uncheckedBounds: (lower: 1, upper: 3)) // 1..<3
Ви можете бачити, що тут є тип індексу Int
. Однак це не працює String
, оскільки рядки створені з символів, а не всі символи мають однаковий розмір. (Прочитайте це для отримання додаткової інформації.) Емодзі, як 😀, наприклад, займає більше місця, ніж літера "b".
Проблема з NSRange
Спробуйте поекспериментувати NSRange
і NSString
з емоджи, і ви побачите, що я маю на увазі. Головний біль.
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 Strings 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 речі були трохи спрощені. Щоразу, коли можна зробити початкову або кінцеву точку діапазону, ви можете залишити його.
Int
Ви можете використовувати однобічні цілі діапазони для перебору колекцій. Ось кілька прикладів із документації .
// 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.startIndex
або str.endIndex
в кінці, ви можете залишити його. Компілятор зробить це висновок.
Дано
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
Примітки
NSString
внутрішньо зберігає своїх символів у кодуванні UTF-16. Повний скаляр однокодового типу - 21-розрядний. Символ обличчя, що посміхнувся ( U+1F600
), не може зберігатися в одному 16-бітовому блоці коду, тому він поширюється на 2. NSRange
рахується на основі 16-бітних одиниць коду. У цьому прикладі 3 кодові одиниці представляють лише 2 символи
Xcode 8 beta 2 • Швидкий 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
Використовуйте так
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)
Вихід: Привіт
Мені здається дивним, що навіть у Swift 4 все ще немає простого рідного способу виразити діапазон String за допомогою Int. Єдині методи Струнні , які дозволяють постачати Int як спосіб отримання підрядка по дальності є prefix
і suffix
.
Корисно мати під рукою деякі утиліти перетворення, щоб ми могли говорити як NSRange під час спілкування з String. Ось утиліта, яка займає розташування та довжину, як і 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)"
це Range<String.Index>
охоплення першого персонажа "hello"
. Як бонус, я дозволив негативним місцеположенням: "hello".range(-1,1)"
це Range<String.Index>
охоплення останнього персонажа "hello"
.
Корисно також перетворити Range<String.Index>
на NSRange для тих моментів, коли вам доведеться поговорити з какао (наприклад, при роботі з діапазонами атрибутів 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)
}
}
Я створив таке розширення:
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
Ви можете використовувати так
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
Я хочу зробити це:
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])
}
}
}
Range<String.Index>
, але іноді з цим доводиться працювати,NSString
іNSRange
тому ще якийсь контекст був би корисний. - Але подивіться на stackoverflow.com/questions/24092884/… .