根据两个条件累积数据

时间:2019-05-12 14:32:41

标签: arrays vb.net

我正在尝试编写一个VB.NET程序,该程序从文件中读取数据并按以下格式对每一列进行计数,并且还写入输出文件。

我可以进行计数,但是无法写出每个餐厅每天的输出。

根据我现有的信息,我只能从数组索引中写出总和。

这是我到目前为止的代码:

Dim IntSubjectArray(23) As String

OpenFileDialog1.ShowDialog()
strInputPath = OpenFileDialog1.FileName
FileOpen(IntInputFileName, strInputPath, OpenMode.Input)
Do While Not EOF(IntInputFileName)
    Dim StrReadLine As String = LineInput(IntInputFileName)
    Dim StrSplitRecord() As String = Split(StrReadLine, ",")
    IntRestaurant = StrSplitRecord(0)
    IntDay = StrSplitRecord(1)
    Meal1 = StrSplitRecord(2)
    Meal2 = StrSplitRecord(3)
    Meal3 = StrSplitRecord(4)
    If SaveDay <> IntDay Then
        IntMealArray(meal1) += 1
        IntMealArray(meal2) += 1
        IntMealArray(meal3) += 1
        SaveDay = IntDay
        SaveDay = 0
    End If
    savetown = IntExamTown
Loop
Call WriteOutputArray()
FileClose(IntInputFileName)
MessageBox.Show("File written to specified location")
Public Sub WriteOutputArray()
    IntOutputFileName = FreeFile()
    For Each Array As String In IntMealArray
        FileOpen(IntOutputFileName, "C:\Users\ireport\foodCount.txt", OpenMode.Append)
        WriteLine(IntOutputFileName, IntMealArray(Array))
        FileClose(IntOutputFileName)
    Next
End Sub

文件格式为

001,1,5,6,21
001,1,5,6,21
001,1,5,6,21
001,1,10,12,18
001,2,5,6,19
001,2,8,9,19
001,2,6,19,21
001,2,5,6,21
001,3,7,12,18
001,3,8,12,19
001,3,7,12,18
040,4,7,12,18
040,4,7,12,18
040,4,7,12,18
040,4,9,12,19

键:
格式为001:餐厅1,然后是第一天,然后是特定客户食用的食物(共有23种不同的食物),每种餐点都由文件中的代码1到23表示。

预期产量是客户每天在每种营养素中食用的食物数量,例如:

Rest day Rice   Beans Yam  Meat  Snack coke Burger Meal8 Meal9  Meal10  M11  M12
001   1    0     0     0    0     3      3     0     0     0      1      0    1
001   2    0     0     0    0     2      3     0     1     1      0      0    0
001   3    0     0     0    0     0      0     2     1     0      0      0    3
040   4    0     0     0    0     0      0     3     0     1      0      0    4

2 个答案:

答案 0 :(得分:1)

首先,您需要将数据转换为某种格式,这样才能更轻松地在代码中看到哪一部分。一种简单的方法是使用具有有意义名称的属性创建一个类。

然后您可以按餐厅将数据分组,对于每个餐厅,可以将每个日期的数据分组。

由于输出位于食物名称宽度的列中,因此在格式化输出时需要考虑这些名称。

为简单起见,我创建了一个控制台应用程序而不是Windows Forms应用程序。另外,如果我不只是为了概念验证,还可以将其分解为更多方法。

Imports System.IO
Imports System.Text

