fbpx

Blog

Отличия Sequence API vs Collection

Вероятно, вы когда-нибудь сталкивались с Sequence API в Kotlin. Возможно, вы слышали, что они могут обрабатывать данные более эффективно, чем обычные коллекции. Но задумывались ли вы когда-нибудь, что именно они делают, как достигают эффективности или когда их следует использовать? В этой статье мы рассмотрим примеры, которые помогут нам визуализировать разницу между Sequence API и обычными коллекциями. Затем в следующих статьях мы заглянем внутрь Sequence API, раскроем некоторые интересные характеристики. Чтобы не пропустить следующие статьи на тему Android и мобильной разработки подписывайтесь на Telegram-канал

Для визуализации того как работают Sequence API в Kotlin нам понадобится надеть защитную каску и отправиться на завод, завод по производству карандашей.

«Слушайте, команда!» начала Марина, менеджер. «Как многие из вас знают, наш последний продукт называется «Набор огненных карандашей». Это небольшая коробочка с цветными карандашами, в которой есть только те цвета, которые обычно ассоциируются у вас с огнем, например красный, оранжевый и желтый. Спрос действительно растет, поэтому нам нужно найти способы быстрее их обрабатывать.

«Вот как это работает сегодня: мы начинаем с большой коробки немаркированных карандашей разных цветов, а в итоге должны получить карандаши, которые имеют:

  • нужный нам “огненный” цвет
  • на них есть этикетки
  • в каждой коробке должно быть 5 штук

При этом она представила диаграмму, показывающую, как в настоящее время обрабатываются карандаши:

Как можно заметить процесс не самый эффективный. Возможно можно что-то придумать.

Пока наши друзья на фабрике карандашей обдумывают этот процесс, давайте напишем его на Kotlin! Мы начнем с простого класса данных, представляющего карандаш. Свойство label будет иметь значение NULL, поскольку карандаши начинаются без них и помечаются позже в процессе.

Далее мы создадим нашу большую коробку c карандашами с множеством цветов.

И, наконец, сделаем небольшое множество, которое будет содержать именно те цвета, которые по требованиям разрешено включать в набор «Огненный карандаш»

И теперь мы готовы приступить к обработке этих карандашей! Помните, алгоритм был такой:

  1. Начните с большой коробки
  2. Отфильтруйте цвета, которые не подходят
  3. Наклейте этикетки на каждый карандаш
  4. Возьмите только первые пять.
  5. Соберите их в последнюю коробку.

Благодаря операторам в стандартной библиотеке Kotlin мы можем смоделировать этот процесс всего за пять строк:

Когда вы запустите этот код, вы получите набор объектов Crayon, составляющих набор Fire. Вот как приведенный выше код соотносится с этапами процесса.

Такие функции, как filter(), map() и take(), часто называют операциями работы с коллекцией. Каждый из них работает с коллекцией, применяя к ней какое-то преобразование для создания другой коллекции.

Collection operations in our code include filter(), map(), take(), and toSet().

В итоге когда мы применяем несколько операторов, у нас получается цепочка из операторов коллекции:

The collection operation chain includes the combination of filter(), map(), take(), and toSet().

Как видите, этот код очень декларативен — нам не пришлось вручную создавать экземпляры объектов коллекции или перебирать элементы. Вместо этого мы просто объявили то, что хотели, а стандартная библиотека Kotlin сделала все остальное! Однако, как обнаружили наши друзья на фабрике по производству карандашей, на самом деле здесь происходит много отходов. Да, они начали с большой коробки и закончили коробкой с огненным набором, но в середине они потратили три коробки. Аналогичным образом, если бы вы заглянули в реализацию этих операций сбора, вы бы увидели, что объекты ArrayList (то есть «ящики» для наших карандашей) создаются и удаляются на каждом из этих этапов обработки — filter(), map( ), и take().

Code with lines indicating where the wasted ArrayList instances are created.

Перенос данных из одной коллекции в другую может оказаться дорогостоящим! В таком небольшом примере, как наш, мы можем не заметить, что потраченные впустую экземпляры ArrayList влияют на производительность. Но чем больше набор данных и чем больше операций по сбору данных выполняется, тем больший штраф мы, скорее всего, испытаем! Должен быть более эффективный способ обработки карандашей! Посмотрим, что думают наши друзья на фабрике…

Возвращаемся на фабрику

Через некоторое время Марина, как типичный менеджер снова заговорила. «Итак, команда, как мы можем сделать этот процесс более эффективным?» Никита, последний сотрудник, сказал: «Вот идея: что, если мы проделаем все необходимые операции с одним карандашом, прежде чем перейти к следующему?» Он развернул новый лист и нарисовал вот такую новую диаграмму:

