Відповіді:
Оновлено для 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 оскільки воно лише порівнянне, а не строгі.
CountableRangeA 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/… .