Файл: Одерски Мартин, Спун Лекс, Веннерс Билл, Соммерс ФрэнкО41 Scala. Профессиональное программирование. 5е изд спб. Питер, 2022. 608 с. ил. Серия Библиотека программиста.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 09.12.2023
Просмотров: 772
Скачиваний: 11
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
24 .4 . Трейты последовательностей Seq, IndexedSeq и LinearSeq 525
последовательности обновляться. Вспомним: в главе 3 мы уже говорили, что синтаксис вида seq(idx)
=
elem
— не более чем сокращение для выражения seq.update(idx,
elem)
. Обратите внимание на разницу между методами update и updated
. Первый изменяет элемент последовательности на месте и доступен только для изменяемых последовательностей. Второй доступен для всех последовательностей и всегда вместо изменения исходной возвра
щает новую последовательность.
Таблица 24.2. Операции в трейте Seq
Что
Что делает
Индексирование и длина
xs(i)
(или после раскры
тия xs.apply(i)
)
Элемент xs с индексом i
xs.isDefinedAt(i)
Проверяет, содержится ли i
в xs.indices xs.length
Длина последовательности (то же самое, что и size
)
xs.lengthCompare(len)
Возвращает отрицательное значение
Int
, если дли
на xs меньше len
, положительное, если больше, и
0
, если они равны. Работает даже для бесконечных коллекций xs xs.indices
Диапазон индексов xs от
0
до xs.length — 1
Поиск индекса
xs.indexOf(x)
Индекс первого элемента в xs
, равного значению x
(существует несколько вариантов)
xs.lastIndexOf(x)
Индекс последнего элемента в xs
, равного значе
нию x
(существует несколько вариантов)
xs.indexOfSlice(ys)
Первый индекс xs при условии, что следующие друг за другом элементы, начинающиеся с элемента с этим индексом, составляют последовательность ys xs.lastIndexOfSlice(ys)
Последний индекс xs при условии, что следующие друг за другом элементы, начинающиеся с элемента с этим индексом, составляют последовательность ys xs.indexWhere(p)
Индекс первого элемента в xs
, удовлетворяющего условию p
(существует несколько вариантов)
xs.segmentLength(p,
i)
Длина самого длинного непрерывного сегмента элементов в xs
, начинающегося с xs(i)
, который удовлетворяет условию p
Добавления
x
+:
xs
(или xs.prepended(x)
)
Новая последовательность со значением x
, добав
ленным в начало xs
526 Глава 24 • Углубленное изучение коллекций
Что
Что делает
ys ++: xs
(или xs.prependedAll(ys)
)
Новая последовательность, состоящая из всех эле
ментов ys
, добавленных в начало xs xs :+ x
(или xs.appended(x)
)
Новая последовательность со значением x
, добав
ленным в конец xs xs :++ ys
(или xs.appendedAll(ys)
)
Новая последовательность, состоящая из всех элементов ys
, добавленных в конец xs
. То же самое, что xs
++
ys xs.padTo(len, x)
Последовательность, получающаяся при добав
лении значения x
к xs
, до тех пор, пока длина не достигнет значения len
Обновления
xs.patch(i, ys, r)
Последовательность, получающаяся путем замены r
элементов последовательности xs
, начиная с i
, элементами последовательности ys xs.updated(i, x)
Копия xs
, в которой элемент с индексом i
заменяет
ся значением x
xs(i) = x
(или после рас
крытия xs.update(i, x)
, которая доступна только для изменяемых последо
вательностей mutable.Seq
)
Изменяет значение элемента xs с индексом i
на значение x
Сортировки
xs.sorted
Новая последовательность, полученная путем сортировки элементов xs в порядке следования элементов типа xs xs.sortWith(lessThan)
Новая последовательность, полученная путем сор
тировки элементов xs с использованием в качестве операции сравнения lessThan
(Меньше чем)
xs.sortBy(f)
Новая последовательность, полученная путем сор
тировки элементов xs
. Сравнение двух элементов выполняется путем применения к ним функции f
и сравнения результатов
Реверсирования
xs.reverse
Последовательность из элементов xs
, следующих в обратном порядке xs.reverseIterator
Итератор, выдающий все элементы xs в обратном порядке
Таблица 24.2 (продолжение)
24 .4 . Трейты последовательностей Seq, IndexedSeq и LinearSeq 527
Что
Что делает
Сравнения
xs.sameElements(ys)
Проверяет, содержат ли xs и ys одинаковые элемен
ты в одном и том же порядке xs.startsWith(ys)
Проверяет, не начинается ли xs с последовательно
сти ys
(имеется несколько вариантов)
xs.endsWith(ys)
Проверяет, не заканчивается ли xs последователь
ностью ys
(имеется несколько вариантов)
xs.contains(x)
Проверяет, имеется ли в xs элемент, равный x
xs.search(x)
Проверяет, содержит ли отсортированная коллек
ция xs элемент, равный x
, и делает это потенциаль
но эффективнее, чем xs contains x
xs.containsSlice(ys)
Проверяет, имеется ли в xs непрерывная последова
тельность, равная ys xs.corresponds(ys)(p)
Проверяет, удовлетворяют ли соответствующие элементы xs и ys бинарному предикату p
Операции над множествами
xs.intersect(ys)
Пересечение множеств, состоящих из элементов последовательностей xs и ys
, которое сохраняет по
рядок следования элементов в xs xs.diff(ys)
Разность множеств, состоящих из элементов после
довательностей xs и ys
, которое сохраняет порядок следования элементов в xs xs.distinct
Часть последовательности xs
, не содержащая ду
бликатов xs.distinctBy(f)
Подпоследовательность xs
, в которой после при
менения преобразующей функции f
нет повторя
ющихся элементов
У каждого трейта
Seq есть два подтрейта:
LinearSeq и
IndexedSeq
. Они не до
бавляют никаких новых операций, но каждый их них предлагает разные ха
рактеристики производительности. У линейной последовательности (linear sequence) есть эффективные операции head и tail
, а у индексированной — эффективные операции apply
, length и (если последовательность изменя
емая) update
. В
List зачастую применяется линейная последовательность, как и в
LazyList
. Представители двух часто используемых индексированных последовательностей —
Array и
ArrayBuffer
. Класс
Vector обеспечивает весьма интересный компромисс между индексированным и последователь
ным доступом. У него практически постоянные линейные издержки как на
528 Глава 24 • Углубленное изучение коллекций индексированный, так и на последовательный доступ. Поэтому векторы слу
жат хорошей основой для смешанных схем доступа, в которых используется как индексированный, так и последовательный доступ. Более подробно мы рассмотрим векторы в разделе 24.7.
Изменяемый подтрейт
IndexedSeq добавляет операции для преобразования имеющихся элементов. В отличие от map и sort
, которые доступны в
Seq
, эти операции, представленные в табл. 24.3, не возвращают новый экземпляр коллекции.
Таблица 24.3. Операции в трейте mutable .IndexedSeq
Что
Что делает
Добавления
xs.mapInPlace(f)
Преобразует все элементы xs
, применяя к каждому из них функцию f
xs.sortInPlace()
Сортирует элементы xs на месте xs.sortInPlaceBy(f)
Сортирует элементы xs на месте и в соответствии с порядком, который определяется путем применения функции f
к каждому элементу xs.sortInPlaceWith(c)
Сортирует элементы xs на месте в соответствии с функцией сравнения c
Буферы
Важная подкатегория изменяемых последовательностей — буферы. Они по
зволяют не только обновлять существующие элементы, но и вставлять и уда
лять элементы, а также с высокой эффективностью добавлять новые элементы в конец буфера. Принципиально новые методы, поддерживаемые буфером, —
+=
(псевдоним: append
) и
++=
(псевдоним: appendAll
) для добавления элемен
тов в конец буфера,
+=:
(псевдоним: prepend
) и
++=:
(псевдоним: prependAll
) для добавления элементов в начало буфера, insert и insertAll для вставки элементов, а также remove
,
-=
(псевдоним: subtractOne
) и
--=
(псевдоним: subtractAll
) для удаления элементов. Все эти операции сведены в табл. 24.4.
Наиболее часто используются две реализации буферов:
ListBuffer и
Ar- rayBuffer
. Исходя из названий,
ListBuffer базируется на классе
List и под
держивает высокоэффективное преобразование своих элементов в список, а
ArrayBuffer базируется на массиве и может быть быстро превращен в мас
сив. Наметки реализации
ListBuffer мы показали в разделе 1.2.
24 .4 . Трейты последовательностей Seq, IndexedSeq и LinearSeq
1 ... 51 52 53 54 55 56 57 58 ... 64
529
Таблица 24.4. Операции в трейте Buffer
Что
Что делает
Добавления
buf += x
(или buf.append(x)
)
Добавляет элемент x
в конец buf и возвращает в ка
честве результата сам buf buf ++= xs
(или buf.appendAll(xs)
)
Добавляет в конец буфера все элементы xs x +=: buf
(или buf.prepend(x)
)
Добавляет элемент x
в начало буфера xs ++=: buf
(или buf.prependAll(xs)
)
Добавляет в начало буфера все элементы xs buf.insert(i, x)
Вставляет элемент x
в то место в буфере, на которое указывает индекс i
buf.insertAll(i, xs)
Вставляет все элементы xs в то место в буфере, на которое указывает индекс i
buf.padToInPlace(n, x)
Добавляет в буфер элементы x
, пока общее количе
ство его элементов не достигнет n
Удаления
buf –= x
(или buf.subtractOne(x)
)
Удаляет из буфера элемент x
buf ––= x
(или buf.subtractAll(xs)
)
Удаляет из буфера все элементы xs buf.remove(i)
Удаляет из буфера элемент с индексом i
buf.remove(i, n)
Удаляет из буфера n
элементов, начиная с элемента с индексом i
buf.trimStart(n)
Удаляет из буфера первые n
элементов buf.trimEnd(n)
Удаляет из буфера последние n
элементов buf.clear()
Удаляет из буфера все элементы
Замена:
buf.patchInPlace(i,
xs,
n)
Заменяет (максимум) n
элементов буфера элемен
тами из xs
, начиная с индекса i
Копирование
buf.clone()
Новый буфер с теми же элементами, что и в buf
530 Глава 24 • Углубленное изучение коллекций
24 .5 . Множества
Коллекции
Set
— это итерируемые
Iterable
коллекции, которые не содер
жат повторяющихся элементов. Общие операции над множествами сведены в табл. 24.5, в табл. 24.6 показаны операции для неизменяемых множеств, а в табл. 24.7 — операции для изменяемых множеств. Операции разбиты на следующие категории.
z z
Проверки contains, apply и subsetOf. Метод contains показывает, со
держит ли множество заданный элемент. Метод apply для множества является аналогом contains
, поэтому set(elem)
— то же самое, что и set contains elem
. Следовательно, множества могут также использоваться в качестве тестовых функций, возвращающих true для содержащихся в них элементов, например:
val fruit = Set("apple", "orange", "peach", "banana")
fruit("peach") // true fruit("potato") // false z
z
Добавления + (псевдоним: incl) и ++ (псевдоним: concat) добавляют в множество один и более элементов, возвращая в качестве результата новое множество.
z z
Удаления - (псевдоним: excl) и -- (псевдоним: removedAll) удаляют из множества один и более элементов, возвращая новое множество.
z z
Операции над множествами для объединения, пересечения и разности множеств. Существуют в двух формах: текстовом и символьном. К тек
стовым относятся версии intersect
, union и diff
, а к символьным —
&
,
|
и
&
. Оператор
++
, наследуемый
Set из
Iterable
, может рассматриваться в качестве еще одного псевдонима union или
|
, за исключением того, что
++
получает
IterableOnce
аргумент, а union и
|
получают множества.
Таблица 24.5. Операции в трейте Set
Что
Что делает
Проверки
xs.contains(x)
Проверяет, является ли x
элементом xs xs(x)
Делает то же самое, что и xs contains x
xs.subsetOf(ys)
Проверяет, является ли xs подмножеством ys
Удаления
xs.empty
Пустое множество того же класса, что и xs
24 .5 . Множества 531
Что
Что делает
Бинарные операции
xs & ys
(или xs.intersect(ys)
)
Пересечение множеств xs и ys xs | ys
(или xs.union(ys)
)
Объединение множеств xs и ys xs & ys
(или xs.diff(ys)
)
Разность множеств xs и ys
Неизменяемые множества предлагают методы добавления и удаления эле
ментов путем возвращения новых множеств, которые сведены в табл. 24.6.
Таблица 24.6. Операции в трейте immutable .Set
Что
Что делает
Добавления
xs + x
(или xs.incl(x)
)
Множество, содержащее все элементы xs и эле
мент x
xs ++ ys
(или xs.concat(ys)
)
Множество, содержащее все элементы xs и все элементы ys
Удаления
xs – x
(или xs.excl(x)
)
Множество, содержащее все элементы xs
, кроме x
xs –– ys
(или xs.removedAll(ys)
)
Множество, содержащее все элементы xs
, кроме элементов множества ys
У изменяемых множеств есть методы, которые добавляют, удаляют и обнов
ляют элементы, которые сведены в табл. 24.7.
Таблица 24.7. Операции в трейте mutable .Set
Что
Что делает
Добавления
xs += x
(или xs.addOne(x)
)
Добавляет элемент x
в множество xs как побочный эффект и возвращает само множество xs xs ++= ys
(или xs.addAll(ys)
)
Добавляет все элементы ys в множество xs как по
бочный эффект и возвращает само множество xs
532 Глава 24 • Углубленное изучение коллекций
Что
Что делает
xs.add(x)
Добавляет элемент x
в xs и возвращает true
, если x
прежде не был в множестве, или false
, если уже был
Удаления
xs –= x
(или xs.subtractOne(x)
)
Удаляет элемент x
из множества xs как побочный эффект и возвращает само множество xs xs ––= ys
(или xs.subtractAll(ys)
)
Удаляет все элементы ys из множества xs как побоч
ный эффект и возвращает само множество xs xs.remove(x)
Удаляет элемент x
из xs и возвращает true
, если x
прежде уже был в множестве, или false
, если его прежде там не было xs.filterInPlace(p)
Сохраняет только те элементы в xs
, которые удов
летворяют условию p
xs.clear()
Удаляет из xs все элементы
Обновление
xs(x) = b
(или после раскрытия xs.update(x,
b)
)
Если аргумент b
типа
Boolean имеет значение true
, то добавляет x
в xs
, в противном случае удаляет x
из xs
Клонирование
xs.clone()
Возвращает новое изменяемое множество с такими же элементами, как и в xs
Операция s
+=
elem в качестве побочного эффекта добавляет elem во множе
ство s
и в качестве результата возвращает измененное множество. По ана
логии с этим s
-=
elem удаляет элемент elem из множества и возвращает в качестве результата измененное множество. Помимо
+=
и
-=
, есть также операции над несколькими элементами
++=
и
--=
, которые добавляют или удаляют все элементы
Iterable или итератора.
Выбор в качестве имен методов
+=
и
-=
означает, что очень похожий код может работать как с изменяемыми, так и с неизменяемыми множествами.
Рассмотрим сначала следующий интерпретатор, в котором используется неизменяемое множество s
:
var s = Set(1, 2, 3)
s += 4
s = 2
s // Set(1, 3, 4)
Таблица 24.7 (окончание)
24 .5 . Множества 533
В этом примере в отношении var
переменной типа immutable.Set использу
ются методы
+=
и
-=
. Согласно объяснениям, которые были даны в шаге 10 главы 3, инструкции вида s
+=
4
— это сокращенная форма записи для s
=
s
+
4
. Следовательно, в их выполнении участвует еще один метод
+
, применяемый в отношении множества s
, а затем результат присваивается переменной s
. А теперь рассмотрим аналогичную работу в интерпретаторе с изменяемым множеством:
val s = collection.mutable.Set(1, 2, 3)
s += 4 // Set(1, 2, 3, 4)
s = 2 // Set(1, 3, 4)
s // Set(1, 3, 4)
Конечный эффект очень похож на предыдущий диалог с интерпретатором: начинаем мы с множеством
Set(1,
2,
3)
, а заканчиваем с множеством
Set(1,
3,
4)
. Но даже притом что инструкции выглядят такими же, как и раньше, они выполняют несколько иные действия. Теперь инструкция s
+=
4
вызыва
ет метод
+=
в отношении значения s
, которое представляет собой изменяемое множество, выполняя изменения на месте. Аналогично этому инструкция s
-=
2
теперь вызывает в отношении этого же множества метод
-=
Сравнение этих двух диалогов позволяет выявить весьма важный принцип.
Зачастую можно заменить изменяемую коллекцию, хранящуюся в val
переменной, неизменяемой коллекцией, хранящейся в var
переменной, и наоборот. Это работает по крайней мере до тех пор, пока нет псевдонимов ссылок на коллекцию, позволяющих заметить, обновилась она на месте или была создана новая коллекция.
Изменяемые множества также предоставляют в качестве вариантов
+=
и
-=
методы add и remove
. Разница в том, что методы add и remove возвращают булев результат, показывающий, возымела ли операция эффект над множе
ством.
В текущей реализации по умолчанию изменяемого множества его элементы хранятся с помощью хештаблицы. В реализации по умолчанию неизменя
емых множеств используется представление, которое адаптируется к ко
личеству элементов множества. Пустое множество представляется в виде простого объектаодиночки. Множества размером до четырех элементов представляются в виде одиночного объекта, сохраняющего все элементы как поля. Все неизменяемые множества, имеющие большие размеры, реализуют
ся в виде сжатых хешмассивов из сопоставленных префиксных деревьев
1 1
Префиксные деревья на основе сжатых хешмассивов описываются в разделе 24.7.
534 Глава 24 • Углубленное изучение коллекций
Последствия применения таких вариантов представления заключаются в том, что для множеств небольших размеров с количеством элементов, не превышающим четырех, неизменяемые множества получаются более ком
пактными и более эффективными в работе, чем изменяемые. Поэтому, если предполагается, что множество будет небольшим, попробуйте сделать его неизменяемым.
24 .6 . Отображения
Коллекции типа
Map представляют собой
Iterable
коллекции, состоящие из пар «ключ — значение», которые также называются отображениями или ассоциациями. Объект
Predef в Scala предлагает неявное преобразование, позволяющее использовать запись вида
ключ
–>
значение
в качестве альтерна
тивы синтаксиса для пары вида
(ключ,
значение)
. Таким образом, выражение для инициализации
Map("x"
–>
24,
"y"
–>
25,
"z"
–>
26)
означает абсолютно то же самое, что и выражение
Map(("x",
24),
("y",
25),
("z",
26))
, но чи
тается легче.
Основные операции над отображениями, сведенные в табл. 24.8, похожи на аналогичные операции над множествами. Неизменяемые отображения под
держивают дополнительные операции добавления и удаления, которые воз
вращают новые отображения, как показано в табл. 24.9. Изменяемые отобра
жения дополнительно поддерживают операции, перечисленные в табл. 24.10.
Операции над отображениями разбиваются на следующие категории.
z z
Операции поиска apply, get, getOrElse, contains и isDefinedAt превра
щают отображения в частично примененные функции от ключей к значе
ниям. Основной метод поиска для отображений выглядит так:
def get(key): Option[Value]
Операция m.get(key)
проверяет, содержит ли отображение ассоциацию для заданного ключа. Будучи в наличии, такая ассоциация возвращает значение ассоциации в виде объекта типа
Some
. Если такой ключ в ото
бражении не определен, то get возвращает
None
. В отображениях также определяется метод apply
, возвращающий значение, непосредственно ассоциированное с заданным ключом, без его инкапсуляции в
Option
Если ключ в отображении не определен, то выдается исключение.
z z
Добавления и обновления + (псевдоним: updated), ++ (псевдоним:
concat) updateWith и updatedWith позволяют добавлять к отображению новые привязки или изменять уже существующие.
24 .6 . Отображения 535
z z
Удаления - (псевдоним: removed) и -- (псевдоним: removedAll) позво
ляют удалять привязки из отображения.
z z
Операции создания подколлекций keys, keySet, keysIterator, valu-
esIterator и values возвращают по отдельности ключи и значения ото
бражений в различных формах.
z z
Преобразования filterKeys и mapValues создают новое отображение путем фильтрации и преобразования привязок существующего отображения.
Таблица 24.8. Операции в трейте Map
Что
Что делает
Поиск
ms.get(k)
Значение
Option
, связанное с ключом k
в отображе
нии ms
, или
None
, если ключ не найден ms(k)
(или после рас
крытия ms apply k
)
Значение, связанное с ключом k
в отображении ms
, или выдает исключение, если ключ не найден ms.getOrElse(k, d)
Значение, связанное с ключом k
в отображении ms
, или значение по умолчанию d
, если ключ не найден ms.contains(k)
Проверяет, содержится ли в ms отображение для ключа k
ms.isDefinedAt(k)
То же, что и contains
Создание подколлекций
ms.keys
Iterableколлекция, содержащая каждый ключ, име
ющийся в ms ms.keySet
Множество, содержащее каждый ключ, имеющийся в ms ms.keysIterator
Итератор, выдающий каждый ключ, имеющийся в ms ms.values
Iterableколлекция, содержащая каждое значение, связанное с ключом в ms ms.valuesIterator
Итератор, выдающий каждое значение, связанное с ключом в ms
Преобразования
ms.view.filterKeys(p)
Представление отображения, содержащее только те отображения в ms
, в которых ключ удовлетворяет условию p
ms.view.mapValues(f)
Представление отображения, получающееся в резуль
тате применения функции f
к каждому значению, связанному с ключом в ms
536 Глава 24 • Углубленное изучение коллекций
Таблица 24.9. Операции в трейте immutable .Map
Что
Что делает
Добавления и обновления
ms + (k –> v)
(или ms.updated(k, v)
)
Отображение, содержащее все ассоциации ms
, а также ассоциацию k –> v ключа k
со значением v
ms ++= kvs
(или ms.concat(kvs)
)
Отображение, содержащее все ассоциации ms
, а также все пары «ключ — значение» из kvs ms.updatedWith(k)(f)
Отображение с добавлением, обновлением или уда
лением привязки для ключа k
. Функция f
принимает в качестве параметра значение, связанное в настоя
щий момент с ключом k
(или
None
, если такой привяз
ки нет), и возвращает новое значение (или
None для удаления привязки)
Удаления
ms – k
(или ms.removed(k)
)
Отображение, содержащее все ассоциации ms
, за ис
ключением тех, которые относятся к ключу k
ms –– ks
(или ms.removedAll(ks)
)
Отображение, содержащее все ассоциации ms
, за ис
ключением тех, ключи которых входят в ks
Таблица 24.10. Операции в трейте mutable .Map
Что
Что делает
Добавления и обновления
ms(k)
=
v
(или после рас
крытия ms.update(k,
v)
)
Добавляет в качестве побочного эффекта ассо
циацию ключа k
со значением v
к отображе
нию ms
, перезаписывая все ранее имевшиеся ассоциации k
ms
+=
(k
–>
v)
Добавляет в качестве побочного эффекта ассоциа
цию ключа k
со значением v
к отображению ms и возвращает само отображение ms ms
++=
kvs
Добавляет в качестве побочного эффекта все ассо
циации, имеющиеся в kvs
, к ms и возвращает само отображение ms ms.put(k,
v)
Добавляет к ms ассоциацию ключа k
со значением v
и возвращает как Option любое значение, ранее связанное с k
ms.getOrElseUpdate(k,
d)
Если ключ k
определен в отображении ms
, то воз
вращает связанное с ним значение. В противном случае обновляет ms ассоциацией k –> d и возвра
щает d
24 .6 . Отображения 537
Что
Что делает
ms.updateWith(k)(f)
Добавляет, обновляет или удаляет ассоциацию с ключом k
. Функция f
принимает в качестве параметра значение, которое в настоящий момент связано с k
(или
None
, если такой ассоциации нет), и возвращает новое значение (или
None t
при удале
нии ассоциации)
Удаления
ms
–=
k
Удаляет в качестве побочного эффекта ассоциацию с ключом k
из ms и возвращает само отображение ms ms
––=
ks
Удаляет в качестве побочного эффекта все ассоциа
ции из ms с ключами, имеющимися в ks
, и возвраща
ет само отображение ms ms.remove(k)
Удаляет все ассоциации с ключом k
из ms и воз
вращает как Option любое значение, ранее связан
ное с k
ms.filterInPlace(p)
Сохраняет в ms только те ассоциации, у которых ключ удовлетворяет условию p
ms.clear()
Удаляет из ms все ассоциации
Преобразование и клонирование
ms.mapValuesInPlace(f)
Выполняет преобразование всех связанных значе
ний в отображении ms с помощью функции f
ms.clone()
Возвращает новое изменяемое отображение с таки
ми же ассоциациями, как и в ms
Операции добавления и удаления для отображений — зеркальные отражения таких же операций для множеств. Неизменяемое отображение может быть преобразовано с помощью операций
+
,
- и updated
. Для сравнения: изменя
емое отображение m
можно обновить «на месте» двумя способами: m(key)
=
value и m
+=
(key
–>
value)
. Изменяемые отображения также поддерживают вариант m.put(key,
value)
, который возвращает значение
Option
, содержа
щее то, что прежде ассоциировалось с ключом, или
None
, если ранее такой ключ в отображении отсутствовал.
Метод getOrElseUpdate пригодится для обращения к отображениям там, где они действуют в качестве кэша. Скажем, у вас есть весьма затратное вычис
ление, запускаемое путем вызова функции f
:
def f(x: String) =
println("taking my time.")
Thread.sleep(100)
x.reverse
538 Глава 24 • Углубленное изучение коллекций
Далее предположим, что у f
нет побочных эффектов, поэтому ее повторный вы
зов с тем же самым аргументом всегда будет выдавать тот же самый результат.
В таком случае можно сберечь время, сохранив ранее вычисленные привязки аргумента и результата выполнения f
в отображении, и вычислять результат выполнения f
, только если результат для аргумента не был найден в отображе
нии. Можно сказать, что отображение — это кэш для вычислений функции f
:
val cache = collection.mutable.Map[String, String]()
Теперь можно создать более эффективную кэшированную версию функ
ции f
:
scala> def cachedF(s: String) = cache.getOrElseUpdate(s, f(s))
def cachedF(s: String): String scala> cachedF("abc")
taking my time.
val res16: String = cba scala> cachedF("abc")
val res17: String = cba
Обратите внимание: второй аргумент getOrElseUpdate
— это аргумент, пере
даваемый по имени. Следовательно, показанное ранее вычисление f("abc")
выполняется лишь в том случае, если методу getOrElseUpdate потребуется значение его второго аргумента, что происходит именно тогда, когда его первый аргумент не найден в кэширующем отображении. Вы могли бы также непосредственно реализовать cachedF
, используя только основные операции с отображениями, но для этого понадобится дополнительный код:
def cachedF(arg: String) =
cache.get(arg) match case Some(result) => result case None =>
val result = f(arg)
cache(arg) = result result
24 .7 . Конкретные классы неизменяемых коллекций
В Scala на выбор предлагается множество конкретных классов неизменя
емых коллекций. Друг от друга они отличаются реализуемыми трейтами
(отображения, множества, последовательности) тем, могут ли они быть бес
24 .7 . Конкретные классы неизменяемых коллекций
1 ... 52 53 54 55 56 57 58 59 ... 64
Таблица 24.4. Операции в трейте Buffer
Что
Что делает
Добавления
buf += x
(или buf.append(x)
)
Добавляет элемент x
в конец buf и возвращает в ка
честве результата сам buf buf ++= xs
(или buf.appendAll(xs)
)
Добавляет в конец буфера все элементы xs x +=: buf
(или buf.prepend(x)
)
Добавляет элемент x
в начало буфера xs ++=: buf
(или buf.prependAll(xs)
)
Добавляет в начало буфера все элементы xs buf.insert(i, x)
Вставляет элемент x
в то место в буфере, на которое указывает индекс i
buf.insertAll(i, xs)
Вставляет все элементы xs в то место в буфере, на которое указывает индекс i
buf.padToInPlace(n, x)
Добавляет в буфер элементы x
, пока общее количе
ство его элементов не достигнет n
Удаления
buf –= x
(или buf.subtractOne(x)
)
Удаляет из буфера элемент x
buf ––= x
(или buf.subtractAll(xs)
)
Удаляет из буфера все элементы xs buf.remove(i)
Удаляет из буфера элемент с индексом i
buf.remove(i, n)
Удаляет из буфера n
элементов, начиная с элемента с индексом i
buf.trimStart(n)
Удаляет из буфера первые n
элементов buf.trimEnd(n)
Удаляет из буфера последние n
элементов buf.clear()
Удаляет из буфера все элементы
Замена:
buf.patchInPlace(i,
xs,
n)
Заменяет (максимум) n
элементов буфера элемен
тами из xs
, начиная с индекса i
Копирование
buf.clone()
Новый буфер с теми же элементами, что и в buf
530 Глава 24 • Углубленное изучение коллекций
24 .5 . Множества
Коллекции
Set
— это итерируемые
Iterable
коллекции, которые не содер
жат повторяющихся элементов. Общие операции над множествами сведены в табл. 24.5, в табл. 24.6 показаны операции для неизменяемых множеств, а в табл. 24.7 — операции для изменяемых множеств. Операции разбиты на следующие категории.
z z
Проверки contains, apply и subsetOf. Метод contains показывает, со
держит ли множество заданный элемент. Метод apply для множества является аналогом contains
, поэтому set(elem)
— то же самое, что и set contains elem
. Следовательно, множества могут также использоваться в качестве тестовых функций, возвращающих true для содержащихся в них элементов, например:
val fruit = Set("apple", "orange", "peach", "banana")
fruit("peach") // true fruit("potato") // false z
z
Добавления + (псевдоним: incl) и ++ (псевдоним: concat) добавляют в множество один и более элементов, возвращая в качестве результата новое множество.
z z
Удаления - (псевдоним: excl) и -- (псевдоним: removedAll) удаляют из множества один и более элементов, возвращая новое множество.
z z
Операции над множествами для объединения, пересечения и разности множеств. Существуют в двух формах: текстовом и символьном. К тек
стовым относятся версии intersect
, union и diff
, а к символьным —
&
,
|
и
&
. Оператор
++
, наследуемый
Set из
Iterable
, может рассматриваться в качестве еще одного псевдонима union или
|
, за исключением того, что
++
получает
IterableOnce
аргумент, а union и
|
получают множества.
Таблица 24.5. Операции в трейте Set
Что
Что делает
Проверки
xs.contains(x)
Проверяет, является ли x
элементом xs xs(x)
Делает то же самое, что и xs contains x
xs.subsetOf(ys)
Проверяет, является ли xs подмножеством ys
Удаления
xs.empty
Пустое множество того же класса, что и xs
24 .5 . Множества 531
Что
Что делает
Бинарные операции
xs & ys
(или xs.intersect(ys)
)
Пересечение множеств xs и ys xs | ys
(или xs.union(ys)
)
Объединение множеств xs и ys xs & ys
(или xs.diff(ys)
)
Разность множеств xs и ys
Неизменяемые множества предлагают методы добавления и удаления эле
ментов путем возвращения новых множеств, которые сведены в табл. 24.6.
Таблица 24.6. Операции в трейте immutable .Set
Что
Что делает
Добавления
xs + x
(или xs.incl(x)
)
Множество, содержащее все элементы xs и эле
мент x
xs ++ ys
(или xs.concat(ys)
)
Множество, содержащее все элементы xs и все элементы ys
Удаления
xs – x
(или xs.excl(x)
)
Множество, содержащее все элементы xs
, кроме x
xs –– ys
(или xs.removedAll(ys)
)
Множество, содержащее все элементы xs
, кроме элементов множества ys
У изменяемых множеств есть методы, которые добавляют, удаляют и обнов
ляют элементы, которые сведены в табл. 24.7.
Таблица 24.7. Операции в трейте mutable .Set
Что
Что делает
Добавления
xs += x
(или xs.addOne(x)
)
Добавляет элемент x
в множество xs как побочный эффект и возвращает само множество xs xs ++= ys
(или xs.addAll(ys)
)
Добавляет все элементы ys в множество xs как по
бочный эффект и возвращает само множество xs
532 Глава 24 • Углубленное изучение коллекций
Что
Что делает
xs.add(x)
Добавляет элемент x
в xs и возвращает true
, если x
прежде не был в множестве, или false
, если уже был
Удаления
xs –= x
(или xs.subtractOne(x)
)
Удаляет элемент x
из множества xs как побочный эффект и возвращает само множество xs xs ––= ys
(или xs.subtractAll(ys)
)
Удаляет все элементы ys из множества xs как побоч
ный эффект и возвращает само множество xs xs.remove(x)
Удаляет элемент x
из xs и возвращает true
, если x
прежде уже был в множестве, или false
, если его прежде там не было xs.filterInPlace(p)
Сохраняет только те элементы в xs
, которые удов
летворяют условию p
xs.clear()
Удаляет из xs все элементы
Обновление
xs(x) = b
(или после раскрытия xs.update(x,
b)
)
Если аргумент b
типа
Boolean имеет значение true
, то добавляет x
в xs
, в противном случае удаляет x
из xs
Клонирование
xs.clone()
Возвращает новое изменяемое множество с такими же элементами, как и в xs
Операция s
+=
elem в качестве побочного эффекта добавляет elem во множе
ство s
и в качестве результата возвращает измененное множество. По ана
логии с этим s
-=
elem удаляет элемент elem из множества и возвращает в качестве результата измененное множество. Помимо
+=
и
-=
, есть также операции над несколькими элементами
++=
и
--=
, которые добавляют или удаляют все элементы
Iterable или итератора.
Выбор в качестве имен методов
+=
и
-=
означает, что очень похожий код может работать как с изменяемыми, так и с неизменяемыми множествами.
Рассмотрим сначала следующий интерпретатор, в котором используется неизменяемое множество s
:
var s = Set(1, 2, 3)
s += 4
s = 2
s // Set(1, 3, 4)
Таблица 24.7 (окончание)
24 .5 . Множества 533
В этом примере в отношении var
переменной типа immutable.Set использу
ются методы
+=
и
-=
. Согласно объяснениям, которые были даны в шаге 10 главы 3, инструкции вида s
+=
4
— это сокращенная форма записи для s
=
s
+
4
. Следовательно, в их выполнении участвует еще один метод
+
, применяемый в отношении множества s
, а затем результат присваивается переменной s
. А теперь рассмотрим аналогичную работу в интерпретаторе с изменяемым множеством:
val s = collection.mutable.Set(1, 2, 3)
s += 4 // Set(1, 2, 3, 4)
s = 2 // Set(1, 3, 4)
s // Set(1, 3, 4)
Конечный эффект очень похож на предыдущий диалог с интерпретатором: начинаем мы с множеством
Set(1,
2,
3)
, а заканчиваем с множеством
Set(1,
3,
4)
. Но даже притом что инструкции выглядят такими же, как и раньше, они выполняют несколько иные действия. Теперь инструкция s
+=
4
вызыва
ет метод
+=
в отношении значения s
, которое представляет собой изменяемое множество, выполняя изменения на месте. Аналогично этому инструкция s
-=
2
теперь вызывает в отношении этого же множества метод
-=
Сравнение этих двух диалогов позволяет выявить весьма важный принцип.
Зачастую можно заменить изменяемую коллекцию, хранящуюся в val
переменной, неизменяемой коллекцией, хранящейся в var
переменной, и наоборот. Это работает по крайней мере до тех пор, пока нет псевдонимов ссылок на коллекцию, позволяющих заметить, обновилась она на месте или была создана новая коллекция.
Изменяемые множества также предоставляют в качестве вариантов
+=
и
-=
методы add и remove
. Разница в том, что методы add и remove возвращают булев результат, показывающий, возымела ли операция эффект над множе
ством.
В текущей реализации по умолчанию изменяемого множества его элементы хранятся с помощью хештаблицы. В реализации по умолчанию неизменя
емых множеств используется представление, которое адаптируется к ко
личеству элементов множества. Пустое множество представляется в виде простого объектаодиночки. Множества размером до четырех элементов представляются в виде одиночного объекта, сохраняющего все элементы как поля. Все неизменяемые множества, имеющие большие размеры, реализуют
ся в виде сжатых хешмассивов из сопоставленных префиксных деревьев
1 1
Префиксные деревья на основе сжатых хешмассивов описываются в разделе 24.7.
534 Глава 24 • Углубленное изучение коллекций
Последствия применения таких вариантов представления заключаются в том, что для множеств небольших размеров с количеством элементов, не превышающим четырех, неизменяемые множества получаются более ком
пактными и более эффективными в работе, чем изменяемые. Поэтому, если предполагается, что множество будет небольшим, попробуйте сделать его неизменяемым.
24 .6 . Отображения
Коллекции типа
Map представляют собой
Iterable
коллекции, состоящие из пар «ключ — значение», которые также называются отображениями или ассоциациями. Объект
Predef в Scala предлагает неявное преобразование, позволяющее использовать запись вида
ключ
–>
значение
в качестве альтерна
тивы синтаксиса для пары вида
(ключ,
значение)
. Таким образом, выражение для инициализации
Map("x"
–>
24,
"y"
–>
25,
"z"
–>
26)
означает абсолютно то же самое, что и выражение
Map(("x",
24),
("y",
25),
("z",
26))
, но чи
тается легче.
Основные операции над отображениями, сведенные в табл. 24.8, похожи на аналогичные операции над множествами. Неизменяемые отображения под
держивают дополнительные операции добавления и удаления, которые воз
вращают новые отображения, как показано в табл. 24.9. Изменяемые отобра
жения дополнительно поддерживают операции, перечисленные в табл. 24.10.
Операции над отображениями разбиваются на следующие категории.
z z
Операции поиска apply, get, getOrElse, contains и isDefinedAt превра
щают отображения в частично примененные функции от ключей к значе
ниям. Основной метод поиска для отображений выглядит так:
def get(key): Option[Value]
Операция m.get(key)
проверяет, содержит ли отображение ассоциацию для заданного ключа. Будучи в наличии, такая ассоциация возвращает значение ассоциации в виде объекта типа
Some
. Если такой ключ в ото
бражении не определен, то get возвращает
None
. В отображениях также определяется метод apply
, возвращающий значение, непосредственно ассоциированное с заданным ключом, без его инкапсуляции в
Option
Если ключ в отображении не определен, то выдается исключение.
z z
Добавления и обновления + (псевдоним: updated), ++ (псевдоним:
concat) updateWith и updatedWith позволяют добавлять к отображению новые привязки или изменять уже существующие.
24 .6 . Отображения 535
z z
Удаления - (псевдоним: removed) и -- (псевдоним: removedAll) позво
ляют удалять привязки из отображения.
z z
Операции создания подколлекций keys, keySet, keysIterator, valu-
esIterator и values возвращают по отдельности ключи и значения ото
бражений в различных формах.
z z
Преобразования filterKeys и mapValues создают новое отображение путем фильтрации и преобразования привязок существующего отображения.
Таблица 24.8. Операции в трейте Map
Что
Что делает
Поиск
ms.get(k)
Значение
Option
, связанное с ключом k
в отображе
нии ms
, или
None
, если ключ не найден ms(k)
(или после рас
крытия ms apply k
)
Значение, связанное с ключом k
в отображении ms
, или выдает исключение, если ключ не найден ms.getOrElse(k, d)
Значение, связанное с ключом k
в отображении ms
, или значение по умолчанию d
, если ключ не найден ms.contains(k)
Проверяет, содержится ли в ms отображение для ключа k
ms.isDefinedAt(k)
То же, что и contains
Создание подколлекций
ms.keys
Iterableколлекция, содержащая каждый ключ, име
ющийся в ms ms.keySet
Множество, содержащее каждый ключ, имеющийся в ms ms.keysIterator
Итератор, выдающий каждый ключ, имеющийся в ms ms.values
Iterableколлекция, содержащая каждое значение, связанное с ключом в ms ms.valuesIterator
Итератор, выдающий каждое значение, связанное с ключом в ms
Преобразования
ms.view.filterKeys(p)
Представление отображения, содержащее только те отображения в ms
, в которых ключ удовлетворяет условию p
ms.view.mapValues(f)
Представление отображения, получающееся в резуль
тате применения функции f
к каждому значению, связанному с ключом в ms
536 Глава 24 • Углубленное изучение коллекций
Таблица 24.9. Операции в трейте immutable .Map
Что
Что делает
Добавления и обновления
ms + (k –> v)
(или ms.updated(k, v)
)
Отображение, содержащее все ассоциации ms
, а также ассоциацию k –> v ключа k
со значением v
ms ++= kvs
(или ms.concat(kvs)
)
Отображение, содержащее все ассоциации ms
, а также все пары «ключ — значение» из kvs ms.updatedWith(k)(f)
Отображение с добавлением, обновлением или уда
лением привязки для ключа k
. Функция f
принимает в качестве параметра значение, связанное в настоя
щий момент с ключом k
(или
None
, если такой привяз
ки нет), и возвращает новое значение (или
None для удаления привязки)
Удаления
ms – k
(или ms.removed(k)
)
Отображение, содержащее все ассоциации ms
, за ис
ключением тех, которые относятся к ключу k
ms –– ks
(или ms.removedAll(ks)
)
Отображение, содержащее все ассоциации ms
, за ис
ключением тех, ключи которых входят в ks
Таблица 24.10. Операции в трейте mutable .Map
Что
Что делает
Добавления и обновления
ms(k)
=
v
(или после рас
крытия ms.update(k,
v)
)
Добавляет в качестве побочного эффекта ассо
циацию ключа k
со значением v
к отображе
нию ms
, перезаписывая все ранее имевшиеся ассоциации k
ms
+=
(k
–>
v)
Добавляет в качестве побочного эффекта ассоциа
цию ключа k
со значением v
к отображению ms и возвращает само отображение ms ms
++=
kvs
Добавляет в качестве побочного эффекта все ассо
циации, имеющиеся в kvs
, к ms и возвращает само отображение ms ms.put(k,
v)
Добавляет к ms ассоциацию ключа k
со значением v
и возвращает как Option любое значение, ранее связанное с k
ms.getOrElseUpdate(k,
d)
Если ключ k
определен в отображении ms
, то воз
вращает связанное с ним значение. В противном случае обновляет ms ассоциацией k –> d и возвра
щает d
24 .6 . Отображения 537
Что
Что делает
ms.updateWith(k)(f)
Добавляет, обновляет или удаляет ассоциацию с ключом k
. Функция f
принимает в качестве параметра значение, которое в настоящий момент связано с k
(или
None
, если такой ассоциации нет), и возвращает новое значение (или
None t
при удале
нии ассоциации)
Удаления
ms
–=
k
Удаляет в качестве побочного эффекта ассоциацию с ключом k
из ms и возвращает само отображение ms ms
––=
ks
Удаляет в качестве побочного эффекта все ассоциа
ции из ms с ключами, имеющимися в ks
, и возвраща
ет само отображение ms ms.remove(k)
Удаляет все ассоциации с ключом k
из ms и воз
вращает как Option любое значение, ранее связан
ное с k
ms.filterInPlace(p)
Сохраняет в ms только те ассоциации, у которых ключ удовлетворяет условию p
ms.clear()
Удаляет из ms все ассоциации
Преобразование и клонирование
ms.mapValuesInPlace(f)
Выполняет преобразование всех связанных значе
ний в отображении ms с помощью функции f
ms.clone()
Возвращает новое изменяемое отображение с таки
ми же ассоциациями, как и в ms
Операции добавления и удаления для отображений — зеркальные отражения таких же операций для множеств. Неизменяемое отображение может быть преобразовано с помощью операций
+
,
- и updated
. Для сравнения: изменя
емое отображение m
можно обновить «на месте» двумя способами: m(key)
=
value и m
+=
(key
–>
value)
. Изменяемые отображения также поддерживают вариант m.put(key,
value)
, который возвращает значение
Option
, содержа
щее то, что прежде ассоциировалось с ключом, или
None
, если ранее такой ключ в отображении отсутствовал.
Метод getOrElseUpdate пригодится для обращения к отображениям там, где они действуют в качестве кэша. Скажем, у вас есть весьма затратное вычис
ление, запускаемое путем вызова функции f
:
def f(x: String) =
println("taking my time.")
Thread.sleep(100)
x.reverse
538 Глава 24 • Углубленное изучение коллекций
Далее предположим, что у f
нет побочных эффектов, поэтому ее повторный вы
зов с тем же самым аргументом всегда будет выдавать тот же самый результат.
В таком случае можно сберечь время, сохранив ранее вычисленные привязки аргумента и результата выполнения f
в отображении, и вычислять результат выполнения f
, только если результат для аргумента не был найден в отображе
нии. Можно сказать, что отображение — это кэш для вычислений функции f
:
val cache = collection.mutable.Map[String, String]()
Теперь можно создать более эффективную кэшированную версию функ
ции f
:
scala> def cachedF(s: String) = cache.getOrElseUpdate(s, f(s))
def cachedF(s: String): String scala> cachedF("abc")
taking my time.
val res16: String = cba scala> cachedF("abc")
val res17: String = cba
Обратите внимание: второй аргумент getOrElseUpdate
— это аргумент, пере
даваемый по имени. Следовательно, показанное ранее вычисление f("abc")
выполняется лишь в том случае, если методу getOrElseUpdate потребуется значение его второго аргумента, что происходит именно тогда, когда его первый аргумент не найден в кэширующем отображении. Вы могли бы также непосредственно реализовать cachedF
, используя только основные операции с отображениями, но для этого понадобится дополнительный код:
def cachedF(arg: String) =
cache.get(arg) match case Some(result) => result case None =>
val result = f(arg)
cache(arg) = result result
24 .7 . Конкретные классы неизменяемых коллекций
В Scala на выбор предлагается множество конкретных классов неизменя
емых коллекций. Друг от друга они отличаются реализуемыми трейтами
(отображения, множества, последовательности) тем, могут ли они быть бес
24 .7 . Конкретные классы неизменяемых коллекций
1 ... 52 53 54 55 56 57 58 59 ... 64
539
конечными, и скоростью выполнения различных операций. Начнем с обзора наиболее востребованных типов неизменяемых коллекций.
Списки
Списки относятся к конечным неизменяемым последовательностям. Они обеспечивают постоянное время доступа к своим первым элементам, а также ко всей остальной части списка и имеют постоянное время выполнения опе
раций cons для добавления нового элемента в начало списка. Многие другие операции имеют линейную зависимость времени выполнения от длины списка. Более подробно списки рассматриваются в главах 14 и 1.
Ленивые списки
Ленивые списки похожи на списки, за исключением того, что их элементы вычисляются лениво (или отложенно). Вычисляться будут только запрошен
ные элементы. Поэтому ленивые списки могут быть бесконечно длинными.
Во всем остальном они имеют такие же характеристики производительности, что и списки.
В то время как списки конструируются с помощью оператора
::
, ленивые конструируются с помощью похожего оператора
#::
. Пример ленивого спи
ска, содержащего целые числа
1
,
2
и
3
, выглядит следующим образом:
scala> val str = 1 #:: 2 #:: 3 #:: LazyList.empty val str: scala.collection.immutable.LazyList[Int] =
LazyList(
«Голова» этого ленивого списка —
1
, а «хвост» —
2
и
3
. Но никакие элементы здесь не выводятся, поскольку список еще не вычислен! Ленивые списки объявлены как вычисляющиеся лениво, и метод toString
, вызванный в от
ношении такого списка, заботится о том, чтобы не навязывать лишние вы
числения.
Далее показан более сложный пример. В нем вычисляется ленивый список, содержащий последовательность чисел Фибоначчи с заданными двумя числами. Каждый элемент последовательности Фибоначчи есть сумма двух предыдущих элементов в серии:
scala> def fibFrom(a: Int, b: Int): LazyList[Int] =
a #:: fibFrom(b, a + b)
def fibFrom: (a: Int, b: Int)LazyList[Int]
540 Глава 24 • Углубленное изучение коллекций
Эта функция кажется обманчиво простой. Понятно, что первый элемент последовательности — a
, за ним стоит последовательность Фибоначчи, на
чинающаяся с b
, затем — элемент со значением a
+
b
. Самое сложное здесь — вычислить данную последовательность, не вызвав бесконечную рекурсию.
Если функция вместо
#::
использует оператор
::
, то каждый вызов функции будет приводить к еще одному вызову, что выльется в бесконечную рекур
сию. Но, поскольку применяется оператор
#::
, правая часть выражения не вычисляется до тех пор, пока не будет востребована.
Вот как выглядят первые несколько элементов последовательности Фибо
наччи, начинающейся с двух заданных чисел:
scala> val fibs = fibFrom(1, 1).take(7)
val fibs: scala.collection.immutable.LazyList[Int] =
LazyList(
scala> fibs.toList val res23: List[Int] = List(1, 1, 2, 3, 5, 8, 13)
Неизменяемые ArraySeq
Списки очень эффективны в алгоритмах, которые работают исключитель
но с начальными элементами. Получение, добавление и удаление начала списка занимает постоянное время. А вот получение или изменения по
следующих элементов — это операции линейного времени, зависящие от длины списка. В результате список может быть не самым оптимальным вариантом для алгоритмов, которые не ограничиваются обработкой на
чальных элементов.
Последовательный массив (
ArraySeq
) — это неизменяемая последователь
ность на основе приватного массива, которая решает проблему неэффек
тивного произвольного доступа в списках. Последовательные массивы позволяют получить любой элемент коллекции за постоянное время. Бла
годаря этому вы можете не ограничиваться работой только с начальными элементами. Время получения элемента не зависит от его местоположения, и потому
ArraySeq может оказаться эффективней списков в некоторых алгоритмах.
С другой стороны, тип
ArraySeq основан на
Array
, поэтому добавление эле
ментов в начало занимает линейное время, а не постоянное, как в случае со списками. Более того, на всякое добавление или обновление одного элемента в
ArraySeq уходит линейное время, поскольку при этом копируется весь внутренний массив.
24 .7 . Конкретные классы неизменяемых коллекций 541
Векторы
List и
ArraySeq
— структуры данных, эффективные для одних вариантов ис
пользования и неэффективные для других. Например, добавление элемента в начало
List занимает постоянное время, а в
ArraySeq
— линейное. С другой стороны, индексированный доступ занимает постоянное время в
ArraySeq и линейное в
List
Вектор обеспечивает хорошую производительность для всех своих операций.
Доступ и обновление любых элементов вектора занимает «эффективно по
стоянное время», как описано ниже. Эти операции выполняются медленнее, чем получение начала списка или чтение элементов в последовательном массиве, но время их выполнения остается постоянным. В результате алго
ритмы, использующие векторы, не должны ограничиваться доступом или обновлением только к началу последовательности. Они могут обращаться к элементам и обновлять их в произвольном месте и поэтому могут быть гораздо более удобными в написании.
Создаются и изменяются векторы точно так же, как и любые другие после
довательности:
val vec = scala.collection.immutable.Vector.empty val vec2 = vec :+ 1 :+ 2 // Vector(1, 2)
val vec3 = 100 +: vec2 // Vector(100, 1, 2)
vec3(0) // 100
Для представления векторов используются широкие неглубокие деревья.
Каждый узел дерева содержит до 32 элементов вектора или до 32 других узлов дерева. Векторы, содержащие до 32 элементов, могут быть представ
лены одним узлом. Векторы с количеством элементов до 32 · 32 = 1024 могут быть представлены в виде одного направления. Двух переходов от корня дерева к конечному элементу достаточно для векторов, имеющих до 2 15
эле
ментов, трех — для векторов с 2 20
, четырех — для векторов с 2 25
элементов, а пяти — для векторов с количеством элементов до 2 30
. Следовательно, для всех векторов разумного размера выбор элемента требует до пяти обычных доступов к массиву. Именно это мы имели в виду, когда написали «эффек
тивно постоянное время».
Векторы неизменяемы, следовательно, внести в элемент вектора изменение на месте невозможно. Но метод updated позволяет создать новый вектор, отличающийся от заданного только одним элементом:
val vec = Vector(1, 2, 3)
vec.updated(2, 4) // Vector(1, 2, 4)
vec // Vector(1, 2, 3)
542 Глава 24 • Углубленное изучение коллекций
Последняя строка данного кода показывает, что вызов метода updated никак не повлиял на исходный вектор vec
. Функциональное обновление векторов также занимает «эффективно постоянное время». Обновить элемент в се
редине вектора можно с помощью копирования узла, содержащего элемент, и каждого указывающего на него узла, начиная с корня дерева. Это значит, функциональное обновление создает от одного до пяти узлов, каждый из которых содержит до 32 элементов или поддеревьев. Конечно, это гораздо затратнее обновления на месте в изменяемом массиве, но все же намного дешевле копирования всего вектора.
Векторы сохраняют разумный баланс между быстрым произвольным досту
пом и быстрыми произвольными функциональными обновлениями, поэтому в настоящий момент они представляют исходную реализацию неизменяемых индексированных последовательностей:
collection.immutable.IndexedSeq(1, 2, 3) // Vector(1, 2, 3)
Неизменяемые очереди
В очередях используется принцип «первым пришел, первым вышел». Упро
щенная реализация неизменяемых очередей рассматривалась в главе 18.
Создать пустую неизменяемую очередь можно следующим образом:
val empty = scala.collection.immutable.Queue[Int]()
Добавить элемент в неизменяемую очередь можно с помощью метода enqueue
:
val has1 = empty.enqueue(1) // Queue(1)
Чтобы добавить в очередь сразу несколько элементов, следует вызвать метод enqueueAll с коллекцией в качестве его аргументов:
val has123 = has1.enqueueAll(List(2, 3)) // Queue(1, 2, 3)
Чтобы удалить элемент из начала очереди, следует воспользоваться методом dequeue
:
scala> val (element, has23) = has123.dequeue val element: Int = 1
has23: scala.collection.immutable.Queue[Int] = Queue(2, 3)
Обратите внимание: dequeue возвращает пару, состоящую из удаленного элемента и оставшейся части очереди.
24 .7 . Конкретные классы неизменяемых коллекций 543
Диапазоны
Диапазон представляет собой упорядоченную последовательность целых чисел с одинаковым интервалом между ними. Например, 1, 2, 3 является диапазоном точно так же, как и 5, 8, 11, 14. Чтобы создать в Scala диапазон, следует воспользоваться предопределенными методами to и by
. Рассмотрим несколько примеров:
1 to 3 // Range(1, 2, 3)
5 to 14 by 3 // Range(5, 8, 11, 14)
Если нужно создать диапазон, исключая его верхний предел, то следует вместо метода to воспользоваться методомпомощником until
:
1 until 3 // Range(1, 2)
Диапазоны представлены постоянным объемом памяти, поскольку могут быть определены всего тремя числами: их началом, их концом и значением шага. Благодаря такому представлению большинство операций над диапа
зонами выполняется очень быстро.
Сжатые коллекции HAMT
Хешизвлечения (hashtries
1
) — стандартный способ эффективной реали
зации неизменяемых множеств и отображений. Сжатые коллекции HAMT
(хешмассивы сопоставленных префиксных деревьев)
2
— разновидность хешизвлечений в
JVM
, которая оптимизирует размещение элементов и сле
дит за тем, чтобы деревья были представлены каноничным и компактным образом.
Их представление похоже на векторы тем, что в них также используются деревья, где у каждого узла имеется 32 элемента, или 32 поддерева, но вы
бор осуществляется на основе хешкода. Например, самые младшие пять разрядов хешкода ключа используются в целях поиска заданного ключа в отображении для выбора первого поддерева, следующие пять — для вы
бора следующего поддерева и т. д. Процесс выбора прекращается, как только
1
Trie происходит от слова retrieval (извлечение) и произносится как «три» или
«трай».
2
Steindorfer M. J., Vinju J. J. Optimizing hasharray mapped tries for fast and lean immutable JVM collections // ACM SIGPLAN Notices, volume 50, pages 783–800.
ACM, 2015.
544 Глава 24 • Углубленное изучение коллекций у всех элементов, хранящихся в узле, будет хешкод, отличающийся от всех остальных в разрядах, выбранных до сих пор. Таким образом, не обязательно используются все разряды хешкода.
Хешизвлечения позволяют достичь хорошего баланса между достаточно быстрым поиском и достаточно эффективными функциональными встав
ками (
+
) и удалениями (
-
). Именно поэтому в Scala они положены в основу реализаций по умолчанию неизменяемых отображений и множеств. Факти
чески для неизменяемых множеств и отображений, содержащих менее пяти элементов, в Scala предусматривается дальнейшая оптимизация. Множества и отображения, содержащие от одного до четырех элементов, хранятся как отдельные объекты, содержащие эти элементы (или в случае с отображения
ми — пары «ключ — значение») в виде полей. Пустое изменяемое множество и пустое изменяемое отображение во всех случаях являются объектами
одиночками — не нужно дублировать для них место хранения, поскольку пустые неизменяемые множество или отображение всегда будут оставаться пустыми.
Красно-черные деревья
Форма сбалансированных двоичных деревьев — красночерные деревья, в ко
торых одни узлы обозначены как красные, а другие — как черные. Операции над ними, как и над любыми другими сбалансированными двоичными дере
вьями, гарантированно завершаются за время, экспоненциально зависящее от размера дерева.
Scala предоставляет реализации множеств и отображений, во внутреннем представлении которых используется красночерное дерево. Доступ к ним осуществляется по именам
TreeSet и
TreeMap
:
val set = collection.immutable.TreeSet.empty[Int]
set + 1 + 3 + 3 // TreeSet(1, 3)
Кроме того, красночерные деревья в Scala — стандартный механизм реали
зации
SortedSet
, поскольку предоставляют весьма эффективный итератор, возвращающий все элементы множества в отсортированном виде.
Неизменяемые битовые множества
Битовое множество представляет коллекцию небольших целых чисел в виде битов большего целого числа. Например, битовое множество, содержащее
24 .7 . Конкретные классы неизменяемых коллекций 545
3, 2 и 0, будет представлено в двоичном виде как целое число 1101, которое в десятичном виде является числом 13.
Во внутреннем представлении битовые множества используют массив из
64разрядных
Long
значений. Первое
Long
значение в массиве предназначе
но для целых чисел от 0 до 63, второе — от 64 до 127 и т. д. Таким образом, битовые множества очень компактны, поскольку самое большое целое число в множестве чуть меньше нескольких сотен или около того.
Операции над битовыми множествами выполняются очень быстро. Про
верка на присутствие занимает постоянное время. На добавление элемента в множество уходит время, пропорциональное количеству
Long
значений в массиве битового множества, которых обычно немного. Некоторые при
меры использования битового множества выглядят следующим образом:
val bits = scala.collection.immutable.BitSet.empty val moreBits = bits + 3 + 4 + 4 // BitSet(3, 4)
moreBits(3) // true moreBits(0) // false
Векторные отображения
VectorMap
(векторное отображение) представляет отображение с использова
нием как вектора (для ключей), так и
HashMap
. Оно предоставляет итератор, возвращающий все элементы в том порядке, в котором они были вставлены.
import scala.collection.immutable.VectorMap val vm = VectorMap.empty[Int, String]
val vm1 = vm + (1 –> "one") // VectorMap(1 –> one)
val vm2 = vm1 + (2 –> "two") // VectorMap(1 –> one, 2 –> two)
vm2 == Map(2 –> "two", 1 –> "one") // true
В первых строчках видно, что содержимое
VectorMap сохраняет порядок вставки, а последняя строчка показывает, что векторные отображения мож
но сравнивать с другими видами отображений и в ходе этого сравнения не учитывается порядок следования элементов.
Списочные отображения
Списочное отображение представляет собой отображение в виде связанно
го списка пар «ключ — значение». Как правило, операции над списочными отображениями могут потребовать перебора всего списка. В связи с этим такие операции занимают время, пропорциональное размеру отображения.
546 Глава 24 • Углубленное изучение коллекций
Списочные отображения не нашли в Scala широкого применения, посколь
ку работа со стандартными неизменяемыми отображениями почти всегда выполняется быстрее. Единственно возможное положительное отличие наблюдается в том случае, если по какойто причине отображение сконстру
ировано так, что первые элементы списка выбираются гораздо чаще других элементов.
val map = collection.immutable.ListMap(1 –> "one", 2 –> "two")
map(2) // "two"
24 .8 . Конкретные классы изменяемых коллекций
Изучив наиболее востребованные из имеющихся в стандартной библиотеке
Scala классы неизменяемых коллекций, рассмотрим классы изменяемых коллекций.
Буферы массивов
Буферы массивов уже встречались нам в разделе 15.1. В таком буфере со
держатся массив и его размер. Большинство операций над буферами мас
сивов выполняются с такой же скоростью, что и над массивами, поскольку эти операции просто обращаются к обрабатываемому массиву и вносят в него изменения. Кроме того, у буфера массива могут быть данные, весьма эффективно добавляемые в его конец. Добавление записи в буфер массива занимает амортизированно постоянное время. Из этого следует, что буферы массивов хорошо подходят для эффективного создания больших коллекций, когда новые элементы всегда добавляются в конец. Вот как выглядят неко
торые примеры их применения:
val buf = collection.mutable.ArrayBuffer.empty[Int]
buf += 1 // ArrayBuffer(1)
buf += 10 // ArrayBuffer(1, 10)
buf.toArray // Array(1, 10)
Буферы списков
В разделе 15.1 нам встречались и буферы списков. Буфер списка похож на буфер массива, за исключением того, что внутри него используется не мас
сив, а связанный список. Если сразу после создания буфер предполагается
24 .8 . Конкретные классы изменяемых коллекций 547
превращать в список, то лучше воспользоваться буфером списка, а не буфе
ром массива. Вот как выглядит соответствующий пример
1
:
val buf = collection.mutable.ListBuffer.empty[Int]
buf += 1 // ListBuffer(1)
buf += 10 // ListBuffer(1, 10)
buf.toList // List(1, 10)
Построители строк
По аналогии с тем, что буфер массива используется для создания массивов, а буфер списка — для создания списков, построитель строк применяется для создания строк. Построители строк используются настолько часто, что за
ранее импортируются в пространство имен по умолчанию. Создать их можно с помощью простого выражения new
StringBuilder
:
val buf = new StringBuilder buf += 'a' // a buf ++= "bcdef" // abcdef buf.toString // abcdef
ArrayDeque
ArrayDeque
— изменяемая последовательность, которая поддерживает эф
фективное добавление элементов в начало и в конец. Внутри она использует массив изменяемого размера. Если вам нужно вставлять элементы в начало или в конец буфера, то используйте
ArrayDeque вместо
ArrayBuffer
Очереди
Наряду с неизменяемыми очередями в Scala есть и изменяемые. Использу
ются они аналогично, но для добавления элементов вместо enqueue приме
няются операторы
+=
и
++=
. Кроме того, метод dequeue будет просто удалять из изменяемой очереди головной элемент и возвращать его. Примеры ис
пользования даны ниже:
val queue = new scala.collection.mutable.Queue[String]
queue += "a" // Queue(a)
1
Появляющийся в ответах интерпретатора в этом и в нескольких других примерах раздела buf.type является синглтонтипом. Как сказано в разделе 7.6, buf.type означает, что переменная содержит именно тот объект, на который указывает buf
1 ... 53 54 55 56 57 58 59 60 ... 64