简体   繁体   English

Excel UDF查找具有给定值的范围内的第一个和最后一个单元格-运行缓慢

[英]Excel UDF to find first and last cell in range with a given value - runs slowly

I'm writing a function which takes a column range and finds the first and last cell in that column which have a certain value. 我正在编写一个函数,该函数采用列范围,并查找该列中具有特定值的第一个和最后一个单元格。 This gives a first row number and a last row number that are then used to return the corresponding subrange in another column. 这给出了第一个行号和最后一个行号,然后将它们用于返回另一列中的相应子范围。
The idea is that with this function I can apply Excel functions to a (continuous) subsection of a range. 我的想法是,通过此函数,我可以将Excel函数应用于范围的(连续)子部分。 Eg suppose I have a table with various prices of Apples and Bananas, grouped so that all prices of Apples come first, then Bananas. 例如,假设我有一张桌子,上面列出了各种价格的苹果和香蕉,将它们分组,以便首先列出所有苹果价格,然后是香蕉。 I want to find the minimum price of Apples and the minimum of Bananas, but selecting the whole range and without changing the range over which to minimise. 我想找到苹果的最低价格和香蕉的最低价格,但要选择整个范围,而又不改变最小范围。 I would use my desired function to feed a range to Excel's MIN function which included just Apples, or just Bananas, without having to manually select these subranges. 我将使用所需的函数将范围提供给Excel的MIN函数,该范围仅包括Apple或Bananas,而无需手动选择这些子范围。 A MINIF, if you will - like a weak version of SUMIF but for MIN (and potentially many other functions). 一个MINIF(如果需要的话),就像SUMIF的一个弱版本,但用于MIN(可能还有许多其他功能)。
I've found a way of doing it but it's running really quite slow. 我已经找到了一种方法,但是它的运行速度非常慢。 I think it may have to do with the for loop, but I don't understand enough about efficiency in Excel/VBA to know how to improve it. 我认为这可能与for循环有关,但是我对Excel / VBA的效率了解得不够,无法知道如何进行改进。 I'm using this code on an Excel table, so the columns I pass are named columns of a table object. 我在Excel表上使用此代码,因此我传递的列称为表对象的列。 I'm using Excel 2010 on Windows 7 Enterprise. 我正在Windows 7 Enterprise上使用Excel 2010。

Grateful for any help. 感谢您的帮助。 Even solutions on how to conditionally apply functions to ranges that deviate radically from this are well received. 甚至关于如何有条件地将函数应用于与之完全偏离的范围的解决方案也广为接受。

Code: 码:

' ParentRange and CriterionRange are columns of the same table. 
'I want to extract a reference to the part of ParentRange which corresponds
'by rows to the part of CriterionRange that contains cells with a certain value.
Function CorrespondingSubrange(CriterionRange As Range, Criterion As _
String, ParentRange As Range) As Range

Application.ScreenUpdating = False

Dim RowCounter As Integer
Dim SubRangeFirstRow As Integer
Dim SubRangeFirstCell As Range
Dim SubRangeLastRow As Integer
Dim SubRangeLastCell As Range
Dim RangeCountStarted As Boolean

RangeCountStarted = False

Set SubRangeFirstCell = CriterionRange.Find(What:=Criterion, LookIn:=xlValues, _
        LookAt:=xlPart, SearchOrder:=xlByColumns, SearchDirection:=xlNext, _
        MatchCase:=False, SearchFormat:=False)

If Not (SubRangeFirstCell Is Nothing) Then
    RangeCountStarted = True
    SubRangeFirstRow = SubRangeFirstCell.Row - CriterionRange.Range("A1").Row + 1

    For RowCounter = SubRangeFirstRow To CriterionRange.Cells.Count

        If Not (CriterionRange.Cells(RowCounter, 1).Value = Criterion) Then
            SubRangeLastRow = RowCounter - 1
            Exit For
        End If

    Next

End If

If RangeCountStarted = True And SubRangeLastRow = 0 Then SubRangeLastRow = RowCounter

Set CorrespondingSubrange = ParentRange.Range("A" & SubRangeFirstRow & ":A" & SubRangeLastRow)

Application.ScreenUpdating = True
End Function

I don't like using VBA when an Excel formula can be used efficiently. 当Excel公式可以有效使用时,我不喜欢使用VBA。

First of all, you can get a minimum or maximum according to conditions using a simple IF in an array formula (enter the formula using Ctrl + Shift + Enter . This will add the surrounding {} that indicate an array formula): 首先,可以在数组公式中使用简单的IF根据条件获得最小值或最大值(使用Ctrl + Shift + Enter输入该公式。这将添加表示数组公式的周围的{} ):

=MIN(IF($A$1:$A$10=D1,$B$1:$B$10))

This formula checks in A for the condition in D1 and returns the corresponding value from B. Notice that your data doesn't even need to be ordered for this formula to work: 该公式在A中检查D1中的条件,并从B中返回相应的值。请注意,您甚至不需要为该公式工作就可以对数据进行排序:

片

