我定义了以下数组Dim myArray(10,5) as Long
,并希望对其进行排序。最好的方法是什么?
我需要处理大量数据,如1000 x 5 Matrix。它主要包含数字和日期,需要根据某列进行排序
答案 0 :(得分:24)
这是一个用于VBA的多列和单列QuickSort,根据Jim Rech在Usenet上发布的代码示例进行了修改。
注意:
你会注意到我做了一个很多更多的防御性编码,而不是你在网上的大多数代码示例中看到的:这是一个Excel论坛,你已经有了预测空值和空值...或者如果源数组来自(比方说)第三方实时市场数据源,则在数组中嵌套数组和对象。
空值和无效项目将发送到列表末尾。
您的电话将是:
QuickSort MyArray,,,2...传递'2'作为列,以排序和排除通过搜索域上下界的可选参数。
[已编辑] - 修复了< code>中的奇怪格式化故障。标签,似乎在代码注释中有超链接问题。
我切除的超链接是Detecting an Array Variant in VBA。
Public Sub QuickSortArray(ByRef SortArray As Variant, Optional lngMin As Long = -1, Optional lngMax As Long = -1, Optional lngColumn As Long = 0)
On Error Resume Next
'Sort a 2-Dimensional array
' SampleUsage: sort arrData by the contents of column 3
'
' QuickSortArray arrData, , , 3
'
'Posted by Jim Rech 10/20/98 Excel.Programming
'Modifications, Nigel Heffernan:
' ' Escape failed comparison with empty variant
' ' Defensive coding: check inputs
Dim i As Long
Dim j As Long
Dim varMid As Variant
Dim arrRowTemp As Variant
Dim lngColTemp As Long
If IsEmpty(SortArray) Then
Exit Sub
End If
If InStr(TypeName(SortArray), "()") < 1 Then 'IsArray() is somewhat broken: Look for brackets in the type name
Exit Sub
End If
If lngMin = -1 Then
lngMin = LBound(SortArray, 1)
End If
If lngMax = -1 Then
lngMax = UBound(SortArray, 1)
End If
If lngMin >= lngMax Then ' no sorting required
Exit Sub
End If
i = lngMin
j = lngMax
varMid = Empty
varMid = SortArray((lngMin + lngMax) \ 2, lngColumn)
' We send 'Empty' and invalid data items to the end of the list:
If IsObject(varMid) Then ' note that we don't check isObject(SortArray(n)) - varMid *might* pick up a valid default member or property
i = lngMax
j = lngMin
ElseIf IsEmpty(varMid) Then
i = lngMax
j = lngMin
ElseIf IsNull(varMid) Then
i = lngMax
j = lngMin
ElseIf varMid = "" Then
i = lngMax
j = lngMin
ElseIf VarType(varMid) = vbError Then
i = lngMax
j = lngMin
ElseIf VarType(varMid) > 17 Then
i = lngMax
j = lngMin
End If
While i <= j
While SortArray(i, lngColumn) < varMid And i < lngMax
i = i + 1
Wend
While varMid < SortArray(j, lngColumn) And j > lngMin
j = j - 1
Wend
If i <= j Then
' Swap the rows
ReDim arrRowTemp(LBound(SortArray, 2) To UBound(SortArray, 2))
For lngColTemp = LBound(SortArray, 2) To UBound(SortArray, 2)
arrRowTemp(lngColTemp) = SortArray(i, lngColTemp)
SortArray(i, lngColTemp) = SortArray(j, lngColTemp)
SortArray(j, lngColTemp) = arrRowTemp(lngColTemp)
Next lngColTemp
Erase arrRowTemp
i = i + 1
j = j - 1
End If
Wend
If (lngMin < j) Then Call QuickSortArray(SortArray, lngMin, j, lngColumn)
If (i < lngMax) Then Call QuickSortArray(SortArray, i, lngMax, lngColumn)
End Sub
......和单列阵列版本:
Public Sub QuickSortVector(ByRef SortArray As Variant, Optional lngMin As Long = -1, Optional lngMax As Long = -1)
On Error Resume Next
'Sort a 1-Dimensional array
' SampleUsage: sort arrData
'
' QuickSortVector arrData
'
' Originally posted by Jim Rech 10/20/98 Excel.Programming
' Modifications, Nigel Heffernan:
' ' Escape failed comparison with an empty variant in the array
' ' Defensive coding: check inputs
Dim i As Long
Dim j As Long
Dim varMid As Variant
Dim varX As Variant
If IsEmpty(SortArray) Then
Exit Sub
End If
If InStr(TypeName(SortArray), "()") < 1 Then 'IsArray() is somewhat broken: Look for brackets in the type name
Exit Sub
End If
If lngMin = -1 Then
lngMin = LBound(SortArray)
End If
If lngMax = -1 Then
lngMax = UBound(SortArray)
End If
If lngMin >= lngMax Then ' no sorting required
Exit Sub
End If
i = lngMin
j = lngMax
varMid = Empty
varMid = SortArray((lngMin + lngMax) \ 2)
' We send 'Empty' and invalid data items to the end of the list:
If IsObject(varMid) Then ' note that we don't check isObject(SortArray(n)) - varMid *might* pick up a default member or property
i = lngMax
j = lngMin
ElseIf IsEmpty(varMid) Then
i = lngMax
j = lngMin
ElseIf IsNull(varMid) Then
i = lngMax
j = lngMin
ElseIf varMid = "" Then
i = lngMax
j = lngMin
ElseIf VarType(varMid) = vbError Then
i = lngMax
j = lngMin
ElseIf VarType(varMid) > 17 Then
i = lngMax
j = lngMin
End If
While i <= j
While SortArray(i) < varMid And i < lngMax
i = i + 1
Wend
While varMid < SortArray(j) And j > lngMin
j = j - 1
Wend
If i <= j Then
' Swap the item
varX = SortArray(i)
SortArray(i) = SortArray(j)
SortArray(j) = varX
i = i + 1
j = j - 1
End If
Wend
If (lngMin < j) Then Call QuickSortVector(SortArray, lngMin, j)
If (i < lngMax) Then Call QuickSortVector(SortArray, i, lngMax)
End Sub
我曾经使用BubbleSort来做这种事情,但是在数组超过1024行之后,它会严重减速。我包含以下代码供您参考:请注意我没有提供ArrayDimensions的源代码,因此除非您重构它,否则不会为您编译 - 或者将其拆分为'Array'和'vector'版本。
Public Sub BubbleSort(ByRef InputArray, Optional SortColumn As Integer = 0, Optional Descending As Boolean = False) ' Sort a 1- or 2-Dimensional array. Dim iFirstRow As Integer Dim iLastRow As Integer Dim iFirstCol As Integer Dim iLastCol As Integer Dim i As Integer Dim j As Integer Dim k As Integer Dim varTemp As Variant Dim OutputArray As Variant Dim iDimensions As Integer iDimensions = ArrayDimensions(InputArray) Select Case iDimensions Case 1 iFirstRow = LBound(InputArray) iLastRow = UBound(InputArray) For i = iFirstRow To iLastRow - 1 For j = i + 1 To iLastRow If InputArray(i) > InputArray(j) Then varTemp = InputArray(j) InputArray(j) = InputArray(i) InputArray(i) = varTemp End If Next j Next i Case 2 iFirstRow = LBound(InputArray, 1) iLastRow = UBound(InputArray, 1) iFirstCol = LBound(InputArray, 2) iLastCol = UBound(InputArray, 2) If SortColumn InputArray(j, SortColumn) Then For k = iFirstCol To iLastCol varTemp = InputArray(j, k) InputArray(j, k) = InputArray(i, k) InputArray(i, k) = varTemp Next k End If Next j Next i End Select If Descending Then OutputArray = InputArray For i = LBound(InputArray, 1) To UBound(InputArray, 1) k = 1 + UBound(InputArray, 1) - i For j = LBound(InputArray, 2) To UBound(InputArray, 2) InputArray(i, j) = OutputArray(k, j) Next j Next i Erase OutputArray End If End Sub
在您需要的时候,这个答案可能已经到了解决问题的时间有点迟了,但是其他人会在谷歌找到类似问题的答案时接听它。
答案 1 :(得分:8)
困难的部分是VBA没有提供直接的方式来交换2D数组中的行。对于每个交换,你将不得不循环5个元素并交换每个元素,这将是非常低效的。
我猜测2D阵列确实不是你应该使用的。每列是否都有特定含义?如果是这样,您是否应该使用用户定义类型的数组,或者是作为类模块实例的对象数组?即使5列没有特定含义,你仍然可以这样做,但是将UDT或类模块定义为只有一个5元素数组的成员。
对于排序算法本身,我会使用普通的'插入排序。 1000项实际上并没有那么大,你可能不会注意到插入排序和快速排序之间的区别,只要我们确保每次交换都不会太慢。如果您执行使用快速排序,则需要仔细编码以确保不会耗尽堆栈空间,这可以完成,但它很复杂,而且快速排序很棘手已经够了。
假设您使用UDT数组,并假设UDT包含名为Field1到Field5的变体,并假设我们要对Field2进行排序(例如),那么代码可能看起来像这样......
Type MyType
Field1 As Variant
Field2 As Variant
Field3 As Variant
Field4 As Variant
Field5 As Variant
End Type
Sub SortMyDataByField2(ByRef Data() As MyType)
Dim FirstIdx as Long, LastIdx as Long
FirstIdx = LBound(Data)
LastIdx = UBound(Data)
Dim I as Long, J as Long, Temp As MyType
For I=FirstIdx to LastIdx-1
For J=I+1 to LastIdx
If Data(I).Field2 > Data(J).Field2 Then
Temp = Data(I)
Data(I) = Data(J)
Data(J) = Temp
End If
Next J
Next I
End Sub
答案 2 :(得分:1)
有时最无脑的答案是最好的答案。
tadaa。不会赢得任何编程奖品,但它可以快速完成工作。
答案 3 :(得分:0)
为了它的价值(我现在无法显示代码......让我看看我是否可以编辑它来发布),我创建了一个自定义对象数组(因此每个属性都带有其中的任何一个元素排序方式),填充一组单元格,每个元素对象属性感兴趣,然后通过vba使用excel排序函数对列进行排序。我确定这可能是一种更有效的排序方式,而不是将其输出到单元格,我还没想到它。这实际上对我有很大帮助,因为当我需要添加维度时,我只是为数组的下一个维度添加了一个let和get属性。
答案 4 :(得分:0)
您可以创建一个包含2列的单独数组。第1列将是您的排序,2是其他数组中的行。按列1对此数组进行排序(仅在交换时切换两列)。然后,您可以根据需要使用2个数组来处理数据。虽然
,但巨大的数组可能会给你带来内存问题答案 5 :(得分:0)
这是一个艰难的过程,因为它取决于许多参数,但是在分析了许多算法之后,我认为这一算法总体上具有出色的性能。在速度不是很快的机器上,我在1秒内对3列数组进行了排序,其中包含10万行。我尝试了更少的行,只是一瞬间,但是对于一百万行,我在9到26秒之间得到了不同的数据(未排序数据的百分比仍然会产生影响)。
一种用于升序的例程,另一种用于降序的排序。 iCol
,这是第二个参数,是应该在其上对数组进行排序的列的索引。
Public Sub MedianThreeQuickSort1_2D_Asc(ByRef pvarArray As Variant, _
ByVal iCol As Integer, _
Optional ByVal plngLeft As Long, _
Optional ByVal plngRight As Long)
'Grade A+
'NOTE: recursive routine, omit plngLeft & plngRight; they are used internally during recursion
Dim j As Integer
Dim lngFirst As Long
Dim lngLast As Long
Dim varMid As Variant
Dim lngIndex As Long
Dim varSwap As Variant
Dim a As Long
Dim b As Long
Dim c As Long
If plngRight = 0 Then
plngLeft = LBound(pvarArray, 1)
plngRight = UBound(pvarArray, 1)
End If
lngFirst = plngLeft
lngLast = plngRight
lngIndex = plngRight - plngLeft + 1
a = Int(lngIndex * Rnd) + plngLeft
b = Int(lngIndex * Rnd) + plngLeft
c = Int(lngIndex * Rnd) + plngLeft
If pvarArray(a, iCol) <= pvarArray(b, iCol) And pvarArray(b, iCol) <= pvarArray(c, iCol) Then
lngIndex = b
Else
If pvarArray(b, iCol) <= pvarArray(a, iCol) And pvarArray(a, iCol) <= pvarArray(c, iCol) Then
lngIndex = a
Else
lngIndex = c
End If
End If
varMid = pvarArray(lngIndex, iCol)
Do
Do While pvarArray(lngFirst, iCol) < varMid And lngFirst < plngRight
lngFirst = lngFirst + 1
Loop
Do While varMid < pvarArray(lngLast, iCol) And lngLast > plngLeft
lngLast = lngLast - 1
Loop
If lngFirst <= lngLast Then
For j = LBound(pvarArray, 2) To UBound(pvarArray, 2)
varSwap = pvarArray(lngLast, j)
pvarArray(lngLast, j) = pvarArray(lngFirst, j)
pvarArray(lngFirst, j) = varSwap
Next j
lngFirst = lngFirst + 1
lngLast = lngLast - 1
End If
Loop Until lngFirst > lngLast
If (lngLast - plngLeft) < (plngRight - lngFirst) Then
If plngLeft < lngLast Then MedianThreeQuickSort1_2D_Asc pvarArray, iCol, plngLeft, lngLast
If lngFirst < plngRight Then MedianThreeQuickSort1_2D_Asc pvarArray, iCol, lngFirst, plngRight
Else
If lngFirst < plngRight Then MedianThreeQuickSort1_2D_Asc pvarArray, iCol, lngFirst, plngRight
If plngLeft < lngLast Then MedianThreeQuickSort1_2D_Asc pvarArray, iCol, plngLeft, lngLast
End If
End Sub
Public Sub MedianThreeQuickSort1_2D_Desc(ByRef pvarArray As Variant, _
ByVal iCol As Integer, _
Optional ByVal plngLeft As Long, _
Optional ByVal plngRight As Long)
'Grade A+
'NOTE: recursive routine, omit plngLeft & plngRight; they are used internally during recursion
Dim j As Integer
Dim lngFirst As Long
Dim lngLast As Long
Dim varMid As Variant
Dim lngIndex As Long
Dim varSwap As Variant
Dim a As Long
Dim b As Long
Dim c As Long
If plngRight = 0 Then
plngLeft = LBound(pvarArray, 1)
plngRight = UBound(pvarArray, 1)
End If
lngFirst = plngLeft
lngLast = plngRight
lngIndex = plngRight - plngLeft + 1
a = Int(lngIndex * Rnd) + plngLeft
b = Int(lngIndex * Rnd) + plngLeft
c = Int(lngIndex * Rnd) + plngLeft
If pvarArray(a, iCol) <= pvarArray(b, iCol) And pvarArray(b, iCol) <= pvarArray(c, iCol) Then
lngIndex = b
Else
If pvarArray(b, iCol) <= pvarArray(a, iCol) And pvarArray(a, iCol) <= pvarArray(c, iCol) Then
lngIndex = a
Else
lngIndex = c
End If
End If
varMid = pvarArray(lngIndex, iCol)
Do
Do While pvarArray(lngFirst, iCol) > varMid And lngFirst < plngRight
lngFirst = lngFirst + 1
Loop
Do While varMid > pvarArray(lngLast, iCol) And lngLast > plngLeft
lngLast = lngLast - 1
Loop
If lngFirst <= lngLast Then
For j = LBound(pvarArray, 2) To UBound(pvarArray, 2)
varSwap = pvarArray(lngLast, j)
pvarArray(lngLast, j) = pvarArray(lngFirst, j)
pvarArray(lngFirst, j) = varSwap
Next j
lngFirst = lngFirst + 1
lngLast = lngLast - 1
End If
Loop Until lngFirst > lngLast
If (lngLast - plngLeft) < (plngRight - lngFirst) Then
If plngLeft < lngLast Then MedianThreeQuickSort1_2D_Desc pvarArray, iCol, plngLeft, lngLast
If lngFirst < plngRight Then MedianThreeQuickSort1_2D_Desc pvarArray, iCol, lngFirst, plngRight
Else
If lngFirst < plngRight Then MedianThreeQuickSort1_2D_Desc pvarArray, iCol, lngFirst, plngRight
If plngLeft < lngLast Then MedianThreeQuickSort1_2D_Desc pvarArray, iCol, plngLeft, lngLast
End If
End Sub
答案 6 :(得分:0)
在对多列数组进行排序时,我不会重新排列元素。相反,我通过了另一个具有相同数量元素的数组S,并对项目1,2,3,....
进行编号然后,我使用S中的值作为要排序的列的索引,当我需要交换元素时,我交换S中的值。
从排序返回时,如果需要,我可以根据S中的排序顺序重新排列原始数组。采用快速排序很容易做到这一点。
答案 7 :(得分:0)
我有类似的Doubles数组要排序,因此我决定编写一个本机.dll。
为了进行测试,我使用的是64位整数,因此您可以使用它对Long
和ULong
数组的最后一个维度进行排序。
<DllImport("Arrayman.dll", EntryPoint:="SortLng")>
Sub sort(ByRef Array1stItem As Long, ByRef Indices1stItem As Integer, ByVal nItemsToSort As Long)
'Note: For sorting ULong integers, replace EntryPoint:="SortLng" with EntryPoint:="SortULng"
End Sub
在您的示例中,您将其称为
Dim idx(5)
sort(myArray(3,0), idx(0), idx.count)
将项目从(3, 0)
到(3, 5)
进行排序。
最低的数字为myArray(3, idx(0))
,最高的数字为myArray(3, idx(5))
。
ArrayMan.dll ,更多信息和示例,on GitHub
答案 8 :(得分:-1)
在我看来,上面的QuickSort代码无法处理空格。我有一个数字和空格的数组。当我对这个数组进行排序时,带有空格的记录会在带有数字的记录之间混淆。我花了很多时间才发现,所以当你使用这段代码时,记住它可能是件好事。
最好的, 烫发
答案 9 :(得分:-1)
这是一个很好的强大代码。 出于习惯对其进行了测试。不是没有。
varMid = SortArray((lngMin + lngMax) \ 2, lngColumn)
'当(lngMin + lngMax)\ 2不是整数时,捕获错误。
'替换为
Dim row_Mid as long
row_Mid = Int((lngMin + lngMax) \ 2)
varMid = SortArray(row_Mid, lngColumn)
先生们,编写测试...