В одной из предыдущих глав мы говорили о классификации типов данных, о таких категориях как хэшируемые (Hashable
), сопоставимые (Comparable
) и эквивалентные (Equatable
). Напомню, что тип относится к определенной категории, если обеспечивает требуемый этой категорией функционал. К примеру значение хэшируемого типа должно иметь свойство hashValue
(листинг 1).
Листинг 1
// получение хэша 12.hashValue //6713485746436704161
Последовательности (Sequence
) и коллекции (Collection
), которые мы рассматриваем в этой части книги — точно так же являются категориями. И некоторые типы, о которых мы будем говорить дальше, входят в эти категории. Всего существует великое множество различных категорий типов, их можно поделить по самым разным функциональным возможностям. И в ходе обучения, а оно надеюсь не будет для вас окончено вместе с окончанием книги, вы будете узнавать все новые и новые категории. А в какой-то момент даже начнете создавать их самостоятельно.
Диапазоны — это, как говорилось ранее, не один, а целая группа типов данных. И каждый из них относится к определенным категориям. В этом разделе я хотел бы чуть подробнее рассмотреть данный вопрос, т.к., повторюсь, категория определяет функционал типа. А значит, если вы знаете, к какой категории относится данный тип, то вы знаете, где он может быть применен.
Напомню, что мы рассмотрели 5 типов данных, которые представляют диапазоны:
Range<T>
, например 1..<5
ClosedRange<T>
, например 1...10
PartialRangeUpTo<T>
, например ..<10
PartialRangeFrom<T>
, например 1...
PartialRangeThrough<T>
, например ...10
где T
— тип данных элементов диапазона
Начнем рассмотрение материала с категорий Sequence
и Collection
. Последовательности (Sequence
) — это упорядоченный набор элементов, в котором есть возможность поочередного доступа к ним. Коллекции (Collection
) основаны на последовательностях, они позволяет осуществить прямой доступ к любому элементу по его индексу.
Не все диапазоны являются последовательностями и/или коллекциями! Взгляните на листинг 2, в котором создаются два диапазона
Листинг 2
let rangeOne = ...10 type(of: rangeOne) // PartialRangeThrough<Int> let rangeTwo = ..<10 type(of: rangeTwo) // PartialRangeUpTo<Int>
У данных диапазонов нет начального элемента, определена лишь верхняя граница. А значит мы вообще не можем считать их упорядоченным набором, т.к. не знаем, где именно этот набор начинается. Чтобы убедиться в этом откройте справку Xcode и найдите в ней описание типа PartialRangeThrough
(пункт меню Help — Developer Documentation), в разделе Conforms to не ни одного упоминания Sequence
и Collection
(рисунок 1).
Рисунок 1. Справка для типа PartialRangeThrough
Та же ситуация и с PartialRangeUpTo
. Он не является последовательностью, а значит и коллекцией.
С остальными типами диапазонов ситуация несколько более сложная, ни такая однозначная. Откройте справку для типа Range
и посмотрите на раздел Conforms to (рисунок 2).
Рисунок 2. Справка для типа Range
Обратите внимание на примечание к Sequence
и Collections
: Conforms when Bound conforms to Strideable and Bound.Stride conforms to SignedInteger. Оно определяет условие, при котором значение данного типа является последовательностью и коллекцией. Если внимательно прочитать данное примечание, то становится понятно, что отношение к последовательности и коллекции зависит от типа данных элементов диапазона, а если точнее, то его верхней и нижней границы. Другими словами, читая дословно, значение типа Range
тогда является последовательностью, когда его границы имеют такой тип данных, который входит в категорию Strideable
(и еще одно условие про Bound.Stride
, но пока его опустим).
Strideable
, если попытаться перевести на русский, то получится что-то вроде шаговые, т.е. между последовательными значениями таких типов можно переходить с определенным шагом. К примеру тип Int
входит в категорию Strideable
, т.к. каждое последующее значение отличается от предыдущего на 1. А вот String
не является Strideable
, т.к. невозможно определить, какой нужно сделать шаг, чтобы перейти от «a» к «b». Прибавить один? Думаю нет. Прибавить букву? Звучит глупо… и т.д.
Таким образом значение типа Range<Int>
является Sequence
и Collections
, а вот Range<String>
не является таковым. Подобное актуально в том числе для типа ClosedRange<T>
.
А вот с PartialRangeFrom<T>
(1...
) получается очень интересная ситуация. Если взглянуть на справочное окно (рисунок 3), то увидим, что Collection
вообще отсутсвует в списке. Это значит, что тип не относится к коллекции ни при каких условиях. А для Sequence мы видим уже знакомое нам ограничение.
Рисунок 3. Справочное окно для PartialRangeFrom
При этом стоит быть очень аккуратным. Раз это последовательность, то мы можем поочередно перебирать ее элементы (как это делать, вы узнаете уже скоро). Но представьте следующую ситуацию: Вы работаете с диапазоном типа PartialRangeFrom<UInt8>
(листинг 3).
Листинг 3
var range = UInt8(1)... type(of: range) // PartialRangeFrom<UInt8>.Type UInt8.max // максимальное значение типа - 255
Вы доходите до значения 255 в данном диапазоне и хотите взять следующее. Так как диапазон не ограничен справа, то вы вполне можете попытаться сделать это. Но при попытке получите сообщение об ошибке, т.к. 256 выходит за рамки возможностей UInt8
.
Именно по этой причине нужно всегда быть осторожным, работая с диапазонами.
Диапазоны, ограниченные с обеих сторон, являются хэшируемыми, т.е. они соответствуют требованиям протокола Hashable
и для них возможно получить значение свойства hashValue
(листинг 4).
Листинг 4
var range = 1...10 range.hashValue // 1819967165199576418 var range2 = 1..<10 range2.hashValue // 1819967165199576418
Причем, интересным является тот факт, что хэш высчитывается исходя только из первого и последнего элементов диапазона. Как видно в листинге 4, два диапазона, созданные с помощью различных операторов, но имеющие одни и те же пределы, имеют один и тот же хэш.
Диапазоны, ограниченные с двух сторон, соответствуют протоколу Equatable
, а значит могут быть проверены на эквивалентность (листинг 5).
Листинг 5
var range = 1...10 var range2 = 1...10 range == range2 // true
Все типы диапазонов не являются сопоставимыми, т.е. не соответствуют требованиям протокола Comparable
, а значит не могут быть сопоставлены. Попытка их сравнения с помощью операторов <=
, <
, >
, >=
приведет к ошибке (листинг 6).
Листинг 6
var range = 1...10 var range2 = 1...10 range < range2 // Ошибка
Важно, чтобы вы помнили, что диапазоны — это точно такие же значения, как и любые другие, рассмотренные нами ранее. Допустимые операции можно производить только между диапазонами одного типа данных. Так, к примеру, при попытке сравнения диапазонов различного типа произойдет ошибка (листинг 7).
Листинг 7
var range = 1...10 type(of: range) // ClosedRange<Int> var range2 = 1..<10 type(of: range2) // Range<Int> range == range2 // Ошибка
Вы не должны знать наизусть все, о чем мы говорили. Программирование — это такой зверь, в котором важнее понимать и знать где найти. Именно этого и хотел я добиться. Важно, что вы получили практику работы со справочной документацией и в очередной раз повторили, что такое категории типов данных.