2013-12-12 4 views
1

Мне очень нравится algorithm, показанный ниже, для разделения списка на подсписок фиксированного размера. Это может быть не наиболее эффективный алгоритм (изменить: вообще).Эффективный список разделов на куски фиксированного размера

Я хотел бы что-то, что имеет хороший баланс читаемости, элегантности и производительности. Проблема заключается в том, что большинство алгоритмов я нахожу в C# требуется yield ключевое слово, которое не доступно, если вы используете .NET 3.5 в Visual Studio 2010;)

public IEnumerable<IEnumerable<T>> Partition<T>(IEnumerable<T> source, int size) 
{ 
    if (source == null) 
     throw new ArgumentNullException("list"); 

    if (size < 1) 
     throw new ArgumentOutOfRangeException("size"); 

    int index = 1; 
    IEnumerable<T> partition = source.Take(size).AsEnumerable(); 

    while (partition.Any()) 
    { 
     yield return partition; 
     partition = source.Skip(index++ * size).Take(size).AsEnumerable(); 
    } 
} 

Я пытался переписывать это в VB, но должен был используйте второй список для сбора результатов, в результате чего значительно больше времени, чем реализация выше.

Я ищу другой алгоритм, который я мог бы использовать в VB.NET, но большая часть результатов столкнулась с проблемой необходимости загружать все в память вместо возможности динамически производить результаты. a la генераторы в python. Не огромная проблема, но не такая идеальная, как это было бы с yield return.

Есть ли хороший, рекомендуемый алгоритм для этого в VB.NET? Должен ли я создать что-то, реализующее IEnumerator, чтобы генерировать результаты по запросу?

+1

Я считаю, что доходность была введена в .NET 2.0 –

+0

возможный дубликат [Yield in VB.NET] (http://stackoverflow.com/questions/97381/yield-in-vb-net) –

+0

@MitchWheat I don ' t думаю, что ... Это не в этом [список ключевых слов VB] (http://msdn.microsoft.com/en-us/library/dd409611 (v = vs.100) .aspx) в VS2010 как минимум .. –

ответ

1

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

Public Sub DoForPartition(Of T)(source As IEnumerable(Of T), 
           size As Integer, 
           doThis As Action(Of IEnumerable(Of T))) 

    Dim partition(size - 1) As T 
    Dim count = 0 

    For Each t in source 
     partition(count) = t 
     count += 1 

     If count = size Then 
      doThis(partition) 
      count = 0 
     End If 
    Next 

    If count > 0 Then 
     Array.Resize(partition, count) 
     doThis(partition) 
    End If 
End Sub 

Эта функция позволяет избежать зацикливания через источник несколько раз, и только накладные расходы памяти размер раздела (вместо всего источника, как некоторые из других вариантов). Я сам не писал эту функцию, но адаптировал аналогично выглядящую функцию C# от this answer.

Это похоже на гораздо лучший алгоритм, чем на мой вопрос.

1

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

Dim testlist As New List(Of List(Of Integer)) 
Partition(Of Integer)({1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, 4, testlist) 

Public Sub Partition(Of T)(source As IEnumerable(Of T), size As Integer, ByRef input As List(Of List(Of T))) 
    If source Is Nothing Then 
     Throw New ArgumentNullException("list") 
    End If 
    If size < 1 Then 
     Throw New ArgumentOutOfRangeException("size") 
    End If 
    For i = 0 To source.Count - 1 Step size 
     input.Add(source.Skip(i).Take(size).ToList()) 
    Next 
End Sub 
1

Отсутствие Yield, вы можете сделать следующее. Я предполагаю, что вы можете конвертировать в VB

public IEnumerable<IEnumerable<T>> Partition<T>(IEnumerable<T> source, int size) 
{ 
    if (source == null) 
     throw new ArgumentNullException("list"); 

    if (size < 1) 
     throw new ArgumentOutOfRangeException("size"); 

    List<List<T>> partitions = new List<List<T>>(); 

    int index = 1; 
    IEnumerable<T> partition = source.Take(size).AsEnumerable(); 

    while (partition.Any()) 
    { 
     partitions.Add(partition); 
     partition = source.Skip(index++ * size).Take(size).AsEnumerable(); 
    } 

    return partitions; 
} 

Обратите внимание, что это неточный перевод. Исходный код возвращает один раздел за раз. Этот код создает все разделы и затем возвращает их все в списке. Не проблема, если source не огромен.

Это будет То же самое с вашим кодом. То есть, если у вас есть:

foreach (IEnumerable<Foo> part in Partition(FooList, 100)) 
{ 
    // whatever 
} 

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

foreach (IEnumerable<Foo> part in Partition(FooList, 100).ToList()) 

Как кто-то отметил в комментарии, это не самый эффективный способ сделать вещи, потому что Skipможет в конечном итоге, перебирать элементы несколько раз. Опять же, если список source не огромен, то это, вероятно, не проблема. И вполне вероятно, что Skip оптимизирован для прямой индексации для вещей, которые реализуют IList.

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