Как создать подперечислитель с ограниченной областью действия?

Допустим, у меня есть коллекция из 100 элементов. Обычный счетчик будет перебирать эти 100 элементов.

Я хотел бы создать перечислитель (который основан на обычном перечислителе, т.е. он не для каждой коллекции, а скорее для одного общего подхода), область действия которого от хе-хе до туда - и я мог бы, например, перебрать более 20 элементов в только середина.

void foo(IEnumerable<int> coll)
{
   var regular_iter = coll.GetEnumerator();
   regular_iter.MoveNext();
   regular_iter.MoveNext();
   // ... 8 more
   var scoped_iter = new ScopeEnumerator(regular_iterator,20);

Поэтому в таком случае, когда я вызываю scoped_iter.Reset(), он сбрасывается до своего 0-го элемента (10-й для всей коллекции).

А также он видит только элементы от 10 до 30.

Вопрос в том -- как реализовать такой счетчик?

Редактировать

1.

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

2.

Джон спросил о фоне. Чего я действительно пытаюсь добиться, так это нарезать коллекцию (т.е. у вас есть, скажем, коллекция из 10 строк, но вы хотели бы интерпретировать ее как коллекцию из 5 элементов, каждый из которых представляет собой коллекцию из 2 строк). Наивный алгоритм довольно прост, но и очень неэффективен. С коллекцией ~16MB (список строк) я думал о другом подходе - просто переинтерпретировать данные, не копируя их. Поэтому я бы создал один итератор, который выбирает каждый элемент SIZE_OF_SLICE из всей коллекции, а также я бы создал этот итератор с ограниченной областью действия, который будет начинаться с первого итератора и переходить к элементам SIZE_OF_SLICE.

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

3

Я реализовал эффективную нарезку для IList (как только вы предполагаете, что у вас есть индексатор, это кусок пирога), но это беспокоит меня, вы не можете (?) Предоставить общий эффективный алгоритм как для списка (LinkedList), так и для массивов (List). Поэтому, если вы читаете это и у вас есть идея, как это сделать, не стесняйтесь отвечать даже через 10 лет (при условии, что C# все еще будет с нами).


person greenoldman    schedule 28.07.2011    source источник


Ответы (2)


Чтобы сделать это с минимальными усилиями, вы в основном заполняете коллекцию, которая действительно поддерживает Reset (например, List<T>) с помощью итератора, а затем возвращаете ее.

Немного сложнее сделать это лениво, то есть при первой итерации заполнить коллекцию. После первого сброса перейдите в режим «повтор». Я уверен, что это осуществимо - просто это будет немного сложно.

Было бы еще сложнее, если бы вам пришлось поддерживать сброс в первый раз только после (скажем) 15 элементов, а затем, когда вы нажмете 16-й элемент во второй раз, возвращаясь к исходному итератору. Угу.

Если вы можете точно определить, какие у вас есть требования, это может быть забавно реализовать...

РЕДАКТИРОВАТЬ: Просто чтобы добавить некоторые комментарии к этому ответу: вы не можете сделать это вообще без копирования данных, потому что нет гарантии, что итератор вообще будет поддерживать сброс. Представьте, что итератор предоставляет данные от какого-то генератора случайных чисел или это прямая трансляция, которая не записывается — очевидно, чтобы воспроизвести данные, что-то должно их скопировать.

Если вы имеете в виду конкретную исходную реализацию, это может быть иначе, но вы не можете сделать это через просто интерфейс IEnumerator<T>.

person Jon Skeet    schedule 28.07.2011
comment
Хм, я не совсем понял. Когда вы пишете, заполняете коллекцию, которую вы имеете в виду для итератора с ограниченной областью действия? Но это также означало бы копирование большого количества данных, потому что вы не можете заранее гарантировать, что элемент коллекции является просто классом (например). Мне нужно просто повторить, без копирования данных. Он должен быть максимально легким. - person greenoldman; 28.07.2011
comment
@macias: Тогда ты застрял. Исходный итератор может не поддерживать сброс, и даже если бы вы знали его исходное положение, чтобы перейти к нему при сбросе итератора с областью действия. - person Jon Skeet; 28.07.2011
comment
Это очень печальная новость. Это означает, что если у вас есть 1T данных и вы хотите просмотреть их как двумерный массив, вам придется скопировать этот 1T данных. Ой! Жду немного, может кто-нибудь придумает чудо-ответ ;-) Если нет, то ваш комментарий и есть ответ. ТАК больно -- я не могу начать свой комментарий с @Jon или @Jon Skeet, вы не забанены, не так ли? ;-). - person greenoldman; 28.07.2011
comment
@macias: Если у вас очень специфический тип источника, вы вполне сможете его отсортировать. Но представьте, если данные откуда-то передаются в потоковом режиме — понятия сброса может и не быть. (Представьте, что это поток случайных чисел.) Таким образом, вы не можете сделать это вообще. - person Jon Skeet; 28.07.2011
comment
Я говорю не о потоках, а о коллекциях — массивах, списках, множествах. Я знаю, что IEnumerable немного больше, но я имею в виду надежные данные. Даже если он случайный, он уже рандомизирован, поэтому каждый элемент больше не меняется. - person greenoldman; 28.07.2011
comment
@macias: Верно, как я уже сказал, имея в виду конкретный тип источника, вы можете это сделать. Но я бы не стал делать это из начальной точки IEnumerator<T> — я бы создал срез из ICollection<T>. - person Jon Skeet; 28.07.2011
comment
Хм, я не вижу особых изменений. Поскольку я не изменяю коллекцию, единственной полезной частью является получение перечислителя. Что возвращает нас к написанию перечислителя. - person greenoldman; 28.07.2011
comment
@macias: Нет, есть огромная разница в том, что вы можете делать с коллекцией: вы можете применить к ней произвольный доступ, так что вернитесь туда, где вы находитесь. Сравните это с итератором, который только обычно обеспечивает прямой доступ, иногда допускает сброс, но только в начало. Это не имеет ничего общего с тем, пытаетесь ли вы изменить коллекцию — все дело в том, как вы можете получить доступ к коллекции. - person Jon Skeet; 28.07.2011
comment
Я все еще не понимаю разницы. Исходная коллекция такая, какая она есть (список, массив, набор), поэтому, когда я создаю слайсы (каждый слайс будет ICollection) поверх него, я ограничен тем, что предоставляет исходная коллекция, и единственная интересная часть — это получение перечислителя msdn.microsoft.com/en-us/library/92t2ye13.aspx - person greenoldman; 28.07.2011
comment
@macias: Хорошо, если я дам вам итератор, уже расположенный на 5-м элементе, как вы дадите мне 6-й элемент, а затем снова 5-й элемент, не копируя этот 5-й элемент? А теперь задайте себе тот же вопрос еще раз, когда вместо этого я подарю вам коллекцию. Вы не можете вернуться от итератора к его исходной коллекции, по крайней мере, без хакерского отражения и т.д. - person Jon Skeet; 28.07.2011
comment
Вот почему меня интересует копирование перечислителя (не данных) :-) Однако я не говорю, что перечислитель лучше, просто использование ICollection вместо этого не помогает. - person greenoldman; 28.07.2011
comment
@macias: Что вы вообще подразумеваете под копированием счетчика? Имея перечислитель, вы просто не можете добраться до произвольных элементов. Используя ICollection<T>, вы можете. Так что да, использование ICollection<T> помогает. Черт возьми, я могу реализовать это для вас с ICollection<T> - я не могу для IEnumerator<T>. - person Jon Skeet; 28.07.2011
comment
Копирование = когда я делаю MoveNext в копии, позиция исходного перечислителя не меняется. ICollection -- я бы очень хотел его увидеть, но не вижу (помните: никакого копирования данных). The Slice‹T›: ICollection‹T› — это действительно представление для оригинальной коллекции. - person greenoldman; 28.07.2011
comment
@macias: Нет, это неправда. MoveNext() изменяет сам итератор. Не забывайте, что IEnumerator<T> — это интерфейс — копирование значения просто скопирует ссылку. Я думал не о Slice<T> : ICollection<T>, а о Slice<T> : IEnumerable<T> и создать Slice<T> из ICollection<T>. Если вас это все еще интересует, я напишу код, когда будет время. - person Jon Skeet; 28.07.2011
comment
@macias: Гах, я немного ошибся в своих интерфейсах. В идеале я бы хотел IList<T>, так как это обеспечивает произвольный доступ. Это по-прежнему хорошо для массивов и списков, а не для наборов, но наборы, как правило, не имеют врожденного порядка, поэтому не имеет смысла говорить о том, чтобы взять их часть. - person Jon Skeet; 28.07.2011
comment
Насчет MoveNext, да знаю, жаль я тогда не могу получить настоящую копию энумератора. С таким изменением, что исходная коллекция IList проста, вы просто избавляетесь от перечислителя и используете индекс для получения базы для среза и смещения внутри среза. Правильный? Я не мог видеть это для чего-то более общего, чем IList. - person greenoldman; 28.07.2011
comment
@macias: Да, верно, потому что все, что делает разрешает произвольный доступ к коллекции по положению, в любом случае должно реализовывать IList<T>. - person Jon Skeet; 28.07.2011

Чтобы получить итератор, который видит только элементы 10-30, используйте original.Skip(10).Take(20), хотя я не думаю, что вы можете использовать для него Reset.

Если вам нужно сбросить его, просто используйте что-то вроде

original.Skip(10).Take(20).ToArray()
person Gabe    schedule 28.07.2011
comment
Многие итераторы не поддерживают Reset. На самом деле это только для совместимости с COM, и на него не следует полагаться. Единственный надежный способ сбросить итератор — создать новый. - person Sven; 28.07.2011
comment
Действительно, на любой итератор, созданный с помощью блока итератора, нельзя полагаться. - person Jon Skeet; 28.07.2011
comment
@Sven, скажем, я могу создать новый, но как? Я не могу повторить оригинал. - person greenoldman; 28.07.2011