Swift 3 Диапазоны: лучшие практики

Я загрузил вчера последнюю версию Xcode 8.0 и, следовательно, Swift 3. Первое, что я сделал, это попытка обновить мой проект для Swift 3, и я чуть не заплакал. Одним из самых серьезных изменений является (по моему мнению) новое управление структурой Swifts Range, особенно потому, что автоматическое преобразование в текущий синтаксис Swift ничего не делает с диапазонами.

Range разделяется на Range, CountableRange, ClosedRange и CountableClosedRange, что имеет смысл при рассмотрении того, что теперь возможно при использовании диапазонов (хотя это в основном довольно ненужно).

Однако: у меня есть много функций, принимающих параметр Range<Int> или возвращающий Range<Int>. Проблема заключается в следующем: я назвал эти функции 0..<5 например или 0...4 (потому что он иногда семантически более экспрессивный). Конечно, я мог бы просто настроить такие вещи. Но почему не все эти типы диапазонов имеют общий интерфейс? Мне пришлось бы перегружать каждую функцию для каждого из этих типов диапазонов, и каждый раз они выполняли бы те же самые операции.

Есть ли еще какие-либо рекомендации по использованию диапазонов в Swift 3?

Ответы

Ответ 1

На самом деле это довольно просто:

A Closed...Range создается с использованием трех точек: 0...10. Это включает нижнюю границу и верхнюю границу. Противоположность - это незамкнутый диапазон, созданный 0..<10, который не включает верхнюю границу.

A Countable...Range - это диапазон типа, с которым вы можете перейти со знаком целого числа, он создается либо с помощью 0...10, либо 0..<10. Эти типы соответствуют протоколу Sequence.

Несколько примеров:

0..<10 // CountableRange
0...Int.max // CountableClosedRange (this was not possible before Swift 3)

"A"..<"A" // Range, empty
"A"..."Z" // ClosedRange, because cannot stride through, only check if a character is in the bounds

Вероятно, вы должны принять ваши методы в качестве общего Collection/Sequence в зависимости от того, что вам нужно:

func test<C: Collection where C.Iterator.Element == Int>(s: C) {
    print(s.first)
}

Возможно, вы можете показать одно из ваших применений для Range<Int>

Ответ 2

Оператор закрытого диапазона

Оператор закрытого диапазона (a...b) определяет диапазон от a до b и включает значения a и b. Значение a не должно быть больше b.

Оператор закрытого диапазона полезен при итерации в диапазоне, в котором вы хотите использовать все значения, например, с циклом for-in:

for index in 1...5 {
    print("\(index) times 5 is \(index * 5)")
}
// 1 times 5 is 5
// 2 times 5 is 10
// 3 times 5 is 15
// 4 times 5 is 20
// 5 times 5 is 25

Оператор полуоткрытого диапазона

Оператор полуоткрытого диапазона (a..<b) определяет диапазон от a до b, но не включает b. Говорят, что он полуоткрыт, потому что он содержит свое первое значение, но не его конечное значение. Как и в случае оператора замкнутого диапазона, значение a не должно превышать b. Если значение a равно b, то полученный диапазон будет пустым.

Полуоткрытые диапазоны особенно полезны, когда вы работаете с нулевыми списками, такими как массивы, где полезно подсчитывать (но не включая) длину списка:

let names = ["Anna", "Alex", "Brian", "Jack"]
let count = names.count
for i in 0..<count {
    print("Person \(i + 1) is called \(names[i])")
}
// Person 1 is called Anna
// Person 2 is called Alex
// Person 3 is called Brian
// Person 4 is called Jack

Обратите внимание, что массив содержит четыре элемента, но 0..<count учитывает только 3 (индекс последнего элемента в массиве), поскольку он является полуоткрытым диапазоном.

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

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

Полуоткрытый диапазон: a..<b

let myRange = 1..<3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]

Здесь реальный пример SpriteKit, который мне пришлось преобразовать, используя arc4Random, который находится практически во всех проектах SpriteKit. Случайный часто имеет дело с диапазонами.

Swift 2

Tools.swift

func randomInRange(_ range: Range<Int>) -> Int {
    let count = UInt32(range.upperBound - range.lowerBound)
    return  Int(arc4random_uniform(count)) + range.lowerBound
}

GameScene.swift

let gap = CGFloat(randomInRange(StackGapMinWidth...maxGap))

Swift 3

Tools.swift

func randomInRange(range: ClosedRange<Int>) -> Int {
    let count = UInt32(range.upperBound - range.lowerBound)
    return  Int(arc4random_uniform(count)) + range.lowerBound
}

GameScene.swift

let gap = CGFloat(randomInRange(range: StackGapMinWidth...maxGap))

Итак, если randomInRange() вычисляет случайное число в заданном диапазоне, включая верхнюю границу, тогда его следует определить как ClosedRange<Bound>

Миграция в Swift 3

Range и ClosedRange не могут быть повторены (они больше не являются коллекциями), поскольку значение, которое является просто Comparable, не может быть увеличено. CountableRange и CountableClosedRange требуют Strideable от их привязки и соответствуют Collection, чтобы вы могли перебирать их.