2013-02-20 2 views
2

У меня возникают проблемы с получением фрагмента бесконечной последовательности Enumerator экземпляра в разумные сроки. Сначала я попробовал drop и take цепочку, но он повесился навсегда, так как drop попытался вернуть бесконечное Array. Затем я включил порядок этих методов, но я все равно придется ждать около десяти минут, чтобы получить 100 значений после десяти milionth образца:Получите кусочек счетчика эффективно

print exbioseq.drop(10**7).take(100) 

Можно ли что-нибудь сделать, чтобы получить кусок быстрее?

+2

ruby ​​2 будет иметь ленивых счетчиков :) http://www.ruby-doc.org/core-2.0/Enumerator/Lazy.html. Между тем, вы, вероятно, можете найти что-то в сторонних драгоценных камнях –

+1

@SergioTulentsev Интересно. Все еще любопытно, действительно ли это ускоряет такие вещи, как мой пример. Я быстро взломал эквивалент в python, используя itertools, islice метод именно, и он занял около 1/10 раз времени запуска Ruby. AFAIK python не является ленивым языком. –

+1

Вы пробовали Haskell? Это супер-ленивый язык :) –

ответ

2

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.

+0

Спасибо за всесторонний ответ. Как я упоминал в комментариях, переписывая код в python, используя itertool, т.е. использование функций генератора снизило время выполнения до 1/10. Похоже, класс Enumerator не слишком оптимизирован. –

+0

'Enumerator' основан на' Fiber's, т. Е. Coroutines. Это 10 миллионов переключателей контекста, которые в значительной степени составляют легкую нить. Генераторы Python не являются полными сопрограммами, они являются особыми случаями. Я бы предположил, что разработчики CPython используют этот специальный случай, чтобы сделать его более эффективным, чем реализация общей сопрограммы. –

+0

'Fiber' используются только при вызове 'Enumerator # next', что здесь не так. –

 Смежные вопросы

  • Нет связанных вопросов^_^