ATeDe
ATeDe

Reputation: 11

Split a collection into n parts with Lambda (or LINQ), in VB.Net

Example of code presented below is working fine, it's doing what is supposed to do, but I'm not satisfied. Looking for much smarter solution in VB.NET. The presentation of results (I mean counts for each subgroup) is quite awkward. The content of data and list of records etc. are not important. Also counts should be sorted in order Less{0}, From{1}To{2}, MoreThan{3}...Thanks in advance.

    Dim Age1 As Integer = 5
    Dim Age2 As Integer = 9
    Dim myList As New List(Of Integer) = {1, 3, 5, 7, 9, 2, 4, 6, 8, 10, 2, 4, 5, 7, 8, 9, 6, 7, 9, 11}
    Dim Lambda = myList.GroupBy(Function(x) New With {Key .Age1 = (x) < Age1,Key .Age2 = (x) > Age1 - 1 And (x) <= Age2,Key .Age3 = (x) > Age2}).ToList()
    Dim group1, group2, group3 As Integer
    myList = myList.OrderBy(Function(x) x).ToList()
    Console.WriteLine(String.Join(",", myList.Select(Function(s) s.ToString).ToArray))
    For Each group In Lambda
        If group.Key.Age1 Then group1 = group.Count()
        If group.Key.Age2 Then group2 = group.Count()
        If group.Key.Age3 Then group3 = group.Count()
    Next
    ' Obviously If Stop Then Error condition
    If group1 + group2 + group3 <> myList.Count Then Stop

    Console.WriteLine(String.Format("Groups: Less{0},From{1}To{2},MoreThan{3}", Age1, Age1, Age2 - 1, Age2))
    Console.WriteLine(String.Format("   Age:  {0,4},{1,8},{2,8}", group1, group2, group3))
    '1,2,2,3,4,4,5,5,6,6,7,7,7,8,8,9,9,9,10,11
    'Groups: Less5,From5To8,MoreThan9
    'Age:     6,      12,        2

Upvotes: 0

Views: 204

Answers (3)

ATeDe
ATeDe

Reputation: 11

Here is 2'nd fastest and I think quite 'clean' solution based on @Enigmativity concept posted a couple of hours ago...Takes a care of his n-bands approach

Function simpleCSVsplit(ageForBins() As Integer, myList As List(Of Integer)) As List(Of Integer)
    Dim Bands As New List(Of Integer)
    For indx As Integer = 0 To ageForBins.Count - 1
        Bands.Add(myList.Where(Function(x) x < ageForBins(indx)).Count())
        myList = myList.Skip(Bands(indx)).ToList()
    Next
    Bands.Add(myList.Count)
    Return Bands
End Function

Upvotes: 0

Enigmativity
Enigmativity

Reputation: 117064

It seems to me that this is the simplest way to do it:

Dim Age1 As Integer = 5
Dim Age2 As Integer = 9
Dim myList As New List(Of Integer) From {1, 3, 5, 7, 9, 2, 4, 6, 8, 10, 2, 4, 5, 7, 8, 9, 6, 7, 9, 11}

Dim group1 As Integer = myList.Where(Function (x) x < Age1).Count()
Dim group2 As Integer = myList.Where(Function (x) x > Age1 - 1 And x <= Age2).Count()
Dim group3 As Integer = myList.Where(Function (x) x > Age2).Count()

If group1 + group2 + group3 <> myList.Count Then Stop

Console.WriteLine(String.Format("Groups: Less{0},From{1}To{2},MoreThan{3}", Age1, Age1, Age2 - 1, Age2))
Console.WriteLine(String.Format("   Age:  {0,4},{1,8},{2,8}", group1, group2, group3))

If you want a funky LINQ-based method then try this:

Dim bands() As Func(Of Integer, Boolean) = _
{ _
    Function (x) x < Age1, _
    Function (x) x <= Age2, _
    Function (x) True _
}

Dim counts = _
    myList _
        .GroupBy(Function (x) Enumerable.Range(0, bands.Count).Where(Function (n) bands(n)(x)).First()) _
        .Select(Function (x) x.Count()) _
        .ToArray()

Dim group1 As Integer = counts(0)
Dim group2 As Integer = counts(1)
Dim group3 As Integer = counts(2)

Upvotes: 1

NetMage
NetMage

Reputation: 26917

Here's how I would try to improve it:

Dim Age1 As Integer = 5
Dim Age2 As Integer = 9

Dim myList As New List(Of Integer) From {1, 3, 5, 7, 9, 2, 4, 6, 8, 10, 2, 4, 5, 7, 8, 9, 6, 7, 9, 11}
Console.WriteLine(String.Join(", ", myList.OrderBy(Function(x) x)))
console.WriteLine

Dim ageBins = myList.GroupBy(Function(age) If(age < Age1, 1, If(age >= Age1 And age <= Age2, 2, 3))) _
                    .Select(Function(agebin) New With { agebin.Key, .AgeCount = agebin.Count() }) _
                    .OrderBy(Function(agebin) agebin.Key)

For Each bin In ageBins
    Dim msg As String
    Select bin.Key
        Case 1
            msg = $"Less {Age1}"
        Case 2
            msg = $"From {Age1} To {Age2}"
        Case Else
            msg = $"MoreThan {Age2}"
    End Select
    Console.WriteLine($"{msg,12}: {bin.AgeCount}")
Next

You could also change the code to handle any number of bins:

Dim agesForBins = {5, 10}
Dim ageBins = myList.GroupBy(Function(age) Enumerable.Range(0, agesForBins.Length).Where(Function(n) age < agesForBins(n)).DefaultIfEmpty(agesForBins.Length).First) _
                    .Select(Function(agebin) New With { agebin.Key, .AgeCount = agebin.Count() }) _
                    .OrderBy(Function(agebin) agebin.Key)

For Each bin In ageBins
    Dim msg As String
    If bin.Key = 0 Then
        msg = $"Less {agesForBins(0)}"
    ElseIf bin.Key = agesForBins.Length Then
        msg = $"MoreThan {agesForBins(bin.Key-1)-1}"
    Else
        msg = $"From {agesForBins(bin.Key-1)} To {agesForBins(bin.Key)-1}"
    End If

    Console.WriteLine($"{msg,12}: {bin.AgeCount}")
Next

Upvotes: 1

Related Questions