VBA(Excel)中的减法范围 我想做什么
我正在尝试编写一个函数来减去Excel范围。它应该有两个输入参数:范围A和范围B。它应该返回一个范围对象,该对象由属于范围A的单元格和不属于范围B的单元格组成(如中所示) 我试过的 我在网上看到过一些使用临时工作表来完成这项工作的例子(速度很快,但可能会引入一些受保护工作簿的问题等),还有一些其他例子是逐单元检查第一个范围与第二个范围的交点(速度非常慢) 经过一番思考,我想出了这段代码{1},它运行得更快,但仍然很慢。根据第二个范围的复杂程度,从代表整个工作表的范围中减去需要1到5分钟 当我查看代码试图找到使其更快的方法时,我看到了应用分而治之范式的可能性,我做到了{2}。但这反而使我的代码变慢了。我不是一个喜欢CS的人,所以我可能做错了什么,或者这个算法根本不是应该使用分治的算法,我不知道 我也尝试过使用递归重写它,但这需要花费很长时间才能完成,或者(更经常)抛出堆栈外空间错误。我没有保存代码 我所能做的唯一(稍微)成功的改进是添加一个翻转开关{3},首先遍历行,然后(在下一次调用中)遍历列,而不是在同一次调用中同时遍历两个列,但效果不如我所希望的好。现在我看到,尽管我们在第一次调用中没有遍历所有行,但在第二次调用中,我们仍然循环遍历与第一次调用相同数量的行,只是这些行稍微短一点:) 如果您能帮助我改进或重写此函数,我将不胜感激,谢谢 解决方案,基于 ,非常感谢您提供的答案!我想我会对它进行一些修改:VBA(Excel)中的减法范围 我想做什么,vba,excel,range,Vba,Excel,Range,我正在尝试编写一个函数来减去Excel范围。它应该有两个输入参数:范围A和范围B。它应该返回一个范围对象,该对象由属于范围A的单元格和不属于范围B的单元格组成(如中所示) 我试过的 我在网上看到过一些使用临时工作表来完成这项工作的例子(速度很快,但可能会引入一些受保护工作簿的问题等),还有一些其他例子是逐单元检查第一个范围与第二个范围的交点(速度非常慢) 经过一番思考,我想出了这段代码{1},它运行得更快,但仍然很慢。根据第二个范围的复杂程度,从代表整个工作表的范围中减去需要1到5分钟 当我查看
- 去掉了全局变量(mrBuild)
- 修正了“某些重叠”条件以排除“无重叠”情况
- 添加了更复杂的条件,以选择是从上到下分割范围还是从左到右分割范围
- 选择如何分割范围(按列或按行)的启发式方法
公共函数减去范围(第一个作为范围,第二个作为范围)作为范围
'
'返回属于rFirst的单元格范围,但不属于rSecond
'(如集合减法)
'
'这个函数可以很好地处理较大的输入范围!
'
'使用单独递归函数的原因是
'处理多区域RFST范围
'
调暗打印机
变暗后转为范围
暗淡的稀有植物
设置rInter=Intersect(第一个,第二个)
设置mrBuild=Nothing
如果rInter为空,则“无重叠”
设置rReturn=rFirst
ElseIf rInter.Address=rFirst.Address然后是“总重叠”
设置返回=无
Else部分重叠
对于第一区域中的每个稀有物种
设置mrBuild=BuildRange(rArea,rInter)递归
下一个稀有物种
设置rReturn=mrBuild
如果结束
设置减法范围=返回
端函数
专用函数BuildRange(rArea作为范围,rInter作为范围_
可选mrBuild As Range=Nothing)As Range
'
'减法范围的递归函数()
'
'从rArea中减去rInter并将结果添加到mrBuild
'
变光rLeft As范围,变光rLeft As范围
变光rTop作为范围,变光RTOM作为范围
Dim rInterSub As系列
作为布尔值的Dim GoByColumns
设置rInterSub=Intersect(rArea,rInter)
如果rInterSub为空,则“无重叠”
如果mrBuild什么都不是,那么
设置mrBuild=rArea
其他的
设置mrBuild=Union(mrBuild,rArea)
如果结束
ElseIf Not rInterSub.Address=rArea.Address然后“有些重叠”
如果不是rArea.Cells.CountLarge=1,则“以防由于某些不可能的原因只有一个单元格”
'决定是按列还是按行
'(在减去整行/整列时有帮助)
如果不是,则rInterSub.Columns.Count=rArea.Columns.Count和_
((不是rInterSub.Cells.CountLarge=1和_
(rInterSub.Rows.Count>rInterSub.Columns.Count_
和rArea.Columns.Count>1)或(rInterSub.Rows.Count=1_
而不是rArea.Columns.Count=1)或_
(rInterSub.Cells.CountLarge=1_
然后是rArea.Columns.Count>rArea.Rows.Count)
GoByColumns=True
其他的
GoByColumns=False
如果结束
如果不是GoByColumns,那么
设置rTop=rArea.Resize(rArea.Rows.Count\2)'从上到下拆分范围
Set rbotom=rArea.Resize(rArea.Rows.Count-rTop.Rows.Count).Offset(rTop.Rows.Count)
设置mrBuild=BuildRange(rTop、rInterSub、mrBuild)'重新运行它
设置mrBuild=BuildRange(RBOTOM、rInterSub、mrBuild)
其他的
设置rLeft=rArea.Resize(,rArea.Columns.Count\2)'从左到右拆分范围
Set rRight=rArea.Resize(,rArea.Columns.Count-rLeft.Columns.Count).Offset(,rLeft.Columns.Count)
设置mrBuild=BuildRange(rLeft、rInterSub、mrBuild)'重新运行它
设置mrBuild=BuildRange(RRRight、rInterSub、mrBuild)
如果结束
如果结束
如果结束
设置BuildRange=mrBuild
EN
Private mrBuild As Range
Public Function SubtractRanges(rFirst As Range, rSecond As Range) As Range
Dim rInter As Range
Dim rReturn As Range
Dim rArea As Range
Set rInter = Intersect(rFirst, rSecond)
Set mrBuild = Nothing
If rInter Is Nothing Then 'No overlap
Set rReturn = rFirst
ElseIf rInter.Address = rFirst.Address Then 'total overlap
Set rReturn = Nothing
Else 'partial overlap
For Each rArea In rFirst.Areas
BuildRange rArea, rInter
Next rArea
Set rReturn = mrBuild
End If
Set SubtractRanges = rReturn
End Function
Sub BuildRange(rArea As Range, rInter As Range)
Dim rLeft As Range, rRight As Range
Dim rTop As Range, rBottom As Range
If Intersect(rArea, rInter) Is Nothing Then 'no overlap
If mrBuild Is Nothing Then
Set mrBuild = rArea
Else
Set mrBuild = Union(mrBuild, rArea)
End If
Else 'some overlap
If rArea.Columns.Count = 1 Then 'we've exhausted columns, so split on rows
If rArea.Rows.Count > 1 Then 'if one cell left, don't do anything
Set rTop = rArea.Resize(rArea.Rows.Count \ 2) 'split the range top to bottom
Set rBottom = rArea.Resize(rArea.Rows.Count - rTop.Rows.Count).Offset(rTop.Rows.Count)
BuildRange rTop, rInter 'rerun it
BuildRange rBottom, rInter
End If
Else
Set rLeft = rArea.Resize(, rArea.Columns.Count \ 2) 'split the range left to right
Set rRight = rArea.Resize(, rArea.Columns.Count - rLeft.Columns.Count).Offset(, rLeft.Columns.Count)
BuildRange rLeft, rInter 'rerun it
BuildRange rRight, rInter
End If
End If
End Sub
?subtractranges(rangE("A1"),range("a10")).Address
$A$1
?subtractranges(range("a1"),range("a1")) is nothing
True
?subtractranges(range("$B$3,$B$6,$C$8:$W$39"),range("a1:C10")).Address
$C$11:$C$39,$D$8:$W$39
?subtractranges(range("a1:C10"),range("$B$3,$B$6,$C$8:$W$39")).Address
$A$1:$A$10,$B$1:$B$2,$B$4:$B$5,$B$7:$B$10,$C$1:$C$7
Sub RangeSubtraction()
Dim firstRange As Range
Dim secondRange As Range
Dim rIntersect As Range
Dim rOutput As Range
Dim x As Range
Set firstRange = Range("A1:B10")
Set secondRange = Range("A5:B10")
Set rIntersect = Intersect(firstRange, secondRange)
For Each x In firstRange
If Intersect(rIntersect, x) Is Nothing Then
If rOutput Is Nothing Then 'ugly 'if-else' but needed, can't use Union(Nothing, Range("A1")) etc.
Set rOutput = x
Else
Set rOutput = Application.Union(rOutput, x)
End If
End If
Next x
Msgbox rOutput.Address
End Sub
Function UnionExclusive(ByRef r1 As Excel.Range, r2 As Excel.Range) As Excel.Range
'
' This function returns the range of cells that is the Union of both ranges with the
' exclusion of the ranges or cells that they have in common.
'
On Error Resume Next
Dim rngWholeArea As Excel.Range
Dim rngIndividualArea As Excel.Range
Dim rngIntersection As Excel.Range
Dim rngIntersectArea As Excel.Range
Dim rngUnion As Excel.Range
Dim rngSection As Excel.Range
Dim rngResultingRange As Excel.Range
Dim lngWholeTop As Long
Dim lngWholeLeft As Long
Dim lngWholeBottom As Long
Dim lngWholeRight As Long
Dim arrIntersection As Variant
Dim arrWholeArea As Variant
'
' Must be on same sheet, return only first range.
'
If Not r1.Parent Is r2.Parent Then Set UnionExclusive = r1: Exit Function
'
' No overlapping cells, return the union.
'
If Application.Intersect(r1, r2) Is Nothing Then Set UnionExclusive = Application.Union(r1, r2): Exit Function
'
' Range to subtract must be contiguous. If the second range has multiple areas, loop through all the individual areas.
'
If (r2.Areas.Count > 1) _
Then
Set rngResultingRange = r1
For Each rngIndividualArea In r2.Areas
Set rngResultingRange = UnionExclusive(rngResultingRange, rngIndividualArea)
Next rngIndividualArea
Set UnionExclusive = rngResultingRange
Exit Function
End If
'
' Get the overall size of the Union() since Rows/Columns "Count" is based on the first area only.
'
Set rngUnion = Application.Union(r1, r2)
For Each rngIndividualArea In rngUnion.Areas
If (lngWholeTop = 0) Then lngWholeTop = rngIndividualArea.Row Else lngWholeTop = Application.WorksheetFunction.Min(lngWholeTop, rngIndividualArea.Row)
If (lngWholeLeft = 0) Then lngWholeLeft = rngIndividualArea.Column Else lngWholeLeft = Application.WorksheetFunction.Min(lngWholeLeft, rngIndividualArea.Column)
If (lngWholeBottom = 0) Then lngWholeBottom = (rngIndividualArea.Row + rngIndividualArea.Rows.Count - 1) Else lngWholeBottom = Application.WorksheetFunction.Max(lngWholeBottom, (rngIndividualArea.Row + rngIndividualArea.Rows.Count - 1))
If (lngWholeRight = 0) Then lngWholeRight = (rngIndividualArea.Column + rngIndividualArea.Columns.Count - 1) Else lngWholeRight = Application.WorksheetFunction.Max(lngWholeRight, (rngIndividualArea.Column + rngIndividualArea.Columns.Count - 1))
Next rngIndividualArea
arrWholeArea = Array(lngWholeTop, lngWholeLeft, lngWholeBottom, lngWholeRight)
'
' Get the entire area covered by the various areas.
'
Set rngWholeArea = rngUnion.Parent.Range(rngUnion.Parent.Cells(lngWholeTop, lngWholeLeft), rngUnion.Parent.Cells(lngWholeBottom, lngWholeRight))
'
' Get intersection, this is or are the area(s) to remove.
'
Set rngIntersection = Application.Intersect(r1, r2)
For Each rngIntersectArea In rngIntersection.Areas
arrIntersection = Array(rngIntersectArea.Row, _
rngIntersectArea.Column, _
rngIntersectArea.Row + rngIntersectArea.Rows.Count - 1, _
rngIntersectArea.Column + rngIntersectArea.Columns.Count - 1)
'
' Get the difference. This is the whole area above, left, below and right of the intersection.
' Identify if there is anything above the intersection.
'
Set rngSection = Nothing
If (arrWholeArea(0) < arrIntersection(0)) _
Then Set rngSection = Application.Intersect(rngWholeArea.Parent.Range(rngWholeArea.Parent.Cells(arrWholeArea(0), arrWholeArea(1)), _
rngWholeArea.Parent.Cells(arrIntersection(0) - 1, arrWholeArea(3))), _
rngUnion)
If Not rngSection Is Nothing _
Then
If rngResultingRange Is Nothing _
Then Set rngResultingRange = rngSection _
Else Set rngResultingRange = Application.Union(rngResultingRange, rngSection)
End If
'
' Identify if there is anything left of the intersection.
'
Set rngSection = Nothing
If arrWholeArea(1) < arrIntersection(1) _
Then Set rngSection = Application.Intersect(rngWholeArea.Parent.Range(rngWholeArea.Parent.Cells(arrWholeArea(0), arrWholeArea(1)), _
rngWholeArea.Parent.Cells(arrWholeArea(2), arrIntersection(1) - 1)), _
rngUnion)
If Not rngSection Is Nothing _
Then
If rngResultingRange Is Nothing _
Then Set rngResultingRange = rngSection _
Else Set rngResultingRange = Application.Union(rngResultingRange, rngSection)
End If
'
' Identify if there is anything right of the intersection.
'
Set rngSection = Nothing
If arrWholeArea(3) > arrIntersection(3) _
Then Set rngSection = Application.Intersect(rngWholeArea.Parent.Range(rngWholeArea.Parent.Cells(arrWholeArea(0), arrIntersection(3) + 1), _
rngWholeArea.Parent.Cells(arrWholeArea(2), arrWholeArea(3))), _
rngUnion)
If Not rngSection Is Nothing _
Then
If rngResultingRange Is Nothing _
Then Set rngResultingRange = rngSection _
Else Set rngResultingRange = Application.Union(rngResultingRange, rngSection)
End If
'
' Identify if there is anything below the intersection.
'
Set rngSection = Nothing
If arrWholeArea(2) > arrIntersection(2) _
Then Set rngSection = Application.Intersect(rngWholeArea.Parent.Range(rngWholeArea.Parent.Cells(arrIntersection(2) + 1, arrWholeArea(1)), _
rngWholeArea.Parent.Cells(arrWholeArea(2), arrWholeArea(3))), _
rngUnion)
If Not rngSection Is Nothing _
Then
If rngResultingRange Is Nothing _
Then Set rngResultingRange = rngSection _
Else Set rngResultingRange = Application.Union(rngResultingRange, rngSection)
End If
Set rngUnion = rngResultingRange
Set rngResultingRange = Nothing
Next rngIntersectArea
'
' Return the result. This is the area "around" the intersection.
'
Set UnionExclusive = rngUnion
End Function
Sub Test()
Dim r As Excel.Range
ActiveSheet.Cells.Clear
Set r = UnionExclusive([A2:C10], [B1:B15])
r.Interior.ColorIndex = 6
Set r = UnionExclusive([F2:H11], [G4:H5,G8:H9,J10:J11,F14:J14])
r.Interior.ColorIndex = 7
Set r = UnionExclusive([F17:J26], [G17:G21,G24:G26,I17:I26,J19:J20])
r.Interior.ColorIndex = 43