获取列/字段总和等于特定值的行

时间:2017-03-10 00:53:49

标签: .net vb.net linq

我想在VB.NET或C#中编写Linq子句的代码,它返回所有记录,直到其中一列值之和达到某个值并按其他列分组。

现在我在MySQL条款中有这个:

SELECT
  O.Id,
  O.Supp,
  O.TotalCol,
  (SELECT
     sum(TotalCol) FROM Table1
   WHERE Id <= O.Id) 'bTotal'
FROM Table1 O
HAVING bTotal <= 50000

我已完成的代码:

对象类:

Class Invoices
        Public id As Integer
        Public supplier As String
        Public total As Decimal
        Public Sub New(ByVal id As Integer, ByVal supplier As String, ByVal total As Decimal)
            Me.id = id
            Me.supplier = supplier
            Me.total = total 
        End Sub
End Class

未分组列表:

Dim LInvoicesPrincipal As New List(Of Invoices)()

Dim InvoiceItem As Invoices

InvoiceItem = New Invoices(1, "SUPP 1", 45000)
LInvoicesPrincipal.Add(InvoiceItem)
InvoiceItem = New Invoices(2, "SUPP 1", 6000)
LInvoicesPrincipal.Add(InvoiceItem)
InvoiceItem = New Invoices(3, "SUPP 2", 15000)
LInvoicesPrincipal.Add(InvoiceItem)
InvoiceItem = New Invoices(4, "SUPP 2", 6000)
LInvoicesPrincipal.Add(InvoiceItem)
InvoiceItem = New Invoices(5, "SUPP 1", 4000)
LInvoicesPrincipal.Add(InvoiceItem)

LINQ中的分组,由提供者(我需要将总数之和限制为某个值的规则(准确地说是50000)):

Dim LGroups

LGroups = _
    From oInvoices In LInvoicesPrincipal _
    Group oInvoices By oInvoices.supplier _
    Into Group Select Group

允许我验证分组的For Each:

For Each EachList As Invoices() In LGroups

    For Each EachItem As Invoices In EachList

    Next

Next

所有这些目前都会返回我这样的列表:

Group 1:
Invoices(1, "SUPP 1", 45000)
Invoices(2, "SUPP 1", 6000)
Invoices(5, "SUPP 1", 4000)

Group 2:
Invoices(3, "SUPP 2", 15000)
Invoices(4, "SUPP 2", 6000)

但是......我想得的是这样的:

Group 1:
Invoices(1, "SUPP 1", 45000)
Invoices(5, "SUPP 1", 4000)

Group 2:
Invoices(2, "SUPP 1", 6000)

Group 3:
Invoices(3, "SUPP 2", 15000)
Invoices(4, "SUPP 2", 6000)

