Ответы:
Обновлено для Swift 4
Диапазоны Swift сложнее NSRange
, и в Swift 3 они не стали проще. Если вы хотите попытаться понять причины, лежащие в основе этой сложности, прочтите это и это . Я просто покажу вам, как их создавать и когда вы можете их использовать.
a...b
Этот оператор диапазона создает диапазон Swift, который включает как элемент, так a
и элемент b
, даже если b
это максимально возможное значение для типа (например, Int.max
). Есть два разных типа закрытых диапазонов: ClosedRange
и CountableClosedRange
.
ClosedRange
Элементы всех диапазонов в Swift сопоставимы (т. Е. Соответствуют протоколу Comparable). Это позволяет вам получить доступ к элементам в диапазоне из коллекции. Вот пример:
let myRange: ClosedRange = 1...3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]
Однако a ClosedRange
не является счетным (т. Е. Не соответствует протоколу Sequence). Это означает, что вы не можете перебирать элементы с помощью 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"]
Опять же, вы не можете перебирать a, 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 Струны вы используете Range<String.Index>
, не Range<Int>
. String Index рассчитывается на основе конкретной строки, поэтому он знает, есть ли какие-либо смайлики или расширенные кластеры графем.
пример
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...
Строка
Это также работает с диапазонами String. Если вы составляете диапазон с одним концом 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 • 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
Используйте как это
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. Единственные методы String, которые позволяют предоставить 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"
.
Полезно также преобразовать a 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/… .