如何将DataTable中的单行中的多列引用为二维数组



基本问题是,如何将DataTable中单行中的多个连续列引用为可以使用For Next结构处理的二维数组?背景是:

有问题的程序从.csv文件加载数据,其中每行包含一个人的基本身份信息,然后是他们对20多个问题的数字答案。程序循环浏览.csv文件的每一行,并识别与当前行具有最高精确答案匹配数的其他五行。

DataTable似乎是读取.csv文件的最佳结构,但不确定如何将每行的最后x列引用为表单答案(人,问题)的数组。

如果这看起来真的很容易或完全不切实际,我应该做以下免责声明:程序代码已经写好并可以工作,但我正在把它从QuickBASIC 4(是的,我确实说过QB4…)重新编码到VB.NET。这个程序基本上是一个约会程序,在过去20年左右的时间里,我每年都会运行一次,当地的学校会把火柴作为筹款活动出售。已经到了Windows 7和Windows XP的最新补丁版本都无法运行QB4的地步,所以我下载了桌面版VS Express,并以此为机会学习VB.NET。我已经做了很多(非窗口的)VBScript应用程序脚本,但对VB6的一些真正轻松的涉猎是我使用传统VB的唯一体验。这里的每个人都很清楚,.NET中的文件I/O与VB6或以前的版本非常不同。这就是我现在要对抗的…

回答Zohar的问题/评论:
下面是.csv文件格式的示例。实际的文件有几百行长,但格式完全相同。为了隐私起见,姓名和电话号码都被更改了。字段按顺序为:

姓氏
名字
电话号码(如果给定,则为占位符)
性别(1=M;2=F)
问题1(1-4)的答案
问题2(1-4)的答案
……
问题24(1-4)的答案

Mouse,Mickey,xxx-xxxx,1,2,3,3,2,3,1,3,4,2,1,4,3,1,1,2,1,2,1,1,1,2,1,1,4
Mouse,Minnie,555-9931,2,1,3,1,2,1,2,3,3,3,4,4,2,4,1,2,3,4,4,4,1,2,1,1,4
Duck,Donald,555-7024,1,2,3,4,2,4,3,4,2,2,1,4,2,4,1,2,1,1,2,1,3,2,1,1,1
McDuck,Scrooge,555-4824,1,2,3,3,2,1,2,4,3,2,4,4,2,4,1,4,2,2,4,4,3,2,1,1,4
GoodWitch,Wendy,xxx-xxxx,2,2,2,4,2,1,2,4,4,3,4,2,2,1,1,2,1,1,4,4,4,4,1,3,1

之所以使用二维数组,是为了创建一个按用户和问题编号列出答案的单一变量数据库。请参阅下面的实际现有QB4代码的排序部分。下面的二维数组是StudentAnswer(matchFrom,question)。

    For matchFrom = 1 To numberSheets
        '
        'The following section of code finds the top maximumToMatch groups of n
        'matching questions per sheet
        '
        For x = 1 To maximumToMatch
            topMatches(x) = 0
            sheetsMatched(x) = 0
        Next x
        For matchTo = 1 To numberSheets
            If StudentSex(matchFrom) <> StudentSex(matchTo) Then
                numberMatched(matchTo) = 0
                highMatch = 0
                For question = 1 To numberQuestions
                    If StudentAnswer(matchFrom, question) = StudentAnswer(matchTo, question) Then
                        numberMatched(matchTo) = numberMatched(matchTo) + 1
                    End If
                Next question
                If numberMatched(matchTo) = topMatches(1) Then
                    sheetsMatched(1) = sheetsMatched(1) + 1
                End If
                If numberMatched(matchTo) > topMatches(1) Then
                    match = maximumToMatch
                    done = False
                    Do
                        If numberMatched(matchTo) = topMatches(match) Then
                            sheetsMatched(match) = sheetsMatched(match) + 1
                            done = True
                        End If
                        If numberMatched(matchTo) > topMatches(match) Then
                            For x = 1 To match - 1
                                topMatches(x) = topMatches(x + 1)
                                sheetsMatched(x) = sheetsMatched(x + 1)
                            Next x
                            topMatches(match) = numberMatched(matchTo)
                            sheetsMatched(match) = 1
                            done = True
                        Else
                            match = match - 1
                        End If
                    Loop Until done
                End If
            Else
                numberMatched(matchTo) = 0
            End If
        Next matchTo
    ...
    <additional code to narrow it down to a fixed number of sheet matches>
    Next matchFrom

预测另外两个可能的问题:

现有的代码是为了使M与F匹配而编写的,反之亦然。我想在重写时让它更灵活,但这是一个农村地区,我真的不确定他们是否准备好了。。。

数据文件采用.csv格式的原因是缺少为程序编写的正式数据输入前端。它一直在待办事项列表中,但与此同时,由于它每年只运行一次,Excel一直是我的朋友。。。如果一切顺利,我将在VB.NET重写期间设计一个数据输入屏幕。

提前感谢所有花时间阅读本文的人。

既然LINQ可以查询对象列表(因为.NET 3.5,所以不是新的),使用DataTable可能不是最好的选择,因为数据来自CSV文件而不是数据库(LINQ也可以用于数据库)。

所以,这并不是你最初问题的答案,但如果你输入了这样的东西:

Joe User,1,2,3,4,5
Jane User,2,2,3,4,6
Jack User,3,4,5,2,8
Jill User,5,3,1,8,6

您可以定义一个类来存储数据:

Public Class UserInfo
    Public Property Name As String
    Public Property Answers As List(Of Integer) = New List(Of Integer)()
    Public Function MatchRating(other As UserInfo) As Integer
        Dim rating As Integer = 0
        For i = 0 To Me.Answers.Count - 1
            If Me.Answers(i) = other.Answers(i) Then
                rating += 1
            End If
        Next
        Return rating
    End Function
End Class

然后,您可以将CSV数据读取到UserInfo对象列表中:

    Dim users = File.ReadLines("Data.csv").Select(
        Function(line)
            Dim parts = line.Split(","c)
            Dim user = New UserInfo() With {.Name = parts(0)}
            user.Answers.AddRange(parts.Skip(1).Select(Function(str) CInt(str)))
            Return user
        End Function
    ).ToList()

然后,您可以用这样的方法找到最佳匹配,该方法在用户中循环,并使用LINQ查询根据匹配的答案数量找到前5个匹配(UserInfo.MatchRating函数),跳过任何不匹配的答案(rating > 0):

    For Each user In users
        Console.WriteLine("{0}:{1}", user.Name, String.Join(",", user.Answers))
        Dim bestMatches = From u In users
                          Where u IsNot user
                          Let rating = u.MatchRating(user)
                          Where rating > 0
                          Order By rating Descending
                          Take 5
                          Select New With {.Name = u.Name, .Rating = rating}
        For Each match In bestMatches
            Console.WriteLine("  Match: {0}, rating: {1}", match.Name, match.Rating)
        Next
    Next

您需要为UserInfo类添加用于实际身份信息的属性,并调整代码以匹配。

您还需要确保您的项目选项设置正确,并且需要适当的导入/引用,例如:

Option Explicit On
Option Infer On
Option Strict On
Imports System.IO

作为参考,我的测试结果是(可怜的杰克,我想你可能需要根据性偏好进行调整,例如Where u IsNot user AndAlso u.Sex <> user.Sex):

Joe User:1,2,3,4,5
  Match: Jane User, rating: 3
Jane User:2,2,3,4,6
  Match: Joe User, rating: 3
  Match: Jill User, rating: 1
Jack User:3,4,5,2,8
Jill User:5,3,1,8,6
  Match: Jane User, rating: 1

最新更新