VBA Excel 2-мерные массивы
Я пытался выяснить, как объявить 2-мерный массив, но все примеры, которые я нашел до сих пор, объявлены целыми числами. Я пытаюсь создать программу, которая будет использовать два двумерных массива, а затем выполнять простые операции с этими массивами (например, находить разницу или процент). Массивы заполняются числами на листах Excel (один набор чисел находится на листе 1, а другой - на листе 2, оба набора имеют одинаковое количество строк и столбцы.)
Так как я не знаю, сколько строк или столбцов есть я собирался использовать переменные.
Dim s1excel As Worksheet
Dim s2excel As Worksheet
Dim s3excel As Worksheet
Dim firstSheetName As String
Dim secondSheetName As String
Dim totalRow As Integer
Dim totalCol As Integer
Dim iRow As Integer
Dim iCol As Integer
Set s1excel = ThisWorkbook.ActiveSheet
' Open the "Raw_Data" workbook
Set wbs = Workbooks.Open(file_path & data_title)
wbs.Activate
ActiveWorkbook.Sheets(firstSheetName).Select
Set s2excel = wbs.ActiveSheet
' Find totalRow, totalColumn (assumes there's values in Column A and Row 1 with no blanks)
totalRow = ActiveSheet.Range("A1").End(xlDown).Row
totalCol = ActiveSheet.Range("A1").End(xlToRight).Column
Dim s2Array(totalRow, totalCol)
Dim s3Array(totalRow, totalCol)
For iRow = 1 To totalRow
For iCol = 1 To totalCol
s2Array(iRow, iCol) = Cells(iRow, iCol)
Next iCol
Next iRow
ActiveWorkbook.Sheets(secondSheetName).Select
Set s3excel = wbs.ActiveSheet
For iRow = 1 To totalRow
For iCol = 1 To totalCol
s3Array(iRow, iCol) = Cells(iRow, iCol)
Next iCol
Next iRow
когда я пытаюсь запустить это, я получаю ошибку времени компиляции в " Dim s2Array (totalRow, totalCol)", говоря, что требуется постоянное выражение. Та же ошибка возникает, если я изменяю ее на "Dim s2Array(1 to totalRow, 1 to totalCol)". Поскольку я не знаю, каковы размеры с самого начала, я не могу объявить его как " Dim s2Array(1, 1)", потому что тогда я получу исключение из правил.
спасибо,
Джесси Smothermon
4 ответов
на самом деле я бы не использовал ни REDIM, ни цикл для передачи данных из листа в массив:
dim arOne()
arOne = range("A2:F1000")
или даже
arOne = range("A2").CurrentRegion
и все, Ваш массив заполняется намного быстрее, чем с циклом, без redim.
вам нужно ReDim
:
m = 5
n = 8
Dim my_array()
ReDim my_array(1 To m, 1 To n)
For i = 1 To m
For j = 1 To n
my_array(i, j) = i * j
Next
Next
For i = 1 To m
For j = 1 To n
Cells(i, j) = my_array(i, j)
Next
Next
как указывали другие, ваша фактическая проблема была бы лучше решена с диапазонами. Вы можете попробовать что-то вроде этого:
Dim r1 As Range
Dim r2 As Range
Dim ws1 As Worksheet
Dim ws2 As Worksheet
Set ws1 = Worksheets("Sheet1")
Set ws2 = Worksheets("Sheet2")
totalRow = ws1.Range("A1").End(xlDown).Row
totalCol = ws1.Range("A1").End(xlToRight).Column
Set r1 = ws1.Range(ws1.Cells(1, 1), ws1.Cells(totalRow, totalCol))
Set r2 = ws2.Range(ws2.Cells(1, 1), ws2.Cells(totalRow, totalCol))
r2.Value = r1.Value
вот!--5-->универсальный массив VBA для функции диапазона который записывает массив на лист одним "ударом" на лист. Это много быстрее, чем запись данных в лист по одной ячейке за раз в циклах для строк и столбцов... Тем не менее, есть некоторая уборка, так как вы должны правильно указать размер целевого диапазона.
эта "уборка" выглядит как много работы, и это, вероятно, довольно медленно: но это код "последней мили", чтобы написать к простыне, и все быстрее, чем запись на рабочий лист. Или, по крайней мере, настолько быстрее, что это эффективно мгновенно, по сравнению с чтением или записью на рабочий лист, даже в VBA, и вы должны сделать все возможное в коде, прежде чем попасть на лист.
основным компонентом этого является ловушка ошибок, которую я раньше видел повсюду . Я ненавижу повторяющееся кодирование: я закодировал все это здесь, и-надеюсь - вам никогда не придется писать снова.
функция VBA 'Array to Range'
Public Sub ArrayToRange(rngTarget As Excel.Range, InputArray As Variant)
' Write an array to an Excel range in a single 'hit' to the sheet
' InputArray must be a 2-Dimensional structure of the form Variant(Rows, Columns)
' The target range is resized automatically to the dimensions of the array, with
' the top left cell used as the start point.
' This subroutine saves repetitive coding for a common VBA and Excel task.
' If you think you won't need the code that works around common errors (long strings
' and objects in the array, etc) then feel free to comment them out.
On Error Resume Next
'
' Author: Nigel Heffernan
' HTTP://Excellerando.blogspot.com
'
' This code is in te public domain: take care to mark it clearly, and segregate
' it from proprietary code if you intend to assert intellectual property rights
' or impose commercial confidentiality restrictions on that proprietary code
Dim rngOutput As Excel.Range
Dim iRowCount As Long
Dim iColCount As Long
Dim iRow As Long
Dim iCol As Long
Dim arrTemp As Variant
Dim iDimensions As Integer
Dim iRowOffset As Long
Dim iColOffset As Long
Dim iStart As Long
Application.EnableEvents = False
If rngTarget.Cells.Count > 1 Then
rngTarget.ClearContents
End If
Application.EnableEvents = True
If IsEmpty(InputArray) Then
Exit Sub
End If
If TypeName(InputArray) = "Range" Then
InputArray = InputArray.Value
End If
' Is it actually an array? IsArray is sadly broken so...
If Not InStr(TypeName(InputArray), "(") Then
rngTarget.Cells(1, 1).Value2 = InputArray
Exit Sub
End If
iDimensions = ArrayDimensions(InputArray)
If iDimensions < 1 Then
rngTarget.Value = CStr(InputArray)
ElseIf iDimensions = 1 Then
iRowCount = UBound(InputArray) - LBound(InputArray)
iStart = LBound(InputArray)
iColCount = 1
If iRowCount > (655354 - rngTarget.Row) Then
iRowCount = 655354 + iStart - rngTarget.Row
ReDim Preserve InputArray(iStart To iRowCount)
End If
iRowCount = UBound(InputArray) - LBound(InputArray)
iColCount = 1
' It's a vector. Yes, I asked for a 2-Dimensional array. But I'm feeling generous.
' By convention, a vector is presented in Excel as an arry of 1 to n rows and 1 column.
ReDim arrTemp(LBound(InputArray, 1) To UBound(InputArray, 1), 1 To 1)
For iRow = LBound(InputArray, 1) To UBound(InputArray, 1)
arrTemp(iRow, 1) = InputArray(iRow)
Next
With rngTarget.Worksheet
Set rngOutput = .Range(rngTarget.Cells(1, 1), rngTarget.Cells(iRowCount + 1, iColCount))
rngOutput.Value2 = arrTemp
Set rngTarget = rngOutput
End With
Erase arrTemp
ElseIf iDimensions = 2 Then
iRowCount = UBound(InputArray, 1) - LBound(InputArray, 1)
iColCount = UBound(InputArray, 2) - LBound(InputArray, 2)
iStart = LBound(InputArray, 1)
If iRowCount > (65534 - rngTarget.Row) Then
iRowCount = 65534 - rngTarget.Row
InputArray = ArrayTranspose(InputArray)
ReDim Preserve InputArray(LBound(InputArray, 1) To UBound(InputArray, 1), iStart To iRowCount)
InputArray = ArrayTranspose(InputArray)
End If
iStart = LBound(InputArray, 2)
If iColCount > (254 - rngTarget.Column) Then
ReDim Preserve InputArray(LBound(InputArray, 1) To UBound(InputArray, 1), iStart To iColCount)
End If
With rngTarget.Worksheet
Set rngOutput = .Range(rngTarget.Cells(1, 1), rngTarget.Cells(iRowCount + 1, iColCount + 1))
Err.Clear
Application.EnableEvents = False
rngOutput.Value2 = InputArray
Application.EnableEvents = True
If Err.Number <> 0 Then
For iRow = LBound(InputArray, 1) To UBound(InputArray, 1)
For iCol = LBound(InputArray, 2) To UBound(InputArray, 2)
If IsNumeric(InputArray(iRow, iCol)) Then
' no action
Else
InputArray(iRow, iCol) = "" & InputArray(iRow, iCol)
InputArray(iRow, iCol) = Trim(InputArray(iRow, iCol))
End If
Next iCol
Next iRow
Err.Clear
rngOutput.Formula = InputArray
End If 'err<>0
If Err <> 0 Then
For iRow = LBound(InputArray, 1) To UBound(InputArray, 1)
For iCol = LBound(InputArray, 2) To UBound(InputArray, 2)
If IsNumeric(InputArray(iRow, iCol)) Then
' no action
Else
If Left(InputArray(iRow, iCol), 1) = "=" Then
InputArray(iRow, iCol) = "'" & InputArray(iRow, iCol)
End If
If Left(InputArray(iRow, iCol), 1) = "+" Then
InputArray(iRow, iCol) = "'" & InputArray(iRow, iCol)
End If
If Left(InputArray(iRow, iCol), 1) = "*" Then
InputArray(iRow, iCol) = "'" & InputArray(iRow, iCol)
End If
End If
Next iCol
Next iRow
Err.Clear
rngOutput.Value2 = InputArray
End If 'err<>0
If Err <> 0 Then
For iRow = LBound(InputArray, 1) To UBound(InputArray, 1)
For iCol = LBound(InputArray, 2) To UBound(InputArray, 2)
If IsObject(InputArray(iRow, iCol)) Then
InputArray(iRow, iCol) = "[OBJECT] " & TypeName(InputArray(iRow, iCol))
ElseIf IsArray(InputArray(iRow, iCol)) Then
InputArray(iRow, iCol) = Split(InputArray(iRow, iCol), ",")
ElseIf IsNumeric(InputArray(iRow, iCol)) Then
' no action
Else
InputArray(iRow, iCol) = "" & InputArray(iRow, iCol)
If Len(InputArray(iRow, iCol)) > 255 Then
' Block-write operations fail on strings exceeding 255 chars. You *have*
' to go back and check, and write this masterpiece one cell at a time...
InputArray(iRow, iCol) = Left(Trim(InputArray(iRow, iCol)), 255)
End If
End If
Next iCol
Next iRow
Err.Clear
rngOutput.Text = InputArray
End If 'err<>0
If Err <> 0 Then
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
iRowOffset = LBound(InputArray, 1) - 1
iColOffset = LBound(InputArray, 2) - 1
For iRow = 1 To iRowCount
If iRow Mod 100 = 0 Then
Application.StatusBar = "Filling range... " & CInt(100# * iRow / iRowCount) & "%"
End If
For iCol = 1 To iColCount
rngOutput.Cells(iRow, iCol) = InputArray(iRow + iRowOffset, iCol + iColOffset)
Next iCol
Next iRow
Application.StatusBar = False
Application.ScreenUpdating = True
End If 'err<>0
Set rngTarget = rngOutput ' resizes the range This is useful, *most* of the time
End With
End If
End Sub
вам понадобится источник для ArrayDimensions:
это объявление API требуется в заголовке модуля:
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
(Destination As Any, _
Source As Any, _
ByVal Length As Long)
...И вот сама функция:
Private Function ArrayDimensions(arr As Variant) As Integer
'-----------------------------------------------------------------
' will return:
' -1 if not an array
' 0 if an un-dimmed array
' 1 or more indicating the number of dimensions of a dimmed array
'-----------------------------------------------------------------
' Retrieved from Chris Rae's VBA Code Archive - http://chrisrae.com/vba
' Code written by Chris Rae, 25/5/00
' Originally published by R. B. Smissaert.
' Additional credits to Bob Phillips, Rick Rothstein, and Thomas Eyde on VB2TheMax
Dim ptr As Long
Dim vType As Integer
Const VT_BYREF = &H4000&
'get the real VarType of the argument
'this is similar to VarType(), but returns also the VT_BYREF bit
CopyMemory vType, arr, 2
'exit if not an array
If (vType And vbArray) = 0 Then
ArrayDimensions = -1
Exit Function
End If
'get the address of the SAFEARRAY descriptor
'this is stored in the second half of the
'Variant parameter that has received the array
CopyMemory ptr, ByVal VarPtr(arr) + 8, 4
'see whether the routine was passed a Variant
'that contains an array, rather than directly an array
'in the former case ptr already points to the SA structure.
'Thanks to Monte Hansen for this fix
If (vType And VT_BYREF) Then
' ptr is a pointer to a pointer
CopyMemory ptr, ByVal ptr, 4
End If
'get the address of the SAFEARRAY structure
'this is stored in the descriptor
'get the first word of the SAFEARRAY structure
'which holds the number of dimensions
'...but first check that saAddr is non-zero, otherwise
'this routine bombs when the array is uninitialized
If ptr Then
CopyMemory ArrayDimensions, ByVal ptr, 2
End If
End Function
также: я бы посоветовал вам сохранить эту декларацию в тайне. Если вы должны сделать его общедоступным в другом модуле, вставьте Option Private Module
заявление в заголовке модуля. Ты правда ... не хотите, чтобы ваши пользователи вызывали какие-либо функции с помощью CopyMemoryoperations и арифметики указателей.
для этого примера вам нужно будет создать свой собственный тип, который будет массивом. Затем вы создаете больший массив, элементы которого имеют тип, который вы только что создали.
чтобы запустить мой пример, вам нужно будет заполнить столбцы A и B на Лист1 С некоторых значений. Затем запустите test (). Он будет читать первым из двух строк и добавьте значения в BigArr. Затем он проверит, сколько строк данных у вас есть и прочитал их все, с того места, где перестал читать, т. е. 3-й ряд.
протестировано в Excel 2007.
Option Explicit
Private Type SmallArr
Elt() As Variant
End Type
Sub test()
Dim x As Long, max_row As Long, y As Long
'' Define big array as an array of small arrays
Dim BigArr() As SmallArr
y = 2
ReDim Preserve BigArr(0 To y)
For x = 0 To y
ReDim Preserve BigArr(x).Elt(0 To 1)
'' Take some test values
BigArr(x).Elt(0) = Cells(x + 1, 1).Value
BigArr(x).Elt(1) = Cells(x + 1, 2).Value
Next x
'' Write what has been read
Debug.Print "BigArr size = " & UBound(BigArr) + 1
For x = 0 To UBound(BigArr)
Debug.Print BigArr(x).Elt(0) & " | " & BigArr(x).Elt(1)
Next x
'' Get the number of the last not empty row
max_row = Range("A" & Rows.Count).End(xlUp).Row
'' Change the size of the big array
ReDim Preserve BigArr(0 To max_row)
Debug.Print "new size of BigArr with old data = " & UBound(BigArr)
'' Check haven't we lost any data
For x = 0 To y
Debug.Print BigArr(x).Elt(0) & " | " & BigArr(x).Elt(1)
Next x
For x = y To max_row
'' We have to change the size of each Elt,
'' because there are some new for,
'' which the size has not been set, yet.
ReDim Preserve BigArr(x).Elt(0 To 1)
'' Take some test values
BigArr(x).Elt(0) = Cells(x + 1, 1).Value
BigArr(x).Elt(1) = Cells(x + 1, 2).Value
Next x
'' Check what we have read
Debug.Print "BigArr size = " & UBound(BigArr) + 1
For x = 0 To UBound(BigArr)
Debug.Print BigArr(x).Elt(0) & " | " & BigArr(x).Elt(1)
Next x
End Sub