Как создать диапазон в Swift?
В Objective-c мы создаем диапазон, используя NSRange
NSRange range;
Итак, как создать диапазон в Swift?
9 ответов
Обновлено для Swift 4
Стремительные диапазоны более сложны, чем NSRange
и они не стали легче в Swift 3. Если вы хотите попытаться понять причину некоторых из этих сложностей, прочитайте это и это. Я просто покажу вам, как их создавать и когда вы можете их использовать.
Закрытые диапазоны: a...b
Этот оператор диапазона создает диапазон Swift, который включает оба элемента a
и элемент b
, даже если b
максимально возможное значение для типа (например, Int.max
). Существует два разных типа закрытых диапазонов: ClosedRange
а также CountableClosedRange
,
1. ClosedRange
Элементы всех диапазонов в Swift сопоставимы (т. Е. Соответствуют протоколу Comparable). Это позволяет получить доступ к элементам в диапазоне из коллекции. Вот пример:
let myRange: ClosedRange = 1...3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]
Тем не менее, ClosedRange
не учитывается (т. е. не соответствует протоколу последовательности). Это означает, что вы не можете перебирать элементы с 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
, Как и выше, есть два разных типа полуоткрытых диапазонов: Range
а также CountableRange
,
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])
}
NSRange
Вы можете (должны) все еще использовать 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
с Emoji, и вы поймете, что я имею в виду. Головная боль.
let myNSRange = NSRange(location: 1, length: 3)
let myNSString: NSString = "abcde"
myNSString.substring(with: myNSRange) // "bcd"
let myNSString2: NSString = "acde"
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 = "acde"
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
Смотрите также:
Заметки
- Вы не можете использовать диапазон, который вы создали с одной строкой на другой строке.
- Как вы можете видеть, строковые диапазоны - боль в Swift, но они позволяют лучше справляться со смайликами и другими скалярами Юникода.
Дальнейшее обучение
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
Я нахожу удивительным, что даже в Swift 4 до сих пор не существует простого нативного способа выражения диапазона String с помощью Int. Единственные методы String, которые позволяют вам указывать Int для получения подстроки по диапазону: prefix
а также suffix
,
Полезно иметь под рукой некоторые утилиты преобразования, чтобы мы могли говорить как 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)"
это 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
Таким образом, мы можем написать еще одну утилиту, в которой мы перейдем непосредственно из местоположения и длины строки в 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
Я создал следующее расширение:
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])
}
}
}