2014-03-31 3 views
4

Так что, может быть, в понедельник, может быть, я глуп. Но я не могу на всю жизнь понять, что хороший способ получить 1D-массив из одной строки в 2D-массиве. (Может быть, это не «настоящий» 2D массив?)Назначить массив 1D из 2D-массива в VBA?

В любом случае, у меня есть массив, который я определил так: dim myArr(2,4) as variant

Я заполнил его со значениями 1 - 15, так что выглядит как:

1,2,3,4,5 
6,7,8,9,10 
11,12,13,14,15 

Итак, теперь я хочу одну строку этого массива. единственный способ, которым я могу понять, делая это:

dim temp() as variant 
    ReDim temp(lbound(myArr,2) to ubound(myArr,2)) 

    For i = 0 To 0 
     For j = LBound(myArr, 2) To UBound(myArr, 2) 
      temp(j) = myArr(i, j) 
     Next 
    Next 

Но мне не нравится, что, потому что если массив получил огромный, то время, которое потребовалось, чтобы сделать это могло бы быть значительно больше. Я думал, что я должен быть в состоянии сделать:

dim temp as variant 
temp = myArr(0) 'since myArr(0) is a 1D array with 5 spots right? 

но нет .. я получаю «неверный номер габариты» ошибки.

Я также перекопал предыдущие вещи и нашел этот вопрос: How to compare two entire rows in a sheet и ответ tim thomas показывает использование транспонирования и упоминает, что вы сравнивали столбцы, которые вы будете использовать только транспонировать один раз, но это не работает .. если бы я сделал это так: dim myArr(2,0) as variant транспонирование работает, но не так, как у меня сейчас.

Я нашел бесчисленные ответы на этот вопрос на других языках, таких как C++ и Python, но я вообще не знаком ни с одним из них, поэтому я не мог их интерпретировать.

Есть ли лучший способ сделать это? Или я застрял в этой двойной схеме, что мне не нравится?

Спасибо!

+0

Проклятье :(Если это айн 't на CPearson Мне нужно подумать, что это невозможно. Хорошо, спасибо simcoe – user1759942

+0

Вы можете скопировать весь массив в VBA, но только в том случае, если это вариант. Копирующая копия - это правильный способ справиться с этим. Он по-прежнему быстро работает в VBA. – dmaruca

+0

как мне скопировать весь массив? без этого цикла? – user1759942

ответ

10

Вот самодостаточная иллюстрация одного из способов "срез" 2-D массива: с

Sub ArraySlicing() 

Dim arr(1 To 5, 1 To 5) 
Dim slice 
Dim x, y 
Dim a As Application 

    'Populate a sample 2-D array with values... 
    For y = 1 To 5 
    For x = 1 To 5 
     arr(y, x) = "R" & y & ":C" & x 
    Next x 
    Next y 
    '...done setting up sample array 

    Set a = Application 'this is just to shorten the following lines 

    'Example 1: get the first "column" 
    slice = a.Transpose(a.Index(arr, 0, 1)) 
    Debug.Print Join(slice, ", ") 'display what we got 

    'Example 2: get second "row" (note double transpose) 
    slice = a.Transpose(a.Transpose(a.Index(arr, 2, 0))) 
    Debug.Print Join(slice, ", ") 'display what we got 

End Sub 

Index() дает 2-D массив - (х, 1) или (1, x) - Transpose() преобразует это в 1-й массив.

+0

Бахаха Уильямс. mahbad! Очень круто, сэр, спасибо :) немного сложнее, чем я надеялся, но мало всего так просто, как хотелось бы – user1759942

+0

Вам нужна только одна строка ;-) –

+0

Lol ok thanks. Я разберу его и разобью все. Ты намного более продвинутый, чем я, когда я читаю ваши ответы, мне часто приходится тратить несколько минут на все. – user1759942

2

Основываясь на ответе Тима Уильямса, я создал автономную функцию, которая вырезает один индекс двумерного массива и возвращает одномерный массив.

Function Slice_String_Array(arr As Variant, dir As Boolean, index As Long) As Variant() 
'This function recieves a two-dimensional array, slices one element of it (either a 'column' or a 'row'), and 
'returns a one dimensional array with the values. Both the array passed in the arguments (arr) and the 
'returned array are of type Variant. 
'############################################################################################ 
'arr is the two-dimensional array being sliced 
'"dir" is used for direction, 0 (False) is vertical (column) and 1 (True) is horizontal (column) 
'"index" is the element number (column number or row number) you want returned 
'############################################################################################ 

Set a = Application 'this is just to shorten the following lines 

If dir = 0 Then 'slice a column 
    slice = a.Transpose(a.index(arr, 0, index)) 
Else 'slice a row 
    slice = a.Transpose(a.Transpose(a.index(arr, index, 0))) 
End If 

Slice_String_Array = slice 

End Function 
+0

это здорово! Но, кажется, имеет ограничение, около 60 000 строк в 20 полях. Кроме того, предположим, что вы используете As XlOrientation вместо Boolean для dir. –

1

@dathanm: Хороший первоначальный подход, но есть несколько моментов, которые вы, кажется, неясно:

  1. В VB, нет необходимости явно сравнивать логические значения с нулем. Интерпретатор VBA автоматически позаботится об этом (сравнивая <> 0, на самом деле, чтобы проверить состояние True).
  2. (@ Тим Уильямс тоже): в вашем дизайне строкой строки вызов метода Transpose не нужен, потому что Index возвращает 1D массив значений строк, готовый к использованию as-is. Как и было предусмотрено, два вложенных вызова Transpose по существу являются No-Op. Подробное объяснение этого см. В технической заметке № 3 в комментарии к заголовку моей версии ниже.
  3. Метод Index предполагает одноуровневое индексирование, поэтому ваш код должен проверять переданный массив VB, который не является одним из основанных (очень распространенных) и настраивается для этого случая.
  4. Еще одна особенность заключается в том, что, если вызывающий код передает зубчатый массив массивов (что не является редкостью в VB), метод Index будет терпеть неудачу при работе с столбцами, поэтому ваш код должен проверить этот случай.
  5. И, наконец, он должен гарантировать, что база возвращаемого элемента sliced-array совпадает с базой исходного 2D-массива в разрезаемом измерении, поскольку это необязательно по умолчанию, когда методы Index или Transpose участвует.

Вот мой вариант, для тех, кто использовать по мере необходимости:

Function ArraySlice(Arr2D As Variant, ByCol As Boolean, SliceIdx As Long) As Variant 
    ' 
    'Returns a 1D row or column array "slice" of the specified 2D array. 
    ' 
    'PARAMETERS: 
    ' 
    ' Arr2D  The 2D array from which a row/column slice is to be extracted. The passed array may be of 
    '    any data type. 
    ' 
    ' ByCol  True = slice a column; False = slice a row. 
    ' 
    ' SliceIdx The array-index of the row or column to be sliced from the 2D array. Note that the meaning 
    '    of SliceIdx value depends on the base of the 2D array's dimensions: a SliceIdx of 1 will be 
    '    the first row/column of a one-based array but the second row/column of a zero-based array. 
    ' 
    'RETURN VALUES: If successful, returns a sliced 1D row or column (Variant) array. If unsuccessful, returns 
    '    an Empty Variant status. Note that, though an array of any data type may be passed to this 
    '    routine, the variable receiving the returned array must be a Variant because whole-array 
    '    assignment is implemented for Variants only in VB/VBA. 
    ' 
    'TECHNICAL NOTES: 
    ' 
    ' 1. IMPORTANT: Though arrays-of-arrays are supported by this routine, jagged arrays-of-arrays (which are 
    '  possible in VBA) would be very complex to handle for column-slice operations, so this routine does not 
    '  support them for that case and will throw an error message accordingly. Also, for column-slice 
    '  operations on rectangular arrays-of-arrays, this routine assumes that each row-array has the same 
    '  LBound (0 or 1 or whatever), which is a condition that can be violated in VB but is extremely unusual. 
    '  This routine will throw an error in that case as well. 
    ' 
    ' 2. The Application.Index method, used by this routine has two forms, an Array form and a Reference form. 
    '  When used by this routine in its Array form, an array (as opposed to a worksheet cell-range reference) 
    '  is passed to it and it returns an array as its return value. In this usage, it doesn't actually 
    '  operate on an Excel worksheet range and is therefore very fast, actually faster than any VBA-coded 
    '  equivalent algorithm, because its functionality is implemented by Excel's underlying compiled-code 
    '  engine. 
    ' 
    ' 3. The Index method and the Transpose method are "orientation-aware" with regard to the row/column 
    '  orientation of the passed array argument. For a multi-row and multi-column rectangular array, the 
    '  orientation question is moot but for a single-row or single-column array it isn't. Without those 
    '  methods' orientation-awareness, they would require an additional optional parameter that the calling 
    '  code would have to set (but only for single-row/column arrays) to inform the methods of the otherwise- 
    '  ambiguous orientation of the passed array. Such a design would further complicate the call interface 
    '  as well as the calling code. 
    ' 
    '  Microsoft's solution was to implement the required orientation-awareness by defining single-row arrays 
    '  as a simple 1D array containing that row's elements (i.e. RowValues(col-num)), and defining single- 
    '  column arrays as a degenerate 2D array containing a single column of each row's value for that column 
    '  (i.e. ColValues(row-num, 1)). That is, those methods determine the orientation of a passed single- 
    '  row/column array by simply checking whether it is a 1D array (row) or 2D array (column). And any 
    '  single-row/column array returned by them also conforms to that scheme. (Technically, it treats the 
    '  passed array as an array-of-arrays, regardless of its implementation in VBA, which is consistent with 
    '  how simple 2D arrays are implemented in C/C++, the language of Excel's compiled-code engine.) 
    ' 
    '  Consequently, to get a true 1D array of column values, the Index method's returned (degenerate) 2D 
    '  array may be passed to the Transpose method, to convert its column-orientation to row-orientation in 
    '  the Transpose method's context. But in the calling-code's context, it becomes just an independent 
    '  1D array containing the specified column's values. 
    ' 
    'AUTHOR: Peter Straton 
    ' 
    '************************************************************************************************************* 

    Const NA As Long = 0 

    Dim Arr1DSlice As Variant 'NOTE: Some slice operations require a subsequent shift of the sliced array's 
           'base in order to make it match the corresponding base of the original 2D array, 
           'in the sliced dimension. But, because this function's ArraySlice variable is a 
           'Variant that's also the function's return value, you can't just use Redim Preserve 
           'to change its base. Instead, you must us a local (shiftable) Variant and then 
           'assign that array to the function's return value before returning. 
    Dim BaseOffset As Long 
    Dim ColIdxLBound As Long 
    Dim ColIdxUBound As Long 
    Dim i As Long 
    Dim IndexFncOffset As Long 
    Dim IsArrayOfArrays As Variant 'Variant: can be Empty (undefined) or Boolean 
    Dim RowIdxLBound As Long 
    Dim RowIdxUBound As Long 


    Arr1DSlice = Empty 'Default: failure status 

    'First, determine whether Arr2D is a 2D array or array-of-arrays because they are processed differently. 

    On Error Resume Next 
    RowIdxLBound = LBound(Arr2D) 
    If Err <> 0 Then Exit Function 'Not an array, so exit with failure status 

    RowIdxUBound = UBound(Arr2D) 
    IsArrayOfArrays = IsArray(Arr2D(RowIdxLBound, RowIdxLBound)) 
    If IsEmpty(IsArrayOfArrays) Then IsArrayOfArrays = IsArray(Arr2D(RowIdxLBound)) 
    On Error GoTo 0 

    'Do the slice operation 

    With Application 
    If ByCol Then 
     If IsArrayOfArrays Then 
      ColIdxLBound = LBound(Arr2D(RowIdxLBound)) 'Assumes consistent column-index LBounds and UBounds for 
      ColIdxUBound = UBound(Arr2D(RowIdxLBound)) 'each row in the array-of-arrays, but... 

      'Ensure that it doesn't have inconsistent column-index LBounds and isn't a jagged array-of-arrays 
      '(neither of which are supported) by checking each row's column-index bounds. 

      For i = RowIdxLBound To RowIdxUBound 
       If LBound(Arr2D(i)) <> ColIdxLBound Then 
        MsgBox "Arr1DSlice: Arrays-of-arrays with inconsistent column-index LBounds are not " & _ 
          "supported for column operations.", vbOKOnly + vbCritical, "PROGRAMMING ERROR" 
        Exit Function 
       End If 
       If UBound(Arr2D(i)) <> ColIdxUBound Then 
        MsgBox "Arr1DSlice: Jagged arrays-of-arrays are not supported for column operations.", _ 
          vbOKOnly + vbCritical, "PROGRAMMING ERROR" 
        Exit Function 
       End If 
      Next i 

     Else 'Standard 2D array 

      ColIdxLBound = LBound(Arr2D, 2) 
      ColIdxUBound = UBound(Arr2D, 2) 
     End If 

     If ColIdxLBound > SliceIdx Then 'If the specified slice-index isn't in-bounds, clip it accordingly 
      SliceIdx = ColIdxLBound 
     ElseIf ColIdxUBound < SliceIdx Then 
      SliceIdx = ColIdxUBound 
     End If 

     IndexFncOffset = 1 - ColIdxLBound 'The Index method assumes one-based indexing, so must adjust for 
              'non-one-based arrays when that is the case. 

     Arr1DSlice = .index(Arr2D, NA, SliceIdx + IndexFncOffset) 'Returns a degenerate 2D array of a single 
              'column's corresponding row values (see TECHNICAL NOTE #3, above), 
              'so must... 
     Arr1DSlice = .Transpose(Arr1DSlice) '...use Transpose to convert it to a 1D array (technically a "row" 
              'array in the Transpose method's context but is actually just an 
              'independent 1D array in this context). 

     'Determine whether the row-dimension base of the original 2D array is different from the base of the 
     'resulting sliced array (which is necessarily one-based when generated by the Index method), in order to 
     'fix it if necessary, below. NOTE: the column being sliced from the original 2D array is indexed by its 
     'row-position index values, therefore it is the 2D array's row-dimension that must be checked for possible 
     'adjustment of the column-sliced 1D array. 

     BaseOffset = 1 - RowIdxLBound 

    Else 'ByRow 

     If RowIdxLBound > SliceIdx Then 'If the specified slice-index isn't in-bounds, clip it accordingly 
      SliceIdx = RowIdxLBound 
     ElseIf RowIdxUBound < SliceIdx Then 
      SliceIdx = RowIdxUBound 
     End If 

     If IsArrayOfArrays Then 

      Arr1DSlice = Arr2D(SliceIdx) 'For array-of-arrays, just return the SliceIdx row of the 2D array 

      'NOTE: The Index method is not used here so there is no need to check for an array-base adjustment. 

     Else 'Standard 2D array 

      IndexFncOffset = 1 - RowIdxLBound 'The Index method assumes one-based indexing, so must adjust for 
               'non-one-based arrays when that is the case. 

      Arr1DSlice = .index(Arr2D, SliceIdx + IndexFncOffset, NA) 'Slice out the SliceIdx row 

      'NOTE: in the row-slice case, there is no need to transpose (from column array to row array). 

      'Determine whether the column-dimension base of the original 2D array is different from the base of 
      'the resulting sliced array (which is necessarily one-based when generated by the Index method), in 
      'order to fix it if necessary (below). NOTE: the row being sliced from the original 2D array is 
      'indexed by its column-position index values, therefore it is the 2D array's column-dimension that 
      'must be checked for possible adjustment of the row-sliced 1D array. 

      BaseOffset = 1 - LBound(Arr2D, 2) '(Is never an array-of-arrays here!) 
     End If 
    End If 

    If BaseOffset <> 0 Then 
     'The base of the original 2D array is different from the base of the resulting sliced array, so fix the 
     'sliced array to match the original. 

     ReDim Preserve Arr1DSlice(LBound(Arr1DSlice) - BaseOffset To UBound(Arr1DSlice) - BaseOffset) 
    End If 
    End With 'Application 

    ArraySlice = Arr1DSlice '(See the technical note at the Arr1DSlice variable's declaration) 
End Function 

И вот удобный тест рутина, с которой играть с ним:

Sub ArraySliceTest() 
    Dim ByCol As Boolean 
    Dim ColIdxLBound As Long 
    Dim ColIdxUBound As Long 
    Dim i As Long 
    Dim j As Long 
    Dim n As Long 
    Dim m As Long 
    Dim PadTabs As String 
    Dim RowIdxLBound As Long 
    Dim RowIdxUBound As Long 
    Dim Sliced As Variant 
    Dim SliceIdx As Long 
    Dim TempBuf1 As Variant 
    Dim TempBuf2 As String 
    Dim TestArr() As Variant 

    #Const Std2DArray = True 'For array-of-arrays, set to False 
    #Const JaggedArray = False 'For jagged array-of-arrays, set to True 

' ByCol = True 'Uncomment for column slice 
    ByCol = False 'Uncomment for row slice 

' SliceIdx = -1 'Uncomment slice-index value to be tested... 
' SliceIdx = 0 
' SliceIdx = 1 
    SliceIdx = 2 
' SliceIdx = 3 
' SliceIdx = 4 

    #If Std2DArray Then 
    ' ReDim TestArr(0 To 2, 0 To 3) 'Uncomment test-array dimensions to be tested... 
    ' ReDim TestArr(1 To 3, 1 To 4) 
     ReDim TestArr(-1 To 1, -1 To 2) 
    ' ReDim TestArr(0 To 2, 1 To 4) 
    ' ReDim TestArr(0 To 2, -1 To 2) 
    ' ReDim TestArr(1 To 3, 0 To 3) 
    ' ReDim TestArr(-1 To 1, 0 To 3) 

     RowIdxLBound = LBound(TestArr, 1) 
     RowIdxUBound = UBound(TestArr, 1) 
     ColIdxLBound = LBound(TestArr, 2) 
     ColIdxUBound = UBound(TestArr, 2) 

     'To demonstrate Variant flexibility, use integers for 2D array 

     For i = RowIdxLBound To RowIdxUBound 
      n = n + 1 
      m = 0 '(Re)init 
      TempBuf1 = vbNullString 
      For j = ColIdxLBound To ColIdxUBound 
       m = m + 1 
       TestArr(i, j) = n * 10 + m 
      Next j 
     Next i 

    #Else 'For array-of-arrays: 

     'To demonstrate Variant flexibility, use strings for array-of-arrays 

     #If Not JaggedArray Then 
      TestArr = Array(Array("11", "12", "13", "14"), _ 
          Array("21", "22", "23", "24"), _ 
          Array("31", "32", "33", "34"))  'Creates an array of arrays. 

     #Else 
      TestArr = Array(Array("11", "12", "13", "14"), _ 
          Array("21", "22"), _ 
          Array("31", "32", "33", "34")) 'Creates a jagged array of arrays. 

     #End If 

     'Test inconsistent col-index LBounds for all rows in an array-of-arrays (unsupported for col slice) 

'   Dim X As Variant 
'   #If JaggedArray Then 
'    X = Array("21", "22") 
'   #Else 
'    X = Array("21", "22", "23", "24") 
'   #End If 
' 
'   ReDim Preserve X(LBound(X) - 1 To UBound(X) - 1) 
'   TestArr(2) = X 

     'Test array-of-arrays col-index LBounds other than the default (supported for row & col slice) 

'   Dim X As Variant 
'   Dim Y As Variant 
'   Dim Z As Variant 
'   X = Array("11", "12", "13", "14") 
'   ReDim Preserve X(LBound(X) + 1 To UBound(X) + 1) 
'   Y = Array("21", "22", "23", "24") 
'   ReDim Preserve Y(LBound(Y) + 1 To UBound(Y) + 1) 
'   Z = Array("31", "32", "33", "34") 
'   ReDim Preserve Z(LBound(Z) + 1 To UBound(Z) + 1) 
'   ReDim TestArr(0 To 2) 
'   TestArr(0) = X 
'   TestArr(1) = Y 
'   TestArr(2) = Z 

     RowIdxLBound = LBound(TestArr) 
     RowIdxUBound = UBound(TestArr) 
     ColIdxLBound = LBound(TestArr(RowIdxLBound)) 'Assumes consistent column-index LBounds and UBounds for 
     ColIdxUBound = UBound(TestArr(RowIdxLBound)) 'each row in the array-of-arrays (and is used accordingly 
                 'below). 
    #End If 'Std2DArray 

    'Print the 2D test array 

    Debug.Print vbLf & "+--- " & IIf(ByCol, "Col", "Row") & " Slice ---+" 
    TempBuf1 = vbTab 
    TempBuf2 = vbTab 
    For j = ColIdxLBound To ColIdxUBound 
     TempBuf1 = TempBuf1 & Format(j, IIf(j >= 0, " 0", "#0")) & vbTab 
     TempBuf2 = TempBuf2 & ".." & vbTab 
    Next j 
    Debug.Print Trim$(TempBuf1) 
    Debug.Print Trim$(TempBuf2) 

    For i = RowIdxLBound To RowIdxUBound 
     TempBuf1 = vbNullString 
     #If Std2DArray Then 
      For j = ColIdxLBound To ColIdxUBound 
       TempBuf1 = TempBuf1 & TestArr(i, j) & vbTab 
      Next j 
     #Else 
      For j = LBound(TestArr(i)) To UBound(TestArr(i)) 'Handles jagged array-of-arrays 
       TempBuf1 = TempBuf1 & TestArr(i)(j) & vbTab 
      Next j 
     #End If 
     Debug.Print Format(i, IIf(i >= 0, " 0", "#0")) & ":" & vbTab & Trim$(TempBuf1) 
    Next i 

    'Get the slice 

    Sliced = ArraySlice(Arr2D:=TestArr, ByCol:=ByCol, SliceIdx:=SliceIdx) 

    If Not IsEmpty(Sliced) Then 
     'Succeeded, so print the 1D slice array 

     PadTabs = String(SliceIdx - LBound(Sliced), vbTab) 
     Debug.Print 
     If ByCol Then 
'   Debug.Print vbTab & PadTabs & Format(SliceIdx, IIf(SliceIdx >= 0, " 0", "#0")) 
'   Debug.Print vbTab & PadTabs & ".." 

      For i = LBound(Sliced) To UBound(Sliced) 
       Debug.Print Format(i, IIf(i >= 0, " 0", "#0")) & ": " & PadTabs & Sliced(i) 
      Next i 
     Else 
      TempBuf1 = Format(SliceIdx, IIf(SliceIdx >= 0, " 0", "#0")) & ":" & vbTab 
      For i = LBound(Sliced) To UBound(Sliced) 
       TempBuf1 = TempBuf1 & Sliced(i) & vbTab 
      Next i 

      Debug.Print TempBuf1 
     End If 
    Else 
     MsgBox "The ArraySlice function call failed" 
    End If 

    Debug.Print "+-----------------+" 
End Sub 
Смежные вопросы