Is there any way I can separate a List
into several separate lists of SomeObject
, using the item index as the delimiter of each s
So performatic as the Sam Saffron's approach.
public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int size)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size), "Size must be greater than zero.");
return BatchImpl(source, size).TakeWhile(x => x.Any());
}
static IEnumerable<IEnumerable<T>> BatchImpl<T>(this IEnumerable<T> source, int size)
{
var values = new List<T>();
var group = 1;
var disposed = false;
var e = source.GetEnumerator();
try
{
while (!disposed)
{
yield return GetBatch(e, values, group, size, () => { e.Dispose(); disposed = true; });
group++;
}
}
finally
{
if (!disposed)
e.Dispose();
}
}
static IEnumerable<T> GetBatch<T>(IEnumerator<T> e, List<T> values, int group, int size, Action dispose)
{
var min = (group - 1) * size + 1;
var max = group * size;
var hasValue = false;
while (values.Count < min && e.MoveNext())
{
values.Add(e.Current);
}
for (var i = min; i <= max; i++)
{
if (i <= values.Count)
{
hasValue = true;
}
else if (hasValue = e.MoveNext())
{
values.Add(e.Current);
}
else
{
dispose();
}
if (hasValue)
yield return values[i - 1];
else
yield break;
}
}
}
If the source collection implements IList < T > (random access by index) we could use the below approach. It only fetches the elements when they are really accessed, so this is particularly useful for lazily-evaluated collections. Similar to unbounded IEnumerable< T >, but for IList< T >.
public static IEnumerable<IEnumerable<T>> Chunkify<T>(this IList<T> src, int chunkSize)
{
if (src == null) throw new ArgumentNullException(nameof(src));
if (chunkSize < 1) throw new ArgumentOutOfRangeException(nameof(chunkSize), $"must be > 0, got {chunkSize}");
for(var ci = 0; ci <= src.Count/chunkSize; ci++){
yield return Window(src, ci*chunkSize, Math.Min((ci+1)*chunkSize, src.Count)-1);
}
}
private static IEnumerable<T> Window<T>(IList<T> src, int startIdx, int endIdx)
{
Console.WriteLine($"window {startIdx} - {endIdx}");
while(startIdx <= endIdx){
yield return src[startIdx++];
}
}
Can work with infinite generators:
a.Zip(a.Skip(1), (x, y) => Enumerable.Repeat(x, 1).Concat(Enumerable.Repeat(y, 1)))
.Zip(a.Skip(2), (xy, z) => xy.Concat(Enumerable.Repeat(z, 1)))
.Where((x, i) => i % 3 == 0)
Demo code: https://ideone.com/GKmL7M
using System;
using System.Collections.Generic;
using System.Linq;
public class Test
{
private static void DoIt(IEnumerable<int> a)
{
Console.WriteLine(String.Join(" ", a));
foreach (var x in a.Zip(a.Skip(1), (x, y) => Enumerable.Repeat(x, 1).Concat(Enumerable.Repeat(y, 1))).Zip(a.Skip(2), (xy, z) => xy.Concat(Enumerable.Repeat(z, 1))).Where((x, i) => i % 3 == 0))
Console.WriteLine(String.Join(" ", x));
Console.WriteLine();
}
public static void Main()
{
DoIt(new int[] {1});
DoIt(new int[] {1, 2});
DoIt(new int[] {1, 2, 3});
DoIt(new int[] {1, 2, 3, 4});
DoIt(new int[] {1, 2, 3, 4, 5});
DoIt(new int[] {1, 2, 3, 4, 5, 6});
}
}
1
1 2
1 2 3
1 2 3
1 2 3 4
1 2 3
1 2 3 4 5
1 2 3
1 2 3 4 5 6
1 2 3
4 5 6
But actually I would prefer to write corresponding method without linq.
Check this out! I have a list of elements with a sequence counter and date. For each time the sequence restarts, I want to create a new list.
Ex. list of messages.
List<dynamic> messages = new List<dynamic>
{
new { FcntUp = 101, CommTimestamp = "2019-01-01 00:00:01" },
new { FcntUp = 102, CommTimestamp = "2019-01-01 00:00:02" },
new { FcntUp = 103, CommTimestamp = "2019-01-01 00:00:03" },
//restart of sequence
new { FcntUp = 1, CommTimestamp = "2019-01-01 00:00:04" },
new { FcntUp = 2, CommTimestamp = "2019-01-01 00:00:05" },
new { FcntUp = 3, CommTimestamp = "2019-01-01 00:00:06" },
//restart of sequence
new { FcntUp = 1, CommTimestamp = "2019-01-01 00:00:07" },
new { FcntUp = 2, CommTimestamp = "2019-01-01 00:00:08" },
new { FcntUp = 3, CommTimestamp = "2019-01-01 00:00:09" }
};
I want to split the list into separate lists as the counter restarts. Here is the code:
var arraylist = new List<List<dynamic>>();
List<dynamic> messages = new List<dynamic>
{
new { FcntUp = 101, CommTimestamp = "2019-01-01 00:00:01" },
new { FcntUp = 102, CommTimestamp = "2019-01-01 00:00:02" },
new { FcntUp = 103, CommTimestamp = "2019-01-01 00:00:03" },
//restart of sequence
new { FcntUp = 1, CommTimestamp = "2019-01-01 00:00:04" },
new { FcntUp = 2, CommTimestamp = "2019-01-01 00:00:05" },
new { FcntUp = 3, CommTimestamp = "2019-01-01 00:00:06" },
//restart of sequence
new { FcntUp = 1, CommTimestamp = "2019-01-01 00:00:07" },
new { FcntUp = 2, CommTimestamp = "2019-01-01 00:00:08" },
new { FcntUp = 3, CommTimestamp = "2019-01-01 00:00:09" }
};
//group by FcntUp and CommTimestamp
var query = messages.GroupBy(x => new { x.FcntUp, x.CommTimestamp });
//declare the current item
dynamic currentItem = null;
//declare the list of ranges
List<dynamic> range = null;
//loop through the sorted list
foreach (var item in query)
{
//check if start of new range
if (currentItem == null || item.Key.FcntUp < currentItem.Key.FcntUp)
{
//create a new list if the FcntUp starts on a new range
range = new List<dynamic>();
//add the list to the parent list
arraylist.Add(range);
}
//add the item to the sublist
range.Add(item);
//set the current item
currentItem = item;
}
Just putting in my two cents. If you wanted to "bucket" the list (visualize left to right), you could do the following:
public static List<List<T>> Buckets<T>(this List<T> source, int numberOfBuckets)
{
List<List<T>> result = new List<List<T>>();
for (int i = 0; i < numberOfBuckets; i++)
{
result.Add(new List<T>());
}
int count = 0;
while (count < source.Count())
{
var mod = count % numberOfBuckets;
result[mod].Add(source[count]);
count++;
}
return result;
}
Another way is using Rx Buffer operator
//using System.Linq;
//using System.Reactive.Linq;
//using System.Reactive.Threading.Tasks;
var observableBatches = anAnumerable.ToObservable().Buffer(size);
var batches = aList.ToObservable().Buffer(size).ToList().ToTask().GetAwaiter().GetResult();