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
, чтобы вы могли перебирать их.