Module Module1

    Dim Foods As Dictionary(Of String, String)

    Class Datum
        Property Restaurant As String
        Property Day As Integer
        Property FoodCodes As List(Of String)

        Public Overrides Function ToString() As String
            ' Useful for debugging.
            Return $"{Restaurant} {Day} " & String.Join(",", FoodCodes)
        End Function

    End Class

    Sub LoadFoods()
        ' Generate some food names. The first food name has a code of "1".
        Foods = New Dictionary(Of String, String)
        Dim names = {"Rice", "Beans", "Banana", "Meat", "Snacks", "Potato", "Spinach",
            "Fish", "Aubergine", "Peas", "Egg", "Chicken", "Cheese", "Onion",
            "Carrots", "Brocolli", "Asparagus", "Garlic", "Cabbage", "Coconut", "Yam",
            "Naan", "Lentils"}

        For i = 1 To names.Count
            Foods.Add(i.ToString(), names(i - 1))
        Next

    End Sub

    Sub Main()
        LoadFoods()

        Dim src = "C:\temp\FoodRecords.txt"
        Dim dest = "C:\temp\meals.txt"

        Dim data As New List(Of Datum)

        For Each line In File.ReadLines(src)
            Dim parts = line.Split({","c})
            If parts.Count = 5 Then
                Dim d As New Datum With {.Restaurant = parts(0),
                                         .Day = Integer.Parse(parts(1)),
                                         .FoodCodes = parts.Skip(2).OrderBy(Function(s) s).ToList()}
                data.Add(d)

            End If
        Next

        ' Prepare information on the widths of the columns to be output...
        Dim colWidths As New List(Of Integer)
        colWidths.Add(-("Restaurant".Length))
        colWidths.Add(-("Day".Length))

        For Each food In Foods
            colWidths.Add(food.Value.Length)
        Next

        ' Group the data by restaurant...
        Dim restaurantData = From d In data
                             Group By RestCode = d.Restaurant
                             Into RestData = Group


        Using sw As New StreamWriter(dest)
            sw.Write("Restaurant Day ")
            sw.WriteLine(String.Join(" ", Foods.Select(Function(f) f.Value)))

            For Each x In restaurantData
                'Console.WriteLine(x.RestCode & " " & String.Join(",", x.RestData))

                ' Get each day of data for this restaurant
                Dim restaurantDay = From y In x.RestData
                                    Group By Day = y.Day
                                    Into DayData = Group


                For Each rd In restaurantDay

                    ' Count the occurrences of food codes for this day...
                    Dim dayFoodCounts As New Dictionary(Of String, Integer)

                    For Each fd In rd.DayData
                        For Each fc In fd.FoodCodes
                            If dayFoodCounts.ContainsKey(fc) Then
                                dayFoodCounts(fc) += 1
                            Else
                                dayFoodCounts.Add(fc, 1)
                            End If
                        Next

                    Next

                    ' Generate the first two columns
                    Dim sb As New StringBuilder()
                    Dim fmt = "{0," & colWidths(0) & "}"
                    sb.AppendFormat(fmt, x.RestCode)
                    sb.Append(" ")
                    fmt = "{0," & colWidths(1) & "}"
                    sb.AppendFormat(fmt, rd.Day)
                    sb.Append(" ")

                    ' Generate the columns with food consumption counts
                    Dim n = 0
                    For Each kvp In Foods
                        If dayFoodCounts.ContainsKey(kvp.Key) Then
                            sb.Append(String.Format("{0," & colWidths(n + 2) & "}", dayFoodCounts(kvp.Key)) & " ")
                        Else
                            ' no count for this food item, so fill it with spaces
                            sb.Append(New String(" "c, colWidths(n + 2) + 1))
                        End If

                        n += 1
                    Next

                    sw.WriteLine(sb.ToString())

                Next

            Next

        End Using

        Console.WriteLine("Done.")
        Console.ReadLine()

    End Sub

End Module

鉴于问题中的示例数据,以上代码生成了具有以下内容的文件:

Restaurant Day Rice Beans Banana Meat Snacks Potato Spinach Fish Aubergine Peas Egg Chicken Cheese Onion Carrots Brocolli Asparagus Garlic Cabbage Coconut Yam Naan Lentils
001        1                               3      3                           1           1                                              1                   3              
001        2                               2      3            1         1                                                                       3           2              
001        3                                              2    1                          3                                              2       1                          
040        4                                              3              1                4                                              3       1                          

答案 1 :(得分:0)

(在我以前的解决方案中,我错误地认为数字是计数,每列代表一篇文章,情况并非如此。这是我的新解决方案。)

我将读取文件和写入表分开。为了能够轻松表示文件的内容,让我们创建一个表示食物的类。

Public Class Food
    Public Property Restaurant As String
    Public Property Day As Integer
    Public Property ArticleNo As Integer
    Public Property Quantity As Integer
End Class

Quantity属性不是严格必需的,因为它始终为1。但是在将来文件格式发展的情况下拥有它似乎是合乎逻辑的。

现在我们可以像这样读取文件

Public Function ReadFoodFile(inputPath As String) As List(Of Food)
    Dim foodList = New List(Of Food)
    For Each line As String In File.ReadLines(inputPath)
        Dim parts As String() = line.Split(",")
        If parts.Length > 2 Then 'Make sure we don't try to read an empty line,
            '                     e.g.at the end of the file.
            Dim dayNo As Integer = CInt(parts(1))
            For i = 2 To parts.Length - 1
                Dim articleNo As Integer
                If Int32.TryParse(parts(i), articleNo) AndAlso articleNo <> 0 Then
                    Dim food = New Food()
                    food.Restaurant = parts(0)
                    food.Day = dayNo
                    food.ArticleNo = articleNo
                    food.Quantity = 1
                    foodList.Add(food)
                End If
            Next
        End If
    Next
    Return foodList
End Function

读取文件的功能将输入路径作为参数,并返回食物列表,其中每个条目对应一个餐厅,一天和一件食物。

这是容易的部分。编写表格非常复杂,因为我们必须按餐厅和日期以及每一行的文章进行分组。然后,我们必须能够通过文章编号查找该文章。我们需要一个代表文章的类:

Public Class Article
    Public Property ArticleNo As Integer
    Public Property Name As String

    Public Sub New(articleNo As Integer, name As String)
        Me.ArticleNo = articleNo
        Me.Name = name
    End Sub

    Private Shared _allArticles = New Article() {
        New Article(1, "Rice"), New Article(2, "Beans"), New Article(3, "Yam"), New Article(4, "Meat"),
        New Article(5, "Snack"), New Article(6, "Coke"), New Article(7, "Burger"), New Article(8, "Meal8"),
        New Article(9, "Meal9"), New Article(10, "Meal10"), New Article(11, "M11"), New Article(12, "M12"),
        New Article(13, "M13"), New Article(14, "M14"), New Article(15, "M15"), New Article(16, "M16"),
        New Article(17, "M17"), New Article(18, "M18"), New Article(19, "M19"), New Article(20, "M20"),
        New Article(21, "M21"), New Article(22, "M22"), New Article(23, "M23")
    }

    Shared ReadOnly Property AllArticles() As Article()
        Get
            Return _allArticles
        End Get
    End Property
End Class

除文章编号外和名称,它包含一个返回文章列表的共享属性。在现实生活中的应用中,文章列表可能应该从文件或数据库中读取,而不是硬编码。

现在,我们可以编写表的Sub了。它大量使用LINQ,并使用自VB / VS 2017起可用的新ValueTuple类型。

Public Sub WriteFoodTable(outputPath As String, foods As IEnumerable(Of Food))
    Const ColumnSize = 8

    'Create an IEnumerable(Of (Restaurant As String,
    '                          Day As Integer,
    '                          Articles As Dictionary(Of Integer, Integer))
    '                         )
    '                     )
    ' where the dictionary stores article quantities using the article no. as key.
    Dim groupedFood = From food In foods
                      Group By food.Restaurant, food.Day Into g1 = Group
                      Select (
                          Restaurant:=Restaurant, Day:=Day,
                          Articles:=
                            (From x In g1
                             Group By x.ArticleNo Into g2 = Group
                             Select (ArticleNo:=ArticleNo,
                                     Quantity:=g2.Sum(Function(f) f.Quantity))
                            ).ToDictionary(Function(a) a.ArticleNo, Function(a) a.Quantity)
                      )

    Using writer As New StreamWriter(outputPath)
        ' Write header
        writer.Write("Rest  Day")
        For Each art In Article.AllArticles
            writer.Write(art.Name.PadLeft(ColumnSize))
        Next
        writer.WriteLine()

        ' Write rows
        For Each row In groupedFood
            writer.Write(row.Restaurant.PadRight(5))
            writer.Write(row.Day.ToString().PadLeft(4))
            For Each art In Article.AllArticles
                Dim quantity As Integer
                row.Articles.TryGetValue(art.ArticleNo, quantity) ' yields 0 if not found.
                writer.Write(quantity.ToString().PadLeft(ColumnSize))
            Next
            writer.WriteLine()
        Next
    End Using
End Sub

把东西放在一起

Dim foods As List(Of Food) = ReadFoodFile(inputPath)
WriteFoodTable(outputPath, foods)

另请参阅: