Excel - 在VBA中,如何使用每行1个值在矩阵中查找所有可能的值总和?

时间:2015-02-18 19:29:52

标签: excel-vba matrix vba excel

我已经为这个答案搜索了一下,但没有什么能解决我的问题。这就是我正在做的事情......

我有一个包含[edit:16rows x 9cols]矩阵的工作表。矩阵的单元格是正整数和负整数。以下是部分数据的示例:

enter image description here

我需要找到所有可能的Summations,条件是每行只能选择一个值。换句话说,我会在每一行中取第一个值并将它们相加。然后,我将获取第一行的第二个值并将其添加到所有其他行'第一个值... on和on,直到我找到每行的最后一个值的总和。

首先,我想存储(-144,-16,0,-96,-74,0,589,-61,-55,-18,-66,0,-279的和) ,-24,-43,-406)。存储的下一个总和将是(-5,-16,0,-96,-74,0,589,-61,-55,-18,-66,0,-279,-24,-43, - 406)。

我试图想出一种巧妙的方法来使用For循环来使用GoTo语句,但我对任何想法持开放态度。也许是一个递归函数?

1 个答案:

答案 0 :(得分:2)

有可能通过递归来解决这类问题,但我发现没有。

考虑速度表。每个循环向最右边的数字添加一个。如果该数字从9溢出到零,则将一个数字添加到左侧的下一个数字。如果该数字溢出,则向左侧的数字添加一个数字。这一直持续到数字不溢出或最左边的数字溢出。因此,您在车速表上看到的值是:

0 0 0 0
0 0 0 1
0 0 0 2
: : : :
0 0 0 9
0 0 1 0
0 0 1 1
: : : :
0 0 1 9
0 0 2 0
: : : :
0 0 9 9
0 1 0 0
: : : :
9 9 9 9

如果速度表的数字是整数数组中的条目,则一个简单的循环可以循环显示这些值。

对于你的问题:

  • 您的数组中需要16个条目,矩阵中每行一个。
  • 每个数字的值可以在0到55之间,而不是0到9。

通过此更改,您的车速表将从以下位置循环:

  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0

 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55

如果您将列编号从0到55,则每个数字都会告诉您哪一列用于相应的行。

你的车速表循环的一个值是:

 1 45  5 30  8 22  1  0 38 51 14 42 29 31 46  7

它告诉你总结:

Column 1 of first row
Column 45 of second row
Column 5 of third row
Column 30 of fourth row
And so on

另一个循环将提取并求和这些值。

因此外环将使速度表从{0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0}循环至{55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55}。对于车速表上的每个值,内循环计算并存储总和。

第2节

更新新要求的原始代码几乎不需要更改。

在原版中,我有一个“车速表”,从(0,0,0,......)循环到(55,55,55,...),每个“车轮”的值从0到55.我现在有了添加了一个数组,它给出了每个“轮”的最大值。例如,第一个“轮”可以取值0到5,它们对应于从矩阵中提取的六个值:-144 -5 0 12 16 20。

  5  2  4  8  2  1  1  3  1  5  7  1  8  3  4  5    New maximum values

  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0    Minimum values

 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55    Old maximum values

我将矩阵图像中的数据输入到新工作簿的Sheet1中:

Worksheet matrix

我的宏将该矩阵导入变量数组。每次访问变量类型变量都有开销,因此我将数据复制到Long数组。为了演示宏正确创建数组,我输出:

Row Lst ---0 ---1 ---2 ---3 ---4 ---5 ---6 ---7 ---8
  0   5 -144   -5    0   12   16   20
  1   2  -16    0   50
  2   4    0    5    8   11   70
  3   8  -96  -57  -47  -45  -29  -13   -2    0    3
  4   2  -74  -18    0
  5   1    0    8
  6   1  589    0
  7   3  -61  -44  -26    0
  8   1  -55    0
  9   5  -18    0    9   18   50   58
 10   7  -66  -36    0    2   16   46   62   82
 11   1    0    8
 12   8 -279 -272 -253 -229 -165 -121  -74  -38    0
 13   3  -24  -19  -17    0
 14   4  -43  -27  -21   -9    0
 15   5 -406  -91  -64  -29   -3    0

列“Lst”给出每行的最后一个条目。

对于宏的第一个版本,我将前200个和的诊断信息输出到Sheet2:

First 200 sums

这足以让我相信宏正确地使车速表循环,正确地从矩阵中提取值并正确地对这些值求和。

对于宏的第二个版本,我删除了所有诊断代码并将总和输出到文件。我在1,000,000总和之后切换文件以保持文件大小可管理。创建文件50后,宏退出。创建这50个文件需要13分15秒。第一个文件的顶部是:

File 0001

然后我切换到Visual Basic 2010.我创建了一个简单形式的Windows应用程序:

Form

我有六个控件,其中四个命名如图所示,另一个lblMessage直到最后才可见。 lblFileNumMax的值8000在运行时被计算出的要创建的文件数替换。每次创建新文件时,lblFileNumCrnt的值都会更新。每分钟创建大约100个,这提供了充分的进展指示。

我本可以从Excel加载矩阵,但我认为硬编码更容易。除此之外,VBA版本的代码差别不大。我保留了在创建50个文件后停止生成的陷阱,并使用批处理文件检查VBA文件是否与VB文件相同:

Del compare.txt
comp "Sums 0001.txt" "Sums 00001VBA.txt" <N.txt >>Compare.txt
comp "Sums 0002.txt" "Sums 00002VBA.txt" <N.txt >>Compare.txt
comp "Sums 0003.txt" "Sums 00003VBA.txt" <N.txt >>Compare.txt
comp "Sums 0004.txt" "Sums 00004VBA.txt" <N.txt >>Compare.txt
comp "Sums 0005.txt" "Sums 00005VBA.txt" <N.txt >>Compare.txt