(值的总和必须<=每组50000次

1 个答案:

答案 0 :(得分:1)

Bin Packing algorithm用于限制打包给定项目集所需的箱数。您似乎最关心的是将供应商的总计限制为&lt; = 50000与创建多少个发票组。 按供应商元素似乎仅在传递时提及。

  • 这应该做你想要的,但它并不是最适合的方法。
  • 我使用了更多数据来获得更好的图片:5家供应商和30家发票
  • 供应商和发票是随机创建的,因此每个供应商没有6张发票
  • 1供应商发票集不是随机的,所以我可以测试特定的数据集

你已经有Invoice课程,但我的课程略有不同:

Public Class Invoice
    Public Property Id As Int32
    Public Property Supplier As String
    Public Property Total As Decimal

    Public Property GroupId As Int32
    ...

我强烈建议您使用实际的Properties代替公共字段。这允许您在DataGridView中显示结果以进行调试。新的GroupId属性允许代码识别哪些发票项目已添加到InvoiceGroup

Public Class InvoiceGrp
    Public Property GroupId As Int32
    Public Property Supplier As String
    Public ReadOnly Property Total As Decimal
        Get  
            Return Invoices.Sum(Function(k) k.Total)
        End Get
    End Property

    Public Property Invoices As New List(Of Invoice)
    ...

还有一个ToString()覆盖和一个Count属性用于调试。首先,&#39; raw&#39;发票清单需要按供应商分组,然后才能处理每个发票清单:

' group the invoices by supplier
Dim grpData = Invoices.GroupBy(Function(g) g.Supplier).ToArray()

' collection to store the results
Dim groupedInvoices = New List(Of InvoiceGrp)

Dim groupId = 1
' create invoice groups one supplier group at a time
For Each item In grpData
    Dim gi = GroupInvoices(item, 50000, groupId)   ' add ToArray() for debug
    groupedInvoices.AddRange(gi)

    ' the GroupId gets incremented for each supplier
    '  group, use the last one for the start value in the next
    '  supplier group 
    groupId = gi.Max(Of Int32)(Function(k) k.GroupId)
Next

发票总额是随机选择的:
 {4000, 2500, 5000, 10000, 15000, 45000, 6000, 25000}

GroupInvoice方法和助手:

Private Iterator Function GroupInvoices(grp As IEnumerable(Of Invoice),
                                        Limit As Decimal,
                                        grpId As Int32) As IEnumerable(Of InvoiceGrp)

    ' make a copy so we can remove those grouped
    Dim myGrp = New List(Of Invoice)(grp.
                                      Where(Function(k) k.GroupId = -1).
                                      OrderByDescending(Function(k) k.Total).
                                      ToArray())

    While (myGrp.Count > 0)
        grpId += 1
        ' NewInvoiceGroup does the actual Group creation
        Dim newGrp = NextInvoiceGroup(myGrp, Limit, grpId)
        ' remove grouped items from the ToDo list
        myGrp.RemoveAll(Function(r) newGrp.Invoices.Contains(r))

        Yield newGrp
    End While
End Function

Private Function NextInvoiceGroup(items As List(Of Invoice),
                                 Limit As Decimal,
                                 nextGrp As Int32) As InvoiceGrp

    ' this creates one InvGrp with as many Invoices
    ' as will fit.  
    Dim InvG = New InvoiceGrp With {.Supplier = items(0).Supplier, .GroupId = nextGrp}

    For Each inv In items
        If InvG.Total + inv.Total <= Limit Then
            ' tag Invoice with the InvoiceGrp Id
            inv.GroupId = nextGrp
            InvG.Invoices.Add(inv)
        End If
    Next

    Return InvG
End Function

重要提示

  • 发件数为N的linq结果中的每个组都会传递到GroupInvoices
  • 按金额(总计)按顺序排列,然后调用NextInvoiceGroup实际创建的总计不超过50k。
  • 它是Iterator,允许它在创建时返回每个组。
  • 创建的每个组中的发票将从列表中删除,以便在没有更新时知道已完成。同样,每个发票都有GroupId,因此代码知道哪些已添加到组中。
  • NextInvoiceGroup会迭代所有发票,将每个发票添加到新的InvoiceGrp,直到达到50k或用完项目。
  • 没有涉及最佳匹配/装箱包装逻辑(并且未在帖子中提及),因此供应商的第一组往往更加充实&#34; (接近50k),最后一个将是剩余的。
  • GroupId应为所有群组的唯一值
  • 最后,您不仅拥有InvoiceGrps,而且每张发票还有一个指示所属的组。
  • 如果Iterator方法让您感到困惑,可以重构以创建并返回List(Of InvoiceGroup)
  • 如果您将内容打印到控制台窗口,有时可以看到如果您将某些项目从一个组交换到另一个组,则剩余部分可能适合其他组。这自然取决于数据:

调试转储:

For Each grp In groupedInvoices
    Console.WriteLine("{0} Grp# {1}: ct: {2} tot: {3}", grp.Supplier, grp.GroupId,
                      grp.Invoices.Count, grp.Total)
    For Each inv In grp.Invoices
        Console.WriteLine("Supp: {0} Inv#: {1} Tot: {2}",
                          inv.Supplier, inv.Id, inv.Total)
    Next
    Console.WriteLine()
Next

结果(非随机供应商组除外)

  

Alpha Grp#1:ct:4 tot:50000
  补充:Alpha Inv#:2 Tot:25000
  补充:Alpha Inv#:14 Tot:15000
  补充:Alpha Inv#:8 Tot:5000
  补充:Alpha Inv#:16 Tot:5000

     

Alpha Grp#2:ct:3 tot:13000
  补充:Alpha Inv#:22 Tot:5000
  补充:Alpha Inv#:1 Tot:4000
  补充:Alpha Inv#:6 Tot:4000

     

Delta Grp#3:ct:2 tot:50000
  补充:Delta Inv#:4 Tot:45000
  补充:Delta Inv#:3 Tot:5000

     

Delta Grp#4:ct:5 tot:22500
  补充:Delta Inv#:5 Tot:6000
  补充:Delta Inv#:21 Tot:6000
  补充:Delta Inv#:11 Tot:4000
  补充:Delta Inv#:12 Tot:4000
  补充:Delta Inv#:9 Tot:2500

     

Echo Grp#5:ct:2 tot:50000
  补充:Echo Inv#:23 Tot:45000
  补充:Echo Inv#:24 Tot:5000

     

Echo Grp#6:ct:2 tot:50000
  补充:Echo Inv#:7 Tot:25000
  Supp:Echo Inv#:18 Tot:25000

     

Echo Grp#7:ct:3 tot:50000
  补充:Echo Inv#:20 Tot:25000
  补充:Echo Inv#:19 Tot:15000
  Supp:Echo Inv#:25 Tot:10000

     

FoxTrot Grp#8:ct:2 tot:50000
  补充:FoxTrot Inv#:15 Tot:45000
  Supp:FoxTrot Inv#:13 Tot:5000

     

FoxTrot Grp#9:ct:1 tot:45000
  补充:FoxTrot Inv#:17 Tot:45000

     

FoxTrot Grp#10:ct:1 tot:6000
  Supp:FoxTrot Inv#:10 Tot:6000