Файл: Одерски Мартин, Спун Лекс, Веннерс Билл, Соммерс ФрэнкО41 Scala. Профессиональное программирование. 5е изд спб. Питер, 2022. 608 с. ил. Серия Библиотека программиста.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 09.12.2023
Просмотров: 779
Скачиваний: 11
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
1 ... 31 32 33 34 35 36 37 38 ... 64
332 Глава 15 • Работа с другими коллекциями
Краткий пример, показывающий способ инициализации списка и получения доступа к его голове и хвосту, выглядит так:
val colors = List("red", "blue", "green")
colors.head // red colors.tail // List(blue, green)
Чтобы освежить в памяти сведения о списках, обратитесь к шагу 8 в главе 3.
А подробности использования списков можно найти в главе 14.
Массивы
Массивы позволяют хранить последовательность элементов и оперативно обращаться к элементу, находящемуся в произвольной позиции, чтобы либо получить его, либо обновить; для этого используется индекс, отсчитываемый от нуля. Массив известной длины, для которого пока неизвестны значения элементов, создается следующим образом:
val fiveInts = new Array[Int](5) // Array(0, 0, 0, 0, 0)
А вот как инициализируется массив, когда значения элементов известны:
val fiveToOne = Array(5, 4, 3, 2, 1) // Array(5, 4, 3, 2, 1)
Как уже упоминалось, получить доступ к элементам массивов в Scala можно, указав индекс в круглых, а не в квадратных, как в Java, скобках. Рассмотрим пример доступа к элементу массива и обновления элемента:
fiveInts(0) = fiveToOne(4)
fiveInts // Array(1, 0, 0, 0, 0)
Массивы в Scala представлены точно так же, как массивы в Java. Поэтому можно абсолютно свободно использовать имеющиеся в Java методы, воз
вращающие массивы
1
В предыдущих главах действия с массивами встречались уже много раз.
Основы этих действий были рассмотрены в шаге 7 главы 3. Ряд примеров поэлементного обхода массивов с помощью выражения for был показан в разделе 7.3.
1
Разница вариантности массивов в Scala и в Java — то есть является ли
Array[String]
подтипом
Array[AnyRef]
— будет рассмотрена в разделе 18.3.
15 .1 . Последовательности 333
Буферы списков
Класс
List предоставляет быстрый доступ к голове и хвосту списка, но не к его концу. Таким образом, при необходимости построить список с добав
лением элементов в конец следует рассматривать возможность построить список в обратном порядке путем добавления элементов спереди. Затем, когда это будет сделано, нужно вызвать метод реверсирования reverse
, чтобы получить элементы в требуемом порядке.
Другой вариант, который позволяет избежать реверсирования, — исполь
зовать объект
ListBuffer
. Это содержащийся в пакете scala.collecti- on.mutable изменяемый объект, который может помочь более эффективно строить списки, когда нужно добавлять элементы в их конец. Объект обе
спечивает постоянное время выполнения операций добавления элементов как в конец, так и в начало списка. В конец списка элемент добавляется с помощью оператора
+=
1
, а в начало — с помощью оператора
+=:
. Когда по
строение будет завершено, можно получить список типа
List
, вызвав в от
ношении
ListBuffer метод toList
. Соответствующий пример выглядит так:
import scala.collection.mutable.ListBuffer val buf = new ListBuffer[Int]
buf += 1 // ListBuffer(1)
buf += 2 // ListBuffer(1, 2)
3 +=: buf // ListBuffer(3, 1, 2)
buf.toList // List(3, 1, 2)
Еще один повод использовать
ListBuffer вместо
List
— возможность предот
вратить потенциальное переполнение стека. Если можно создать список в нужном порядке, добавив элементы в его начало, но рекурсивный алгоритм, который потребуется, не является алгоритмом с хвостовой рекурсией, то вме
сто этого можно задействовать выражение for или цикл while и
ListBuffer
Буферы массивов
Объект
ArrayBuffer похож на массив, за исключением того, что в дополнение ко всему здесь предоставляет возможность добавлять и удалять элементы в начало и в конец последовательности. Доступны все те же операции, что и в классе
Array
, хотя выполняются они несколько медленнее, посколь
ку в реализации есть уровеньоболочка. На новые операции добавления
1
Операторы
+=
и
+=:
являются псевдонимами для append и prepend соответственно.
334 Глава 15 • Работа с другими коллекциями и удаления затрачивается в среднем одно и то же время, но иногда требуется время, пропорциональное размеру, изза реализации, требующей выделить новый массив для хранения содержимого буфера.
Чтобы воспользоваться
ArrayBuffer
, нужно сначала импортировать его из пакета изменяемых коллекций:
import scala.collection.mutable.ArrayBuffer
При создании
ArrayBuffer нужно указать параметр типа, а длину указывать не обязательно. По мере надобности
ArrayBuffer автоматически установит выделяемое пространство памяти:
val buf = new ArrayBuffer[Int]()
Добавить элемент в
ArrayBuffer можно с помощью метода
+=
:
buf += 12 // ArrayBuffer(12)
buf += 15 // ArrayBuffer(12, 15)
Доступны все обычные методы работы с массивами. Например, можно за
просить у
ArrayBuffer его длину или извлечь элемент по его индексу:
buf.length // 2
buf(0) // 12
Строки (реализуемые через StringOps)
Еще одной последовательностью, заслуживающей упоминания, является
StringOps
. В ней реализованы многие методы работы с последовательностя
ми. Поскольку в
Predef есть неявное преобразование из
String в
StringOps
, то с любой строкой можно работать как с последовательностью. Вот пример:
def hasUpperCase(s: String) = s.exists(_.isUpper)
hasUpperCase("Robert Frost") // true hasUpperCase("e e cummings") // false
В этом примере метод exists вызывается в отношении строки, которая в теле метода hasUpperCase называется s
. В самом классе
String не объявлено ника
кого метода по имени exists
, поэтому компилятор Scala выполнит неявное преобразование s
в
StringOps
, где такой метод есть. Метод exists считает строку последовательностью символов и вернет значение true
, если какой
либо из них относится к верхнему регистру
1 1
Подобный пример представлен на с. 49.
15 .2 . Множества и отображения 335
15 .2 . Множества и отображения
В предыдущих главах, начиная с шага 10 в главе 3, уже были показаны ос
новы множеств и отображений. Прочитав этот раздел, вы получите более глубокое представление о способах их использования и увидите несколько дополнительных примеров.
Ранее мы уже говорили, что библиотека коллекций Scala предлагает как изменяемые, так и неизменяемые версии множеств и отображений. Иерар
хия множеств показана на рис. 3.2 (см. с. 80), а иерархия отображений — на рис. 3.3 (см. с. 82). Из этих схем следует, что простые имена
Set и
Map исполь
зуются тремя трейтами и все они находятся в разных пакетах.
По умолчанию, когда в коде используется
Set или
Map
, вы получаете не
изменяемый объект. Если нужен изменяемый вариант, то следует приме
нить явно указанное импортирование. К неизменяемым вариантам Scala предоставляет самый простой доступ — в качестве небольшого поощрения за то, что предпочтение отдано им, а не их изменяемым аналогам. Доступ предоставляется через объект
Predef
, неявно импортируемый в каждый файл исходного кода на языке Scala. Соответствующие определения по
казаны в листинге 15.1.
Листинг 15.1. Исходные определения отображений map и множеств в set в Predef object Predef:
type Map[A, +B] = collection.immutable.Map[A, B]
type Set[A] = collection.immutable.Set[A]
val Map = collection.immutable.Map val Set = collection.immutable.Set
// ...
end Predef
Имена
Set и
Map в качестве псевдонимов для более длинных полных имен трейтов неизменяемых множеств и отображений в
Predef определяются с помощью ключевого слова type
1
. Чтобы ссылаться на объектыодиночки для неизменяемых
Set и
Map
, выполняется инициализация val
переменных с именами
Set и
Map
. Следовательно,
Map является тем же, что и объект
Predef.Map
, который определен быть тем же самым, что и scala.collecti- on.im mutable.Map
. Это справедливо как для типа
Map
, так и для объекта
Map
Если нужно воспользоваться как изменяемыми, так и неизменяемыми множествами или отображениями в одном и том же исходном файле, то
1
Более подробно ключевое слово type мы рассмотрим в разделе 20.6.
336 Глава 15 • Работа с другими коллекциями рекомендуемым подходом является импортирование имен пакетов, содер
жащих изменяемые варианты:
import scala.collection.mutable
Можно продолжать ссылаться на неизменяемое множество, как и прежде
Set
, но теперь можно будет сослаться и на изменяемое множество, указав mutable.Set
. Вот как выглядит соответствующий пример:
val mutaSet = mutable.Set(1, 2, 3)
Использование множеств
Ключевой характеристикой множества является то, что оно гарантирует наличие каждого объекта не более чем в одном экземпляре, по определению оператора
==
. В качестве примера воспользуемся множеством, чтобы вы
числить количество уникальных слов в строке.
Если указать в качестве разделителей слов пробелы и знаки пунктуации, то метод split класса
String может разбить строку на слова. Для этого вполне достаточно применить регулярное выражение
[
!,.]+
: оно показывает, что строка должна быть разбита во всех местах, где есть один или несколько пробелов и/или знак пунктуации:
val text = "See Spot run. Run, Spot. Run!"
val wordsArray = text.split("[ !,.]+")
// Array(See, Spot, run, Run, Spot, Run)
Чтобы посчитать уникальные слова, их можно преобразовать, приведя их символы к единому регистру, а затем добавить в множество. Поскольку мно
жества исключают дубликаты, то каждое уникальное слово будет появляться в множестве только раз.
Сначала можно создать пустое множество, используя метод empty
, предо
ставляемый объектомкомпаньоном
Set
:
val words = mutable.Set.empty[String]
Далее, просто перебирая слова с помощью выражения for
, можно преоб
разовать каждое слово, приведя его символы к нижнему регистру, а затем добавить его в изменяемое множество, воспользовавшись оператором
+=
:
for word <- wordsArray do words += word.toLowerCase words // Set(see, run, spot)
15 .2 . Множества и отображения 337
Таким образом, в тексте содержится три уникальных слова: spot
, run и see
Наиболее часто используемые методы, применяемые равно к изменяемым и неизменяемым множествам, показаны в табл. 15.1.
Таблица 15.1. Наиболее распространенные операторы для работы с множествами
Что используется
Что этот метод делает
val nums = Set(1, 2, 3)
Создает неизменяемое множество
(
nums.toString возвращает
Set(1,
2,
3)
)
nums + 5
Добавляет элемент в неизменяемое множе
ство (возвращает
Set(1,
2,
3,
5)
)
nums — 3
Удаляет элемент из неизменяемого множе
ства (возвращает
Set(1,
2)
)
nums ++ List(5, 6)
Добавляет несколько элементов
(возвращает
Set(1,
2,
3,
5,
6)
)
nums –– List(1, 2)
Удаляет несколько элементов из неизменяе
мого множества (возвращает
Set(3)
)
nums & Set(1, 3, 5, 7)
Выполняет пересечение двух множеств
(возвращает
Set(1,
3)
)
nums.size
Возвращает размер множества (возвраща
ет
3
)
nums.contains(3)
Проверка включения (возвращает true
)
import scala.collection.mutable
Упрощает доступ к изменяемым коллекциям val words = mutable.Set.
empty[String]
Создает пустое изменяемое множество
(
words.toString возвращает
Set()
)
words += "the"
Добавляет элемент (
words.toString возвра
щает
Set(the)
)
words –= "the"
Удаляет элемент, если он существует
(
words.toString возвращает
Set()
)
words ++= List("do", "re", "mi")
Добавляет несколько элементов
(
words.toString возвращает
Set(do, re, mi)
)
words ––= List("do", "re")
Удаляет несколько элементов
(
words.toString возвращает
Set(mi)
)
words.clear
Удаляет все элементы
(
words.toString возвращает
Set()
)
338 Глава 15 • Работа с другими коллекциями
Применение отображений
Отображения позволяют связать значение с каждым элементом мно
жества. Отображение и массив используются похожим образом, за ис
ключением того, что вместо индексирования с помощью целых чисел, начинающихся с нуля, можно применить ключи любого вида. Если им
портировать пакет с именем mutable
, то можно создать пустое изменяемое отображение:
val map = mutable.Map.empty[String, Int]
Учтите, что при создании отображения следует указать два типа. Первый тип предназначен для ключей отображения, а второй — для их значений. В данном случае ключами являются строки, а значениями — целые числа. Задание за
писей в отображении похоже на задание записей в массиве:
map("hello") = 1
map("there") = 2
map // Map(hello > 1, there > 2)
По аналогии с этим чтение отображения похоже на чтение массива:
map("hello") // 1
Чтобы связать все воедино, рассмотрим метод, подсчитывающий количество появлений каждого из слов в строке:
def countWords(text: String) =
val counts = mutable.Map.empty[String, Int]
for rawWord <- text.split("[ ,!.]+") do val word = rawWord.toLowerCase val oldCount =
if counts.contains(word) then counts(word)
else 0
counts += (word –> (oldCount + 1))
counts countWords("See Spot run! Run, Spot. Run!")
// Map(spot –> 2, see –> 1, run –> 3)
Этот код работает благодаря тому, что используется изменяемое отображе
ние по имени counts и каждое слово отображается на количество его появ
лений в тексте. Для каждого слова в тексте выполняется поиск предыдущего количества появлений слова и его увеличение на единицу, а затем в counts сохраняется новое значение количества. Обратите внимание: проверка того, встречалось ли это слово раньше, выполняется с помощью метода contains
15 .2 . Множества и отображения 339
Если counts.contains(word)
не возвращает true
, значит, слово еще не встре
чалось и за количество принимается ноль.
Многие из наиболее часто используемых методов работы как с изменяемы
ми, так и с неизменяемыми отображениями показаны в табл. 15.2.
Таблица 15.2. Наиболее часто используемые операции для работы с отображениями
Что используется
Что этот метод делает
val nums = Map("i" –> 1,
"ii" –> 2)
Создает неизменяемое отображение (
nums.
toString возвращает
Map(i –> 1, ii –> 2)
)
nums + ("vi" –> 6)
Добавляет запись в неизменяемое отображение
(возвращает
Map(i –> 1, ii –> 2, vi –> 6)
)
nums — "ii"
Удаляет запись из неизменяемого отображения
(возвращает
Map(i –> 1)
)
nums ++ List("iii" –> 3,
"v" –> 5)
Добавляет несколько записей (возвращает
Map(i –> 1, ii –> 2, iii –> 3, v –> 5)
)
nums –– List("i", "ii")
Удаляет несколько записей из неизменяемого отображения (возвращает
Map()
)
nums.size
Возвращает размер отображения (возвращает
2
)
nums.contains("ii")
Проверяет на включение (возвращает true
)
nums("ii")
Извлекает значение по указанному ключу (воз
вращает
2
)
nums.keys
Возвращает ключи (возвращает результат итера
ции, выполненной над строками "i"
и "ii"
)
nums.keySet
Возвращает ключи в виде множества (возвраща
ет
Set(i,
ii)
)
nums.values
Возвращает значения (возвращает
Iterable над целыми числами
1
и
2
)
nums.isEmpty
Показывает, является ли отображение пустым
(возвращает false
)
import scala.collection.
mutable
Упрощает доступ к изменяемым коллекциям val words = mutable.Map.
empty[String, Int]
Создает пустое изменяемое отображение words += ("one" –> 1)
Добавляет запись в отображение из ключа "one"
и значения
1
(
words.toString возвращает
Map(one –> 1)
)
words –= "one"
Удаляет запись из отображения, если она суще
ствует (
words.toString возвращает
Map()
)