Second, if you want to keep getting the first and last row numbers, you can use this very formula with a minor addition. 其次,如果要保持获取第一行和最后一行的行号,可以使用此公式加上少量的加法。 However , I suspect that one would use the INDIRECT or OFFSET functions with these values, which is unnecessary and inefficient, as this functions are volatile. 但是 ,我怀疑有人会将INDIRECTOFFSET函数与这些值一起使用,这是不必要且效率低下的,因为此函数易变。 Regardless, the addition to the formula is the ROW function. 无论如何,公式的添加项是ROW函数。 (This formula will need the data to be ordered of course). (当然,此公式将需要对数据进行排序)。 Array formula for row numbers: 行号的数组公式

=MAX(IF($A$1:$A$10=D1,ROW($A$1:$A$10)))

This will return the last row number for Bananas . 这将返回Bananas的最后一行。

By settiing Find SearchDirection to xlPrevious you can easily Find the last occurrence in a range. 通过将“查找搜索方向”设置为xlPrevious,您可以轻松地查找范围中的最后一个匹配项。
Toggling Application.ScreenUpdating has little effect when you are just reading values. 当您仅读取值时,切换Application.ScreenUpdating效果不大。 I prefer shorter variable names. 我更喜欢较短的变量名。 Longer names tend to clutter the screen and make it harder to see what's going on. 较长的名称容易使屏幕混乱,并使得很难看清正在发生的事情。 But's that's just my opinion. 但这只是我的意见。

Function CorrespondingSubrange(rCriterion As Range, Criterion As _
                                                        String, rParent As Range) As Range
    Dim FirstCell As Range
    Dim LastCell As Range

    Set FirstCell = rCriterion.Find(What:=Criterion, LookIn:=xlValues, _
                                                LookAt:=xlPart, SearchOrder:=xlByColumns, SearchDirection:=xlNext, _
                                                MatchCase:=False, SearchFormat:=False)
    Set LastCell = rCriterion.Find(What:=Criterion, LookIn:=xlValues, _
                                                LookAt:=xlPart, SearchOrder:=xlByColumns, SearchDirection:=xlPrevious, _
                                                MatchCase:=False, SearchFormat:=False)


    If Not (FirstCell Is Nothing) Then
        Set CorrespondingSubrange = rParent.Range("A" & FirstCell.Row & ":A" & LastCell.Row)
    End If
End Function

My answer is similar to the VBA UDF solution posted earlier by Thomas Inzina with a couple of differences. 我的答案与Thomas Inzina先前发布的VBA UDF解决方案相似,但有一些区别。

The After:= parameter is used to ensure that the first match found is the first match in the range. After:=参数用于确保找到的第一个匹配项是该范围内的第一个匹配项。 The Range.Find method uses a 'tin-can' approach where it loops through the cells of hte range and restarts at the beginning once it reaches the end. Range.Find方法使用“锡罐”方法,在其中循环遍历hte range的单元格,并在到达末尾时从头开始重新启动。 By starting After:=.Cells(.Cells.Count) and moving in a forward direction, you will find hte first cell in the range that matches. 通过启动After:=.Cells(.Cells.Count)并向前移动,您将找到匹配范围内的第一个单元格。 Similarly, by starting at After:=.Cells(1) and moving SearchDirection:=xlPrevious you will quickly find the last without looping. 同样,通过从After:=.Cells(1)并移动SearchDirection:=xlPrevious您将快速找到最后一个而不循环。

I've also used the Intersect method to a) cut down full column references to the Worksheet.UsedRange property and b) to quickly return the working range from the determined criteria range. 我还使用了Intersect方法来a)减少对Worksheet.UsedRange属性的完整列引用,并b)快速从确定的标准范围返回工作范围。

Function CorrespondingSubrange(rngCriterion As Range, Criterion As String, _
                               rngWorking As Range) As Variant

    Dim SubRangeFirstCell As Range
    Dim SubRangeLastCell As Range

    'set the return value to an #N/A error (success will overwrite this)
    CorrespondingSubrange = CVErr(xlErrNA)

    'chop any full column references down to manageable ranges
    Set rngCriterion = Intersect(rngCriterion, rngCriterion.Parent.UsedRange)

    With rngCriterion

        'look forwards for the first occurance
        Set SubRangeFirstCell = .Find(What:=Criterion, After:=.Cells(.Cells.Count), _
                LookIn:=xlValues, LookAt:=xlPart, SearchOrder:=xlByColumns, _
                SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False)

        If Not SubRangeFirstCell Is Nothing Then

            'there is at least one of the criteria - now look backwards
            Set SubRangeLastCell = .Find(What:=Criterion, After:=.Cells(1), _
                    LookIn:=xlValues, LookAt:=xlPart, SearchOrder:=xlByColumns, _
                    SearchDirection:=xlPrevious, MatchCase:=False, SearchFormat:=False)

            Set CorrespondingSubrange = Intersect(rngWorking, Range(SubRangeFirstCell, SubRangeLastCell).EntireRow)

            Debug.Print CorrespondingSubrange.Address(0, 0, external:=True)

        End If
    End With

End Function

对应子范围

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM