可以通过递归解决这类问题,但我发现没有递归会更容易。
考虑一个速度计。每个循环将最右边的数字加一。如果该数字从 9 溢出到 0,则在左边的下一个数字上加 1。如果该数字溢出,则在其左侧的数字上加一。这一直持续到一个数字没有溢出或最左边的数字溢出。因此,您在速度计上看到的值是:
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 中:
我的宏将该矩阵导入到变量数组中。每次访问变体类型的变量都会产生开销,因此我将数据复制到了 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:
这足以让我相信宏正确地循环速度计,正确地从矩阵中提取值并将这些值正确地求和。
对于宏的第二个版本,我删除了所有诊断代码并将总和输出到文件中。我在 1,000,000 次总和后切换文件以保持文件大小可管理。创建文件 50 后,宏退出。创建这 50 个文件需要 13 分 15 秒。第一个文件的顶部是:
然后我切换到 Visual Basic 2010。我用一个简单的表单创建了一个 Windows 应用程序:
我有六个控件,其中四个的名称如图所示,另一个 lblMessage 直到最后才可见。 lblFileNumMax 的值 8000 在运行时被计算的要创建的文件数替换。每次创建新文件时,lblFileNumCrnt 的值 1 都会更新。每分钟创建大约 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
然后我移除了陷阱,让程序在我的 2.1 GHz 笔记本电脑上创建所有 8063 文件,这需要 51 分 45 秒。
我无法发布 VBA 代码,因为我不小心删除了它以及总共 40 Gb 的 8063 文件,这些文件大到足以导致我的回收站溢出。
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