Marcello
Marcello

Reputation: 448

get sublist from list, and build average, with linq

probably there are past solutions somewhere, i've searched but didn't find them. My issue is very simple: Students get votes. I record votes on a list Then i want to get a list with the average. just in one instruction with linq.

In example: 3 student, Tim, Nick and Mark. During the year they get votes: Tim, 6, 7, 8 Nick 8 Mark 4,5

Using linq i want a list with: Tim 7 Nick 8 Mark 4.5

Here the code

Sub Main
  Dim StudentsVotes As List(of Votes) From {New Votes() With {.Name = "Tim", .Vote = 6}, _
                                          New Votes() With {.Name = "Nick", .Vote = 8}, _
                                          New Votes() With {.Name = "Mark", .Vote = 4}, _
                                          New Votes() With {.Name = "Tim", .Vote = 7}, _
                                          New Votes() With {.Name = "Mark", .Vote = 5}, _
                                          New Votes() With {.Name = "Tim", .Vote = 8}}

  Dim StudentsAverage As List(Of Votes) = StudentsVotes....() 'Don't know if use .Foreach, Select, Where...

  ' Here the normal function that i can use, but i would like to use a brief Linq instruction
  Private Function getStudentsAverage(ByVal sv As List(of Votes)) As List(Of Votes)
    getStudentsAverage = New List(Of Votes)
    Dim Votes As List(Of Integer)
    For Each studVote As Votes In sv
        Dim i As Integer = getStudentsAverage.FindIndex(Function(x) x.Name = studVote.Name)
        If i = -1 Then
            getStudentsAverage.Add(New Votes() With {.Name = studVote.Name, .Vote = studVote.Vote})
            Votes.Add(1)
        Else
            getStudentsAverage.Item(i).Vote += studVote.Vote
            Votes.Item(i) += 1
        End If
    Next studVote

    For a = 0 To getStudentsAverage.Item.Count-1
        getStudentsAverage.Item(a).Vote = getStudentsAverage.Item(a).Vote / Votes.Item(a)
    Next studAvg

    Return getStudentsAverage
  End Function
End Sub

Public Class Votes
    Public Name As String
    Private _Vote As Double
    Public Property Vote As Double
        Get
            Return _Vote
        End Get
        Set(ByVal value As Double)
            _Vote = Math.Round(Value, 2)
        End Set
    End Property
End Class

Upvotes: 2

Views: 196

Answers (2)

Mary
Mary

Reputation: 15091

I added a constructor to your class to simplify the code.

Public Class Votes
    Public Property Name As String
    Private _Vote As Double
    Public Property Vote As Double
        Get
            Return _Vote
        End Get
        Set(ByVal value As Double)
            _Vote = Math.Round(value, 2)
        End Set
    End Property

    Public Sub New(n As String, v As Double)
        Name = n
        Vote = v
    End Sub
End Class

I created a new list to hold the the averages.

Private StudentsVotes As New List(Of Votes) From {New Votes("Tim", 6),
                                      New Votes("Nick", 8),
                                      New Votes("Mark", 4),
                                      New Votes("Tim", 7),
                                      New Votes("Mark", 5),
                                      New Votes("Tim", 8)}

Dim VoteAverages As New List(Of Votes)

I used the Group from Linq and anonymous type.

Private Sub OPCode()
    Dim Result = (From v As Votes In StudentsVotes
                  Group v By v.Name
                     Into StudentAverage = Group
                  Select New With {.Name = Name, .Average = StudentAverage.Average(Function(v) v.Vote)}).ToList
    For Each a In Result
        Debug.Print($"{a.Name} {a.Average}")
        VoteAverages.Add(New Votes(a.Name, a.Average))
    Next
End Sub

Upvotes: 1

Marcello
Marcello

Reputation: 448

Ok, Thanks to JQSOFT, who helped everyone.

Here the solution:

Dim StudentsAverage As List(Of Votes) = StudentsVotes.GroupBy(Function(x) x.Name).Select(Function(x) New Votes With {.Name = x.Key, .Vote = x.Select(Function(y) y.Vote).Average}).ToList

Upvotes: 3

Related Questions