Проделаем все необходимые операции с одним карандашом, прежде чем перейти к следующему

Никита продолжил: «При таком подходе, когда цвет правильный, мы наклеиваем этикетку на карандаш, кладем его в последнюю коробку и переходим к следующему карандашу. Как только в коробке окажется 5 карандашей, останавливаемся. Другими словами, новый процесс будет проходить так…» Он вытащил маркер и очертил границы каждой итерации:

Никита заключил: «Такой подход избавляет нас от всех этих лишних коробок между шагами. Кроме того, мы никогда не фильтруем и не упаковываем карандаши, которые все равно не поместятся в окончательную коробку». Чувствуя уверенность в переменах, Марина заявила: «Ну, на этом все решено! Давайте воплотим этот план в жизнь!» И действительно, изменив процесс так, чтобы операции применялись только к одному цветному карандашу за раз, коробки не тратились зря, обрабатывались только необходимые карандаши, и наша фабрика наконец смогла удовлетворить спрос покупателей!

Программируем изменения

Как мы видели на фабрике, мы можем повысить эффективность процесса изготовления карандашей, обновив конвейер так, чтобы все необходимые операции применялись только к одному цветному карандашу, прежде чем переходить к следующему. Чтобы заставить Kotlin использовать новый способ, предложенный Никитой вместо старого, все, что нам нужно сделать, это внести одно простое изменение:

Вызов метода asSequence() меняет способ работы как показано выше на диаграмме

Чтобы обрабатывать большой набор карандашей более эффективно, все, что нам нужно было сделать, это вызвать asSequence() для BigBoxOfCrayons перед выполнением над ним операций! Вот как этот код отображается на диаграмме:

The efficient crayon process, indicating the filter(), map(), take() and toSet() sections.

Очень важно понимать, что в цепочке операторов Sequence операторы применяются к отдельным элементам в другом порядке, чем в стандартной цепочке операторов для коллекции. Чтобы визуализировать и понять эту разницу, давайте посмотрим еще на один набор диаграмм. На этих диаграммах будет изображена та же цепочка filter(), map() и take(), которую мы использовали все время. Для справки, вот еще раз код:

Теперь давайте расположим операции с каждым карандашом в сетке, где цвета карандашей расположены по горизонтальной оси, а операции — по вертикальной оси. Цвет кружков соответствует цвету обрабатываемого карандаша. Буква в круге обозначает операцию, выполняемую с этим карандашом. Линия, проходящая через круги, представляет порядок выполнения операций над каждым из этих элементов.

Diagrams comparing the order of operations for a collection operation chain versus a sequence operation chain.

Если вы проведете пальцем по этим линиям, вы заметите, что: В цепочке операторов для коллекции буквы группируются — строка проходит через все «F», затем все «М», а затем все «Т». Другими словами, происходит фильтрация, затем происходит работа оператора map(), а затем срабатывает оператор take(). С другой стороны, в примере где указана цепочка операторов для Sequence цвета группируются вместе: линия проходит через все оранжевые кружочки, затем все лаймовые кружочки, затем все красные кружочки и так далее. Это означает, что каждый карандаш полностью обрабатывается, прежде чем перейти к следующему.

Вы, вероятно, также заметили, что в цепочке с Sequence выполняется меньше отдельных операций — 18 по сравнению с 31 — потому что обработка останавливается, как только ящик заполняется!

Итак, что такое Sequence (последовательность) ?

Теперь, когда мы увидели, что делает последовательность, пришло время дать определение этому термину.

Последовательность — это итерируемый тип, с которым можно работать, не создавая ненужных промежуточных коллекций, применяя все указанные операторы над каждым элементом перед переходом к следующему.

Заключение

Поздравляем! Теперь у вас есть четкое понимание разницы между тем, как обрабатывают данные операторы коллекции, и тем, как обрабатываются данные операторами если используется Sequence. В следующей статье мы рассмотрим расширенные концепции Sequence, чтобы раскрыть некоторые интересные характеристики и несколько типичных ошибок.

Если вы хотите прокачать свои знания до middle-уровня, обратите внимание на интенсивный курс по Android-разработке с code-review и консультациями. <<<< 👉 Записаться>>>>

Полезные ссылки

https://kotlinlang.org/docs/reference/collections-overview.html

https://kotlinlang.org/docs/reference/sequences.html

Чтобы не пропустить новые интересные статьи на тему Android и мобильной разработки подписывайтесь на Telegram-канал

Материал статьи основан на https://typealias.com/guides/kotlin-sequences-illustrated-guide/