Як створити діапазон у Swift?


104

У Objective-c ми створюємо діапазон, використовуючи NSRange

NSRange range;

Тож як створити діапазон у Swift?


3
Для чого вам потрібен асортимент? Швидкі рядки використовують Range<String.Index>, але іноді з цим доводиться працювати, NSStringі NSRangeтому ще якийсь контекст був би корисний. - Але подивіться на stackoverflow.com/questions/24092884/… .
Мартін Р

Відповіді:


257

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

Діапазони Swift складніші за них NSRange, і вони не стали легшими у Swift 3. Якщо ви хочете спробувати зрозуміти міркування, що стоять за деякою цією складністю, прочитайте це та це . Я просто покажу вам, як їх створити і коли ви можете їх використовувати.

Закриті діапазони: a...b

Цей оператор діапазону створює діапазон Swift, який включає як елемент, так a і елемент b, навіть якщо bце максимально можливе значення для типу (наприклад Int.max). Існує два різних типи закритих діапазонів: ClosedRangeі CountableClosedRange.

1. ClosedRange

Елементи всіх діапазонів у Swift порівнянні (тобто вони відповідають Порівнюваному протоколу). Це дозволяє отримати доступ до елементів у асортименті з колекції. Ось приклад:

let myRange: ClosedRange = 1...3

let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]

Однак a 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

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з емоджи, і ви побачите, що я маю на увазі. Головний біль.

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

Дивитися також:

Примітки

  • Ви не можете використовувати діапазон, який ви створили однією струною в іншій рядку.
  • Як бачите, діапазони рядків - це біль у Swift, але вони, можливо, дозволяють краще боротися з емоджи та іншими скалярами Unicode.

Подальше навчання


Мій коментар стосується підрозділу "Проблема з NSRange". NSStringвнутрішньо зберігає своїх символів у кодуванні UTF-16. Повний скаляр однокодового типу - 21-розрядний. Символ обличчя, що посміхнувся ( U+1F600), не може зберігатися в одному 16-бітовому блоці коду, тому він поширюється на 2. NSRangeрахується на основі 16-бітних одиниць коду. У цьому прикладі 3 кодові одиниці представляють лише 2 символи
різний код

25

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


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. Єдині методи Струнні , які дозволяють постачати 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)
    }
}

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)
}

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

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])
        }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.