Как создать диапазон в Swift?

104

В Objective-c мы создаем диапазон с помощью NSRange

NSRange range;

Итак, как создать диапазон в Swift?

Вичхай
источник
3
Зачем вам диапазон? Строки Swift используются Range<String.Index>, но иногда необходимо работать с NSStringи NSRange, поэтому было бы полезно добавить дополнительный контекст. - Но посмотрите stackoverflow.com/questions/24092884/… .
Martin R

Ответы:

258

Обновлено для Swift 4

Диапазоны Swift сложнее 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"]

Однако a ClosedRangeне является счетным (т. Е. Не соответствует протоколу Sequence). Это означает, что вы не можете перебирать элементы с помощью 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"]

Опять же, вы не можете перебирать a, Rangeпотому что он только сопоставим, а не стримируем.

2. 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

Вы можете (должны) время от времени использовать NSRangeSwift (например, при создании строк с атрибутами ), поэтому полезно знать, как их создать.

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

Смотрите также:

Ноты

  • Вы не можете использовать диапазон, созданный с одной строкой, в другой строке.
  • Как видите, строковые диапазоны - это боль в Swift, но они позволяют лучше справляться с эмодзи и другими скалярами Unicode.

Дальнейшее обучение

Suragch
источник
Мой комментарий относится к подразделу «Проблема с NSRange». NSStringвнутри хранит свои символы в кодировке UTF-16. Полный скаляр Юникода составляет 21 бит. Символ ухмыляющегося лица ( U+1F600) не может быть сохранен в единственной 16-битной кодовой единице, поэтому он разбивается на 2. NSRangeсчетчики на основе 16-битных кодовых единиц. В этом примере 3 кодовых единицы представляют только 2 символа
Code Different
25

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
Лео Дабус
источник
3

(1 .. <10)

возвращается ...

Диапазон = 1 .. <10

Виктор Лин
источник
Как это отвечает на вопрос?
Machavity
2

Используйте как это

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)

Выход: Здравствуйте

Дхармбир Сингх
источник
Я вижу, что у них есть тип данных "Диапазон". Так как я могу его использовать?
vichhai
@vichhai Не могли бы вы рассказать подробнее, где вы хотите использовать?
Дхармбир Сингх
Как в объекте c: диапазон NSRange;
vichhai
1

Я нахожу удивительным, что даже в 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)
    }
}
матовый
источник
1

Если кто-то хочет создать объект NSRange, можно создать как:

let range: NSRange = NSRange.init(location: 0, length: 5)

это создаст диапазон с позицией 0 и длиной 5

Ван
источник
1
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)
}
Abo3atef
источник
0

Я создал следующее расширение:

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
доктор OX
источник
0

Вы можете использовать это так

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
Мохаммад Нурдин
источник
0

Я хочу сделать это:

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])
        }
    }
}
СкоттиБлейдс
источник