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

В Objective-c мы создаем диапазон, используя NSRange

NSRange range;

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

Ответ 1

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

Сдвиговые диапазоны более сложны, чем NSRange, и они не стали легче в Swift 3. Если вы хотите попытаться понять причину некоторой сложности, прочитайте this и this. Я просто покажу вам, как их создавать и когда вы можете их использовать.

Закрытые диапазоны: 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"]

Опять же, вы не можете перебирать 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

Вы можете (обязательно) использовать 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 = "a😀cde"
myNSString2.substring(with: myNSRange) // "😀c"    Where is the "d"!?

У смайликов есть два кодовых блока UTF-16, поэтому он дает неожиданный результат, не включая "d".

Быстрое решение

Из-за этого с Swift Strings вы используете Range<String.Index>, а не Range<Int>. Индекс String рассчитывается на основе определенной строки, чтобы он знал, существуют ли какие-либо кластеры emoji или расширенные графемы.

Пример

var myString = "abcde"
let start = myString.index(myString.startIndex, offsetBy: 1)
let end = myString.index(myString.startIndex, offsetBy: 4)
let myRange = start..<end
myString.substring(with: 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.substring(with: myRange2) // "😀cd"

См. также:

Примечания

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

Дальнейшее исследование

Ответ 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

Ответ 3

(1.. < 10)

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

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

Ответ 4

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

var start = str.startIndex // Start at the string 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)

Вывод: Привет

Ответ 5

Мне кажется удивительным, что даже в Swift 4 все еще нет простого родного способа выражения диапазона строк с использованием 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".

Полезно также преобразовать a Range<String.Index> в NSRange, для тех моментов, когда вам нужно поговорить с Cocoa (например, при работе с диапазонами атрибутов 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)
    }
}

Ответ 6

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

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

Ответ 7

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

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

Ответ 8

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)

}

Ответ 9

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

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

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