我正在尝试编写一个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
答案 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)
另请参阅: