索引到大的不连续范围

时间:2016-07-18 19:01:45

标签: excel vba excel-vba

假设我定义了一个很大的不连续范围,可能是Range("B:B,E:E,F:F")。我如何进入索引范围以将其视为连续的。

E.g。我想做一些像

这样的事情
Set myRange = Range("B:B,E:E,F:F")
v = myRange.ContiguousIndex(5, 3).Value 'retrieves the value in cell F5 (row 5 col 3)

我所知道的每个方法都会根据范围中的第一个单元格(“B1”)进行偏移,并且很乐意超出该范围的范围,溢出到工作簿的其余内容中。这意味着尝试访问第5行,第3列将获得D5,就好像列C和D在我试图索引的范围内。

我尝试过Range.Cells,Range.Offset和Range.Range,但似乎都表现出同样的溢出效应。

我想到的另一种方法是将值分配给变量数组并从那里手动索引,但这很快变得复杂,因为像

这样的简单代码片段
Dim v() As Variant
v = myRange

只会将不连续范围的第一个区域分配到数组中,留下一个(20 ^ 20-1)x1数组并完全忽略myRange的其余部分。因此,如果我循环遍历所有区域并将它们分别分配到我继续重新分配的数组中,那么将整个myRange放入数组中可能是可行的,但这远非易事而是我最终得到的内存比我想要的内存多得多(除非我把更多的开销用于修剪它或者我随意选择较少数量的行来复制)。

此时,手动遍历区域并自行编制索引会更加高效和简单,而无需将所有内容放入数组中。最后的方法是我目前正在做的。

问题

是否有任何现有的方法或技巧可以用来对待myRange,好像它以我描述的方式连续,并以忽略不连续性的方式索引到myRange?

TL; DR如果我有

Set myRange = Range("B:B,E:E,F:F")
v = myRange.ContiguousIndex(5, 3).Value

我想要一些方法ContiguousIndex来返回Range(“F5”)。值而不必完成手动检查Range.Areas和处理所有索引的所有工作。

奖金问题

假设myRange为Range("E:E,B:B,F:F")(注意不同的列顺序)。有没有一种很好的方法可以将E视为第一列,将B视为第二列,将F视为第三列,这样

Set myRange = Range("E:E,B:B,F:F")
v = myRange.ContiguousIndex(5, 2).Value 'retrieves the value in cell B5

返回B5的值?这是我正在使用的方法的属性,我很乐意继续使用。

同样,我的功能也有效,但我猜测在Excel的所有怪癖中都隐藏着某种奇妙的方法或技巧,甚至更好。

4 个答案:

答案 0 :(得分:2)

需要注意的是,使用.Cells / .Rows / .Columns / ._Default,您可以获得超出范围的值:

Set myRange = Range("E2:E4,C4:B2,F2:F4")   ' C4:B2 gets B2:C4 
Debug.Print myRange.Areas(2)(1).Address ' $B$2

Debug.Print myRange.Areas(2)(0, 0).Address              ' $A$1
Debug.Print myRange.Areas(2).Cells(0, 0).Address        ' $A$1
Debug.Print myRange.Areas(2).Rows(0).Columns(0).Address ' $A$1

相反,如果您将值编入索引:

Debug.Print myRange.Areas(2).Value2(1, 1) ' value of B2
Debug.Print myRange.Areas(2).Value2(0, 0) ' Run-time error '9': Subscript out of range

如果你有任何机会有多个列,例如"E:E,A:B",那么如果你将每列指定为一个单独的区域,那么对它们进行索引会更容易:"E:E,A:A,B:B"

答案 1 :(得分:2)

我会发布自己的解决方案,以防其他人遇到类似的问题。这是唯一对我有用的,因为其他答案和评论依赖于了解范围内的区域(例如,依赖于每个区域是整个单列,我无法保证,因为我的范围是用户输入,可以跨越多列或有限行数。

' Indexes into a discontiguous area as expected, ignoring cells not in Range r
' and treating areas as concatenated (and top-aligned) in the order they are specified
Public Function ContiguousIndex(r As Range, row As Long, col As Long)
    Dim area As Range

    For Each area In r.Areas
        If col <= area.Columns.count Then
            If row <= area.Rows.count Then
                ContiguousIndex = area.Cells(row, col)
                Exit Function
            Else
                Err.Raise vbObjectError + 9, , "Row Index out of bounds"
            End If
        Else
            col = col - area.Columns.count
        End If
    Next

    ' col argument > sum of all cols in all areas
    Err.Raise vbObjectError + 9, , "Col Index out of bounds"
End Function

值得重新注意我在评论中介绍的内容,但可能会出乎意料:此代码将对齐所有区域,使区域1中的第一行与区域2中的第一行处于同一索引是相同的...等等。这会在调用像ContiguousIndex(Range("A1:B7,A8:B10"), 9, 2)这样的东西时产生怪癖。虽然很明显这应该返回B9,但事实并非如此 - 它实际上会尝试访问A1:B7的第9行第2列,从而导致错误。这是因为两个不连续的范围,尽管它们在实际工作表上从上到下清楚地排列,但它们被视为是左右对齐。因此可以通过命令B9(非直观地)访问ContiguousIndex(Range("A1:B7,A8:B10"), 2, 4)。这种行为是我所要求的,但它可能不是你所期望的。

为了避免这种情况,您可以使用内置的Application.UnionApplication.Intersect方法。如果可能,这些会自动折叠连续区域。以下所有工作:

' Every statement will print "A1:B10" - the areas are merged

' Union of separate areas
Debug.Print Union(Range("A1:B7"), Range("A8:B10")).Address

' Union of range with a known subrange
Debug.Print Union(Range("A1:B7,A8:B10"), Range("A1:B7,A8:B10").Cells(1, 1)).Address

' Union of range with itself
Debug.Print Union(Range("A1:B7,A8:B10"), Range("A1:B7,A8:B10")).Address

' Intersect of range with itself
Debug.Print Intersect(Range("A1:B7,A8:B10"), Range("A1:B7,A8:B10")).Address

如果这是索引时所需的行为,则在调用ContiguousIndex之前执行列出的合并之一。请注意,如果区域在联合操作中未合并,则它们的相对不连续索引保持不变。 E.g。

' Yields "A:A,F:F,C:D" not "A:A,C:D,F:F" as you might desire
Debug.Print Union(Range("A:A,F:F,C:C,D:D"), Range("A:A,F:F,C:C,D:D")).Address

答案 2 :(得分:2)

我认为在看到你的例子之后我会更好地理解你的问题。它可以简化&#34;枚举列而不是范围:

Public Function ContiguousIndex(r As Range, row As Long, col As Long) As Range
    Dim column As Range

    For Each column In r.Columns
        If col > 1 Then
            col = col - 1
        ElseIf col = 1 Then
            If row <= column.Rows.Count And row > 0 Then
                Set ContiguousIndex = column.Rows(row)
                Exit Function
            End If
            Err.Raise vbObjectError + 9, , "Row Index out of bounds"
        ElseIf col < 1 Then
            Err.Raise vbObjectError + 9, , "Column Index out of bounds"
        End If
    Next
End Function

我找不到直接访问枚举器的方法(例如
r.Columns.[_NewEnum].Item(col)不起作用)

<强>更新

仅举例来说

Public Function veryContiguousIndex(r As Range, row As Long, col As Long) As Range
    Dim cell As Range, i As Long: i = col * row

    For Each cell In r.Cells
        If i = 1 Then Set veryContiguousIndex = cell: Exit Function
        i = i - 1
    Next
End Function

然后

Dim r As Range: Set r = [A1:B7,A8:B10]
Debug.Print r.Cells.Count; r.Columns.Count; r.Rows.Count  ' 20  2  7
Debug.Print veryContiguousIndex(r             , 9, 2).Address(0, 0) ' B9
Debug.Print veryContiguousIndex(r.EntireColumn, 9, 2).Address(0, 0) ' B9
Debug.Print veryContiguousIndex(r.EntireRow   , 9, 2).Address(0, 0) ' R1

答案 3 :(得分:0)

怎么样:

v = myRange.Areas(2).Rows(5).Value 'retrieves the value in cell B5

只要每个子范围是一列,这似乎适用于原始和奖励问题。您还可以在VBA中创建一个简单的包装函数ContiguousIndex(Row,Column),以提供您描述的接口。

希望有所帮助。