然后我删除了陷阱,让程序创建所有8063文件,在我的2.1 GHz笔记本电脑上花了51分45秒。

我无法发布VBA代码,因为我不小心将它与8063文件一起删除了40 Gb,这足以导致我的回收站溢出。

VB.net代码如下。

Option Strict On
Imports System.IO

Public Class Form1
  Dim fileOut As StreamWriter
  Private Sub cmdStart_Click(sender As System.Object, e As System.EventArgs) Handles cmdStart.Click

    Dim matrix(,) As Integer = {{-144, -5, 0, 12, 16, 20, 0, 0, 0}, _
                                {-16, 0, 50, 0, 0, 0, 0, 0, 0}, _
                                {0, 5, 8, 11, 70, 0, 0, 0, 0}, _
                                {-96, -57, -47, -45, -29, -13, -2, 0, 3}, _
                                {-74, -18, 0, 0, 0, 0, 0, 0, 0}, _
                                {0, 8, 0, 0, 0, 0, 0, 0, 0}, _
                                {589, 0, 0, 0, 0, 0, 0, 0, 0}, _
                                {-61, -44, -26, 0, 0, 0, 0, 0, 0}, _
                                {-55, 0, 0, 0, 0, 0, 0, 0, 0}, _
                                {-18, 0, 9, 18, 50, 58, 0, 0, 0}, _
                                {-66, -36, 0, 2, 16, 46, 62, 82, 0}, _
                                {0, 8, 0, 0, 0, 0, 0, 0, 0}, _
                                {-279, -272, -253, -229, -165, -121, -74, -38, 0}, _
                                {-24, -19, -17, 0, 0, 0, 0, 0, 0}, _
                                {-43, -27, -21, -9, 0, 0, 0, 0, 0}, _
                                {-406, -91, -64, -29, -3, 0, 0, 0, 0}}

    Dim lastEntryPerRow() As Integer = {5, 2, 4, 8, 2, 1, 1, 3, 1, 5, 7, 1, 8, 3, 4, 5}

    Const sumsPerFile As Long = 1000000

    Dim fileOutNum As Integer
    Dim fileOutNumMax As Long
    Dim finished As Boolean
    Dim numSums As Integer
    Dim pathProg As String
    Dim posChar As Int32
    Dim speedo() As Integer
    Dim sumCrnt As Integer
    Dim rowCrnt As Integer
    Dim rowMax As Integer = matrix.GetUpperBound(0)
    Dim timeStart As Long

    cmdStart.Visible = False
    cmdExit.Visible = False

    ' Extract folder containing program
    pathProg = Application.ExecutablePath
    posChar = InStrRev(pathProg, "\")
    If posChar <> 0 Then
      ' Discard the name of the program
      pathProg = Mid(pathProg, 1, posChar)
    End If

    ' Initialise Speedo to all zeros
    ReDim speedo(rowMax)
    For rowCrnt = 0 To rowMax
      speedo(rowCrnt) = 0
    Next

    ' Calculate number of files to be created
    fileOutNumMax = 1
    For rowCrnt = 0 To rowMax
      fileOutNumMax *= CLng(lastEntryPerRow(rowCrnt) + 1)
    Next
    fileOutNumMax = CInt(fileOutNumMax / sumsPerFile)

    lblFileNumMax.Text = CStr(fileOutNumMax)

    ' Initialise control variables
    numSums = 0
    fileOutNum = 1
    finished = False
    lblFileNumCrnt.Text = CStr(fileOutNum)
    Application.DoEvents()

    timeStart = (Hour(DateTime.Now) * 24 + Minute(DateTime.Now)) * 60 + Second(DateTime.Now)

    Do While True

      ' False means overwrite if file already exists
      fileOut = New StreamWriter(pathProg & "\Sums " & Format(fileOutNum, "0000") & ".txt", False)

      Do While True

        ' Output sum identified by current value of Speedo
        sumCrnt = 0
        numSums = numSums + 1
        For rowCrnt = 0 To rowMax
          sumCrnt += matrix(rowCrnt, speedo(rowCrnt))
        Next
        fileOut.WriteLine(sumCrnt)

        ' Generate next value for Speedo
        ' Process entries from left to right
        For rowCrnt = 0 To rowMax
          If speedo(rowCrnt) = lastEntryPerRow(rowCrnt) Then
            ' This column is about to overflow
            speedo(rowCrnt) = 0
            If rowCrnt = rowMax Then
              ' rightmost entry has overflowed. All done
              finished = True
              Exit Do
            End If
            ' Continue with For-Loop to step next column to right
          Else
            ' This column is not about to overflow
            speedo(rowCrnt) = speedo(rowCrnt) + 1
            ' Have finished generation
            Exit For
          End If
        Next

        If numSums >= sumsPerFile Then
          Exit Do
        End If

      Loop

      fileOut.Close()
      fileOut = Nothing
      numSums = 0
      fileOutNum = fileOutNum + 1
      'If fileOutNum >= 51 Then
      '  Exit Do
      'End If
      If finished Then
        Exit Do
      End If
      lblFileNumCrnt.Text = CStr(fileOutNum)
      Application.DoEvents()

    Loop

    Debug.Print(CStr((Hour(DateTime.Now) * 24 + Minute(DateTime.Now)) * 60 + _
                      Second(DateTime.Now) - timeStart) & " seconds")

    cmdExit.Visible = True

  End Sub
  Private Sub cmdExit_Click(sender As System.Object, e As System.EventArgs) Handles cmdExit.Click

    If fileOut IsNot Nothing Then
      fileOut.Close()
      fileOut = Nothing
    End If

    Me.Close()

  End Sub
End Class