Отличие последовательности от коллекций Iterable

Последнее обновление: 04.03.2024

Отличие последовательности от коллекций Iterable

И последовательности, и коллекции, которые реализуют интерфейс Iterable, по сути представляют набор элементов. Более того предоставляют похожий набор операций для обработки элементов. Но отличие состоит, как эти операции обрабатывают элементы при применении сразу нескольких операций.

Так, при применении набора операций к коллекции Iterable каждая отдельная операция возвращает промежуточный результат - промежуточную коллекцию. А при обработке последовательности весь набор операций выполняется только тогда, когда требуется конечный результат обработки.

Также меняется порядок применения операций. Коллекция применяет каждую операцию последовательно к каждому элементу. То есть сначала выполняет первую операцию для всех элементов, потом вторую операцию для элементов коллекции, полученных после первой операции. И так далее.

Последовательность применяет весь набор операций отдельно к каждому элементу. То есть сначала весь набор операций применяется к первому элементу, потом ко второму элементу и так далее. Таким образом, последовательность позволяет избежать создания помежуточных коллекций и в тоже время повышают производительность при выполнении набора операций особенно для большого набора данных. Однако при небольших наборах данных и малом количестве операций может быть эффективнее использовать коллекции Iterable.

Рассмотрим на примере. Сначала возьмем коллекции Iterable (в данном случае List):

fun main(){

    var people = listOf(
        Person("Tom", 37),
        Person("Sam", 25),
        Person("Alice", 33)
    )
    people = people.filter { println("Age filter: ${it}"); it.age > 30 }
                    .filter{ println("Name filter: ${it}"); it.name.length == 3 }
    println("Result:")
    for(person in people) println(person)
}
data class Person(val name: String, val age: Int)

Здесь создается коллекция - список people (объект типа List), который содержит объекты Person. Далее к этому списку применяются две операции фильтрации в виде метода filter(). Сначала получаем все объекты Person, у которых свойство age больше 30:

people.filter { println("Age filter: ${it}"); it.age > 30 }

Эта операция filter() возвратит промежуточную коллекцию, которая содержит все объекты Person с возрастом больше 30. Для наглядности здесь логгируются на консоль объекты, к которым применяется операция.

Затем выполняется вторая операция filter() - она возвращает из промежуточной коллекции те объекты Person, у которых длина свойства name равна 3.

filter{ println("Name filter: ${it}"); it.name.length == 3 }

Опять же для наглядности здесь логгируются на консоль объекты, к которым применяется операция.

Результатом будет вторая коллекция, которая будет присвоена переменной people и которую в конце с помощью цикла foreach выводится на консоль. В итоге мы получим следующий консольный вывод:

Age filter: Person(name=Tom, age=37)
Age filter: Person(name=Sam, age=25)
Age filter: Person(name=Alice, age=33)
Name filter: Person(name=Tom, age=37)
Name filter: Person(name=Alice, age=33)
Result:
Person(name=Tom, age=37)

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

Теперь вместо коллекций применим последовательности:

fun main(){

    var people = sequenceOf(
        Person("Tom", 37),
        Person("Sam", 25),
        Person("Alice", 33)
    )
    people = people.filter { println("Age filter: ${it}"); it.age > 30 }
                    .filter{ println("Name filter: ${it}"); it.name.length == 3 }
    println("Result:")
    for(person in people) println(person)
}
data class Person(val name: String, val age: Int)

Здесь абсолютно такой же код, как и в предыдущем примере, только переменная people теперь представляет последовательность объектов Person. Однако консольный вывод будет совершенно иным:

Result:
Age filter: Person(name=Tom, age=37)
Name filter: Person(name=Tom, age=37)
Person(name=Tom, age=37)
Age filter: Person(name=Sam, age=25)
Age filter: Person(name=Alice, age=33)
Name filter: Person(name=Alice, age=33)

Во-первых, обратите внимание, что строка "Result:" выводится до выполнения всех операций. Потому что получение результата фактически происходит в цикле for при обращении к последовательности. До этого нет смысла выполнять операции, если элементы последовательности никак не используются.

Во-вторых, также обратите внимание на применение операций к элементам.

  1. Сначала обрабатывается первый элемент - Person(name=Tom, age=37). Поскольку он соответствует обоим фильтрам, то он в конечном счете выводится на консоль в цикле for.

  2. Далее обрабатывается второй элемент - Person(name=Sam, age=25), однако после применения первой операции filter его обработка завершается, поскольку он не соответствует условию первого фильтра

  3. В конце обрабатывается третий элемент - Person(name=Alice, age=33), к нему применяются две операции filter, но затем его обработка завершается, поскольку он не соответствует условию второго фильтра

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

fun main(){

    var people = sequenceOf(
        Person("Tom", 37),
        Person("Sam", 25),
        Person("Alice", 33)
    )
    people = people.filter { println("Age filter: ${it}"); it.age > 30 }
    println("Between Age filter and Name filter")
    people = people.filter{ println("Name filter: ${it}"); it.name.length == 3 }
    for(person in people) println(person)
}
data class Person(val name: String, val age: Int)

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

Between Age filter and Name filter
Age filter: Person(name=Tom, age=37)
Name filter: Person(name=Tom, age=37)
Person(name=Tom, age=37)
Age filter: Person(name=Sam, age=25)
Age filter: Person(name=Alice, age=33)
Name filter: Person(name=Alice, age=33)

Сокращение набора операций

Применение последовательностей может значительно сократить количество применяемых операций. Например:

fun main(){

    var people = listOf(
        Person("Tom", 37),
        Person("Sam", 25),
        Person("Alice", 33)
    )
    people = people.filter { println("Age filter: ${it}"); it.age > 30 }
                    .take(1)
    for(person in people) println(person)

}
data class Person(val name: String, val age: Int)

Здесь опять же к списку people применяется фильтр по возрасту и затем с помощью вызова take(1) выбираем один объект в результирующую коллекцию. И в этом случае мы получим следующий консольный вывод:

Age filter: Person(name=Tom, age=37)
Age filter: Person(name=Sam, age=25)
Age filter: Person(name=Alice, age=33)
Person(name=Tom, age=37)

Здесь опять же мы видим, что вызов filter() применяется ко всем элементам, из которых формируется промежуточная коллекция, из которой в итоге выбирается 1 объект.

Изменим тип набора на последовательность:

fun main(){

    var people = sequenceOf(
        Person("Tom", 37),
        Person("Sam", 25),
        Person("Alice", 33)
    )
    people = people.filter { println("Age filter: ${it}"); it.age > 30 }
                    .take(1)
    for(person in people) println(person)

}
data class Person(val name: String, val age: Int)

Консольный вывод программы:

Age filter: Person(name=Tom, age=37)
Person(name=Tom, age=37)

Сначала обрабатывается первый объект - Person(name=Tom, age=37) - к нему применяется вызов filter(). Поскольку этот объект соответствует фильтру, он переходит к примению вызова take(1). Этот вызов выбирает в результирующую коллекцию первый объект. Но поскольку результирующая коллекция должна содержать только 1 объект, то остальные элементы последовательности нет смысла обрабатывать. И на этом обработка последовательности закончилась. Таким образом, вместо 3 операций filter в данном случае мы получаем только 1. Соответственно на большем количестве данных и операций сокращение набора операций может быть более значительным.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850