In VB.Net, If I have a collection like this:
Dim collection As IEnumerable(Of Integer) = Enumerable.Range(0, 99)
How I could split it in groups/Ienumerables of an indeterminated amount of elements?
Using a LINQ query (not MORELinq or any other 3rd party libs)
Not writting a Function
, just using (or appending to the collection) a LINQ query avoiding the insertion of a custom and generic procedure to split in parts.
Not generating an Anonymous
Type (because I will respect the VB.Net Option
Statements).
I've read at these questions but I was not able to properly translate the C# LINQ querys into VB.Net (even online translators fails and need much modifications):
Split a collection into `n` parts with LINQ?
How can I split an IEnumerable<String> into groups of IEnumerable<string>
Split List into Sublists with LINQ
Split a entity collection into n parts
These are two different approaches that I've tried to adapt from the solutions given in those S.O. questions above, but my translations doesn't works, the first can't compile because the condition of Group By and the second does not generates an splitted collection, it generates a collection per each element:
1.
Dim parts As Integer = 4
Dim i As Integer = 0
Dim splits As IEnumerable(Of IEnumerable(Of Integer)) =
From item As Integer In collection
Group By (Math.Max(Interlocked.Increment(i), i - 1) Mod parts)
Into Group
Select Group.AsEnumerable
2.
Dim parts As Integer = 4
Dim result As IEnumerable(Of IEnumerable(Of Integer)) =
collection.Select(Function(s, i) New With
{
Key .Value = s,
Key .Index = i
}
).GroupBy(Function(item)
Return (item.Index >= (item.Index / parts)) And (item.Index >= item.Value)
End Function,
Function(item) item.Value).Cast(Of IEnumerable(Of Integer))()
So, If I have a source collection like this:
Dim collection As IEnumerable(Of Integer) = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
And supposing that I have that imaginary LINQ query that can split into a variable named splits
by the value specified on variable parts
:
Dim parts as Integer = ...
Dim splits As IEnumerable(Of IEnumerable(Of Integer)) =
From value As Integer In collection (Linq Query that splits by given 'parts' value)
If I choosen that parts
value is 4 then the result will be an IEnumerable(Of IEnumerable(Of Integer))
which will contains 3 collections, where:
splits(0) = {1, 2, 3, 4}
splits(1) = {5, 6, 7, 8}
splits(2) = {9, 10}
If I choosen that parts
value is 5 then the result will be an IEnumerable(Of IEnumerable(Of Integer))
which will contains 2 collections, where:
splits(0) = {1, 2, 3, 4, 5}
splits(1) = {6, 7, 8, 9, 10}
If I choosen that parts
value is 1 then the result will be an IEnumerable(Of IEnumerable(Of Integer))
which will contains the identical source collection because I choosen to split in just 1 part:
splits(0) = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
If I choosen that parts
value is 10 then the result will be an IEnumerable(Of IEnumerable(Of Integer))
which will contains 10 collections, one collection per each value of the source example collection:
splits(0) = {1}
splits(1) = {2}
splits(2) = {3}
and so on...
You can use Range, Skip and Take to achieve your goal.
Dim collection As IEnumerable(Of Integer) = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
Dim parts As Integer = 4
Dim count As Integer = CInt(Math.Ceiling(collection.Count() / parts)) '= 3
Dim result As IEnumerable(Of IEnumerable(Of Integer)) = Nothing
'Method 1:
result = From i In Enumerable.Range(0, count) Select collection.Skip(i * parts).Take(parts)
'Method 2:
result = Enumerable.Range(0, count).Select(Function(i) collection.Skip(i * parts).Take(parts))
For Each item In result
Debug.WriteLine(String.Join(", ", item))
Next
1, 2, 3, 4
5, 6, 7, 8
9, 10
Mandatory extension method:
Public Module Extensions
<System.Runtime.CompilerServices.Extension()>
Public Function Split(Of TSource)(collection As IEnumerable(Of TSource), ByVal parts As Integer) As IEnumerable(Of IEnumerable(Of TSource))
If (collection Is Nothing) Then Throw New ArgumentNullException("collection")
If (parts < 1) Then Throw New ArgumentOutOfRangeException("parts")
Dim count As Integer = collection.Count()
If (count = 0) Then Return {}
If (parts >= count) Then Return {collection}
Return Enumerable.Range(0, CInt(Math.Ceiling(count / parts))).Select(Function(i) collection.Skip(i * parts).Take(parts))
End Function
End Module
result = collection.Split(4)
The query you're looking for could look like this:
Dim partitionSize As Integer = 4
Dim collection As IEnumerable(Of Integer) = Enumerable.Range(0, 100)
Dim parts As Integer = collection.Count \ partitionSize
Dim splits = (From i In collection
Group i By key = i Mod parts Into g = Group
Select key, g)
This relies on the sample you've shown. However if the actual data is arranged more randomly, this way will use the value at the index grouped according to the index value:
Dim partitionSize As Integer = 4
Dim collection As IEnumerable(Of Integer) = Enumerable.Range(100, 100)
Dim parts As Integer = collection.Count \ partitionSize
Dim splits = (From i In Enumerable.Range(0, 100)
Group collection(i) By key = i Mod parts Into g = Group
Select key, g)
Your post says that you don't want to use anonymous types. However Group By uses anonymous types. You can make the anonymous type temporary by casting the collection to a Dictionary:
Dim splits = (From i In Enumerable.Range(0, 100)
Group collection(i) By key = i Mod parts Into g = Group
Select key, g).ToDictionary(Function(x) x.key, Function(x) x.g)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With