Главная страница
Навигация по странице:

  • Листинг 15.1.

  • Таблица 15.1.

  • Таблица 15.2.

  • Scala. Профессиональное программирование 2022. Одерски Мартин, Спун Лекс, Веннерс Билл, Соммерс ФрэнкО41 Scala. Профессиональное программирование. 5е изд спб. Питер, 2022. 608 с. ил. Серия Библиотека программиста


    Скачать 6.24 Mb.
    НазваниеОдерски Мартин, Спун Лекс, Веннерс Билл, Соммерс ФрэнкО41 Scala. Профессиональное программирование. 5е изд спб. Питер, 2022. 608 с. ил. Серия Библиотека программиста
    Дата27.04.2023
    Размер6.24 Mb.
    Формат файлаpdf
    Имя файлаScala. Профессиональное программирование 2022.pdf
    ТипДокументы
    #1094967
    страница36 из 64
    1   ...   32   33   34   35   36   37   38   39   ...   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()
    )

    340 Глава 15 • Работа с другими коллекциями
    Что используется
    Что этот метод делает
    words ++= List("one" –> 1,
    "two" –> 2, "three" –> 3)
    Добавляет записи в изменяемое отображение
    (
    words.toString возвращает
    Map(one –> 1, two –> 2, three –> 3)
    )
    words ––= List("one",
    "two")
    Удаляет несколько объектов (
    words.toString воз­
    вращает
    Map(three –> 3)
    )
    Множества и отображения, используемые по умолчанию
    Для большинства случаев реализаций изменяемых и неизменяемых мно­
    жеств и отображений, предоставляемых
    Set()
    , scala.collection.mutab- le.Map()
    и тому подобными фабриками, наверное, вполне достаточно.
    Реализации, предоставляемые этими фабриками, используют алгоритм ускоренного поиска, в котором обычно задействуется хеш­таблица, поэтому они могут быстро обнаружить наличие или отсутствие объекта в коллекции.
    Так, фабричный метод scala.collection.mutable.Set()
    возвращает sca- la.col lection.mutable.HashSet
    , внутри которого используется хеш­таблица.
    Аналогично этому фабричный метод scala.collection.mutable.Map()
    воз­
    вращает scala.collection.mutable.HashMap
    История с неизменяемыми множествами и отображениями несколько сложнее. Как показано в табл. 15.3, класс, возвращаемый фабричным методом scala.collection.immutable.Set()
    , зависит, к примеру, от того, сколько элементов ему было передано. В целях достижения максималь­
    ной производительности для множеств, состоящих не более чем из пяти элементов, применяется специальный класс. Но при запросе множества из пяти и более элементов фабричный метод вернет реализацию, исполь­
    зующую хеш.
    По аналогии с этим, как следует из данной таблицы, в результате выполне­
    ния фабричного метода scala.collection.immutable.Map()
    будет возвращен нужный класс в зависимости от того, сколько пар «ключ — значение» ему передано. Как и в случае с множествами, для того чтобы неизменяемые ото­
    бражения с количеством элементов меньше пяти достигли максимальной производительности для отображения каждого конкретного размера, ис­
    пользуется специальный класс. Но если отображение содержит пять и более пар «ключ — значение», то используется неизменяемый класс
    HashMap
    1   ...   32   33   34   35   36   37   38   39   ...   64


    написать администратору сайта