A Enumerator
- очень общий интерфейс, он делает только очень простые предположения о том, что «коллекция» проходит. В частности, он действительно поддерживает только две операции: получить текущий элемент и выполнить итерацию следующего элемента.
Учитывая эти две операции, если вы хотите получить 10-миллионный элемент, вы можете сделать только одно: повторить 10 миллионов раз. Это требует времени.
Нет такой вещи, как «нарезка» Enumerator
. Перечисляется Enumerator
. Вот и все.
Теперь, как вы обнаружили, есть еще одна проблема: операции коллекции Ruby не сохраняются в типе. Независимо от того, какую коллекцию вы вызываете map
или select
или take
или что бы там ни было, он всегда будет возвращать тот же тип: полностью реализованный, бетонный, строгий Array
. Вот как работают большинство фреймворков на большинстве языков, например. в .NET все операции сбора возвращаются IEnumerable
. Это связано с тем, что большинство этих методов имеют только одну общую реализацию в смесителе Enumerable
.
Smalltalk является исключением, но есть и другая проблема: операции сбора дублируются для каждого типа коллекции. У каждого типа коллекции есть своя почти индентическая копия & вставки вставки collect:
, select:
и т. Д. Это дублирование кода сложно поддерживать и накладывает большую нагрузку на всех, кто хочет интегрировать свою собственную коллекцию в каркас. В Ruby это легко: реализовать each
, mixin Enumerable
, и все готово.
Примечание: по состоянию на Ruby 1.9, есть на самом деле некоторые этого дублирования: Hash
реализует свою собственную версию select
, которая на самом деле возвращает Hash
и не Array
. Итак, теперь не только дублирование кода, но и асимметрия в интерфейсе: все реализации select
возвращают Array
с, за исключением одного в Hash
.
Рамка для коллекции Scala 2.8 - это первый раз, когда кто-то выяснил, как обеспечить операции сбора, сохраняющие тип, без дублирования кода. Но структура коллекции Ruby была разработана за 15 лет до Scala 2.8, поэтому она не может воспользоваться этими знаниями.
В Ruby 2.0 есть ленивые Enumerator
s, где все операции по сбору возвращают еще один ленивый Enumerator
. Но это вам не поможет: разница только в том, что ленивый Enumerator
задержит 10 миллионов итераций, пока вы не нажмете print
значений. Он по-прежнему должен выполнять эти 10 миллионов итераций, потому что просто нет способа сделать иначе.
Если вы хотите нарезку, вам нужна разрезаемая структура данных, такая как Array
.
ruby 2 будет иметь ленивых счетчиков :) http://www.ruby-doc.org/core-2.0/Enumerator/Lazy.html. Между тем, вы, вероятно, можете найти что-то в сторонних драгоценных камнях –
@SergioTulentsev Интересно. Все еще любопытно, действительно ли это ускоряет такие вещи, как мой пример. Я быстро взломал эквивалент в python, используя itertools, islice метод именно, и он занял около 1/10 раз времени запуска Ruby. AFAIK python не является ленивым языком. –
Вы пробовали Haskell? Это супер-ленивый язык :) –