I have been working on this particular problem for sometime and am obviously missing something very simple. I ma trying to create an aray based on a dynamic range in Excel and using the individual elements to compare against another array. The only problem with the attached code is it continues to show empty elements. Any guidance would be appreciated.
Part of my overall code attached.
Sub Test_Again()
Dim R As Long
Dim C As Long
Dim List() As Variant
Dim i As Integer
List = Sheets("Sheet11").Range("A2:A17").Value
For R = 1 To UBound(List, 1) ' First array dimension is rows.
For C = 1 To UBound(List, 2) ' Second array dimension is columns.
Debug.Print List(R, C)
Next C
Next R
ReDim List(UBound(List, 1))
Do Until i = UBound(List)
If List(i) = Now() Then
End If
i = i + 1
Loop
End Sub
The normal Redim will clear your array - unless you use Redim Preserve. However, according to the help:
If you use the Preserve keyword, you can resize only the last array dimension and you can't change the number of dimensions at all. For example, if your array has only one dimension, you can resize that dimension because it is the last and only dimension. However, if your array has two or more dimensions, you can change the size of only the last dimension and still preserve the contents of the array.
Therefore, in your case Redim will not help you here. If you want to transfer a two dimensional array to a one dimensional array, you need to do this manually instead:
Sub Test_New()
Dim lRow As Long, lCol As Long
Dim vListSource() As Variant, vListTarget() As Variant
'Assign soure array
vListSource = Sheets("Sheet11").Range("A2:A17").Value
'Show full content for debug
For lRow = LBound(vListSource) To UBound(vListSource) ' First array dimension is rows.
For lCol = LBound(vListSource, 2) To LBound(vListSource, 2) ' Second array dimension is columns.
Debug.Print vListSource(lRow, lCol)
Next lCol
Next lRow
'Transfer array to one dimension
ReDim vListTarget(LBound(vListSource) To UBound(vListSource))
For lRow = LBound(vListSource) To UBound(vListSource)
vListTarget(lRow) = vListSource(lRow, LBound(vListSource, 2))
Next lRow
'Your check code
For lRow = LBound(vListTarget) To UBound(vListTarget)
If vListTarget(lRow) = Now() Then
'Do something here
End If
Next lRow
End Sub
This will copy the first row of your range/array to a one dimensional array and use this for further processing.
However, from your code and question I do not see the advantage of redimming it to one dimension - you could easily do your loop one the two dimensional array - and just look in the first and only column.
Related
I am new to StackOverflow, apologies in advance if I am not going about this in the right way.
I have some raw data that looks like the following:
All the values are separated by commas, in this string I am looking to find if the full range of numbers (1,2,3,4,5) is found, if it does, then it should return a 100% match.
In case only 4 numbers out of this range are found then it should return 80%, for 3 numbers 60%, for 2 numbers 40%, for 1 number 10% and in case none are found it should return "none". (see desired output below)
I am still new to VBA, but my thought was to split my comma separated values into an array, and then try to find a match. However unfortunately I already got stuck at the first match (i.e. finding 100%).
Sub CheckNumberMatches()
Dim i As Long
Dim Elem As Variant
Dim myArr As Variant
With Worksheets("data")
For i = 1 To .Cells(.Rows.Count, "A").End(xlUp).Row
myArr = Split(.Range("A" & i).Value, ",")
' loop through array elements and look for 1,2,3,4,5
For Each Elem In myArr
If Elem Like "1,2,3,4,5" Then
.Range("B" & i).Value = "100%"
Exit For
End If
Next Elem
Next i
End With
End Sub
After #FunThomas his reply, I realize my requirements are not very clear, let me provide a few more examples of what can happen:
The main criteria is (1,2,3,4,5) needs to be found in the cell, but this does not need to be in numerical order, i.e. can be random (2,4,1,3,5). If all these numbers are found in any order it should be 100%.
If for example all five numbers are found (1,2,3,4,5) in the cell, but the cell also includes other numbers (1,2,3,4,5,6,7) - it should still be counted as 100%.
If for example only four numbers of the main criteria are found (for example: 1,2,4,5) it should be considered as 80% (as long as 4 out of main numbers are found), likewise for 3, 2, 1 and 0 matches.
The data can have gaps, i.e. it can be a range of (5,2,7,11,12), in this particular example it should be counted as 40% (2 out of 5 choices are found).
Duplicate numbers are not possible.
I hope that clarifies.
Array approach
Instead of looping through each cell in column "A" which can be time consuming, you can benefit from using arrays:
Assign data to 1-based 2-dim data field array (see section 3),
analyze each splitted element cur in a single loop (section 4), where counting the result of Match() with two array inputs receives the wanted information in one go via
Application.Count(Application.Match(cur, base, 0))
Note that Application.Count() neglects errors resulting from non-findings.
All infos are reassigned to the data array and written back via rng.Offset(, 1) = data
Sub FoundBaseNumbers()
With Tabelle1
'1. Assign needed base numbers to 1-dim array
Dim base As Variant: base = Split("1,2,3,4,5", ",")
'2. Define data range
Dim lastRow As Long
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
Dim rng As Range
Set rng = .Range("A1:A" & lastRow)
'3. Assign data to 1-based 2-dim data field array
Dim data As Variant: data = rng.Value2
'4. Analyze data
Dim i As Long, cur As Variant, cnt As Long
For i = 1 To UBound(data)
'a) count findings of current elements
cur = Split(data(i, 1), ",")
cnt = Application.Count(Application.Match(cur, base, 0))
'b) remember percentages using data field array
data(i, 1) = IIf(cnt, Format(cnt / (UBound(base) + 1), "0%"), "None")
Next i
'5. Write data to neighbour column
rng.Offset(, 1) = data
End With
End Sub
You started off well for your code. There are many ways something like this can be done. I've done up a simple way for you utiziling your code already. Have a look below and see if it's right for you.
I used Select Case as it allows to check for multiple things a lot easier than an If statement. You can even use ranges like Case 1 To 10. You can also do multiple Case lines to have different results do different things (like an ElseIf) etc.
Sub CheckNumberMatches()
Dim i As Long, Elem As Variant, myArr As Variant, Counter As Long
With Worksheets("data")
For i = 1 To .Cells(.Rows.Count, "A").End(xlUp).Row
myArr = Split(.Range("A" & i).Value, ",")
Counter = 0
' loop through array elements and look for 1,2,3,4,5
For Each Elem In myArr
Select Case Elem
Case 1, 2, 3, 4, 5
Counter = Counter + 1
End Select
Next
If Counter > 0 Then
.Cells(i, "B").Value = Format(Counter / 5, "0%")
Else
.Cells(i, "B").Value = "None"
End If
Next i
End With
End Sub
I am doing some code to put a range into an array so i can create plots by analyzing the data inside that array. I am trying to use a general code for the range since the input can be different depending on the type of analysis i want to perform. Tried to find a solution for this in other questions without success.
Dim DieBankArray As Variant
last_row = Sheets("Tabela CT geral").Range("A2").End(xlDown).Row 'Last row of the data set
For i = 0 To last_row - 2 '-2 to exclude the first line and another value because the array first position is 0, not 1
DieBankArray(i) = Range("A" & i + 2)
Next
The return is a type mismatch error that i can't understand...
Here's one approach:
Function RangeTo1DArray(rngStart As Range)
Dim rv(), arr, r As Long, n As Long
'read the source data to an array for better performance
With rngStart.Parent
arr = .Range(rngStart, .Cells(Rows.Count, rngStart.Column).End(xlUp)).Value
End With
n = UBound(arr, 1)
ReDim rv(0 To n - 1)
'Fill the output array. Note: purposefully not using transpose()
' to avoid its limitations
For r = 1 To n
rv(r - 1) = arr(r, 1)
Next r
RangeTo1DArray= rv
End Function
Ok, i used the Redim and it worked just fine.
What i couldn't understand is that there's a need to set the correct size of an array to read/write data. I thought a simple Dim as Variant should be enough to store the data at my will without need to set a correct size each time i want to use an array.
The code after ReDim:
Dim DieBankArray As Variant
last_row = Sheets("Tabela CT geral").Range("A2").End(xlDown).Row 'Last row of the data set
ReDim DieBankArray(A2 To last_row - 2)
For i = 0 To last_row - 2 '-2 to exclude the first line and another value because the array first position is 0, not 1
DieBankArray(i) = Range("A" & i + 2)
Next
I have to move data in my input files to another workbook. The data is structured in worksheets as hardcoded input as below where the column with all the identifiers is a named range called "INPUT_MARKER".
IQ_SALES 100 200 300
IS_MARGIN 20 30 40
IQ_EBITDA 50 30 20
I only have to move some of the data. So for instance in the above I would only have to move the IQ_SALES data and IQ_EBITDA data. So I need to understand how to create an array of arrays with only the data that is needed.
The code below compares the data in the INPUT_MARKER column with elements in the array called "identifierArray" and I then intend to insert all of the row data in the multidimensional array called "bigDataArray". I have tried several approaches but have not been able to make this work. Would much appreciate any help. I have left out some of the redundant code in the below such that only the code pertaining to this problem is included.
Sub Update()
Dim identifierArray(), bigDataArray() As Variant
Application.ScreenUpdating = False
Application.CutCopyMode = False
'Definition of the array of data that is to be transferred to the targetModel
identifierArray = Array("IQ_SALES", "IQ_EBITDA")
ReDim bigDataArray(1 To UBound(identifiersArray))
With Workbooks(sourceModel).Sheets("DATA")
For Each c In .Range("INPUT_MARKER")
For Each element In identifierArray
If element = c.Value Then
'To construct bigDataArray by inserting row data every time element equals c.Value
End If
Next element
Next c
End With
End Sub
I've tackled a similar issue recently. This can be handled with a multi-dimensional array from the look of things
Though as a predisposition I'd recommend checking reference on dynamic
multi-dimensional
arrays
Private Sub fill_array()
Dim arr() As String
Dim i As Integer: i = 0
Dim cell As Range
Dim ws As Worksheet: Set ws = Sheets("DATA")
For Each cell In ws.Range("INPUT_MARKER")
If ws.Cells(cell.Row, 1) = "IQ_SALES" Or ws.Cells(cell.Row, 1) = "IQ_EBITDA" Then
ReDim Preserve arr(0 To 2, i)
arr(0, i) = ws.Cells(cell.Row, 2)
arr(1, i) = ws.Cells(cell.Row, 3)
arr(2, i) = ws.Cells(cell.Row, 4)
i = i + 1
End If
Next cell
End Sub
So your array will have the structure ofarr(x, y), where:
x - [0;2] - will be the 3 columns of data you want to store
y - n - index of the array (with only IQ_SALES and IQ_EBITDA being added)
EDIT:
This is of course presuming, your data "INPUT_MARKER" starts at
Column A
Also as an extra tip, if you want to also store information of the arrays source - in resemblence of a primary key, you can increment the first dimension
ReDim Preserve arr(0 to 3, i)
arr(3, i) = cell.Row ' edited (instead of arr(3)= …)
and use example the cell.Row as a reference as to where the data was obtained from, in order to reverse trace the data
If you know the range of the values you want to pick you can shortcut using:
Dim bigDataArray() As Variant
bigDataArray = Range(A1:D4)
This will set up the array with the same size as the range you pick up,
Then you can output the specific values you want from the array.
I am trying to multiply two arrays placing the value of each iteration into a spreadsheet. Here is what I have so far:
Sub Test1()
Dim x As Long
Dim myArray
Dim myArrayAdj
myArray = Array(24800, 26300, 27900)
myArrayAdj = Array(1.0025, 1.005, 1.0075, 1.01)
For x = 1 To 1000
Cells(x, x) = myArray * myArrayAdj
Next x
End Sub
When I run this I get a Run-time13 error with the following highlighted:
Cells(x, x) = myArray * myArrayAdj
Can someone explain to me where I've gone wrong? Thank you!
Your have a few issues which I think I've corrected below. One of the things you'll notice about my code is that I use Variant variables to loop through the arrays instead of identifying the element by number (e.g. myArrayElm instead of myArray(x)). This is just my personal preference.
Sub Test1()
Dim x As Long
Dim myArray 'Your first array
Dim myArrayElm 'A variable for the elements in your first array
Dim myArrayAdj 'Your second array
Dim myArrayAdjElm 'A variable for the elements in your second array
'Add values to your arrays
myArray = Array(24800, 26300, 27900)
myArrayAdj = Array(1.0025, 1.005, 1.0075, 1.01)
'Loop through the elements in your first array
For Each myArrayElm In myArray
'Loop through the elements in your second array
For Each myArrayAdjElm In myArrayAdj
x = x + 1
'Multiply the two array elements together
Cells(x, 1) = myArrayElm * myArrayAdjElm
Next myArrayAdjElm
Next myArrayElm
End Sub
This code loops through each element in both arrays, multiplies the two elements, and stores the values in a list beginning in cell A1.
Now, if you have a large dataset you're working with, the below example will be more efficient and will finish quicker since it stores the results in another array and then pastes the results to a sheet all at once instead of individually:
Option Base 1
Sub Test1()
Dim x As Long
Dim myArray 'Your first array
Dim myArrayElm 'A variable for the elements in your first array
Dim myArrayAdj 'Your second array
Dim myArrayAdjElm 'A variable for the elements in your second array
Dim Results 'An array for your results
Dim r As Range 'Range to store values
'Add values to your arrays
myArray = Array(24800, 26300, 27900)
myArrayAdj = Array(1.0025, 1.005, 1.0075, 1.01)
'Set the size of the results array
ReDim Results(1 To UBound(myArray) * UBound(myArrayAdj))
'Loop through the elements in your first array
For Each myArrayElm In myArray
'Loop through the elements in your second array
For Each myArrayAdjElm In myArrayAdj
x = x + 1
'Multiply the two array elements together
Results(x) = myArrayElm * myArrayAdjElm
Next myArrayAdjElm
Next myArrayElm
'Set the destination range
Set r = Range("A1:A" & UBound(Results))
'Paste results to sheet
r = Application.Transpose(Results)
End Sub
Note the Option Base 1 at the top. This just means that all the arrays will now start at element 1 instead of element 0 which is the default.
The whole problem stems from your statement right here: "I want to multiply two arrays"
I assume by that you meant you would like to multiple the individual elements in the two arrays, one by one.
In that case you want to do something this:
Cells(x, x) = myArray(x) * myArrayAdj(x)
That said, I'm not sure whether your intention was to store the results of the multiplication in cells on the diagonal of the work sheet or in some other place
If it's the former then Cells(x,x) makes sense but if it's the latter, than you need to be more specific about your expectation in multiplying the two arrays.
Could you please say- how a Excel Range("G2:AA1000") can be assigned to a 2D array? If possible how to return back that 2D array to the same range after performing some operation on that 2D array?After assignment a Range to an 2D array,How each row will be identified from that 2D matrix?
Thanks,
There is an easy way to make changes to an area using an array, and write it out to the same place, or somewhere else.
This example code will copy data from one area to another, using an array:
Sub example()
Dim testdata()
testdata = Range("A1:B13")
Range("D1:E13") = testdata ' simple copy
Range("G1") = testdata ' copy only 1 cell
Range("I1:K22") = testdata 'try to copy too much
End Sub
The testdata array starts from 1, and will extend to the number of columns and rows specified in the range. In this case, testdata(1,1) refers to the data obtained from A1, testdata(1,2) refers to B1, finishing up with testdata(13,1) referring to A13, and testdata(13,2) referring to B13.
Setting the range equal to the array in the next line copies the array into the specified location.
If the area is smaller than the original array, it will copy only enough of the array to fill that space, so Range("D1")=testdata will only place one cell on the sheet.
If you specify a larger area, then #N/A will fill the area that is not in the space covered by array elements, so Range("A1:A3")=testdata will fill A1 and A2 with data from the array, but A3 will have #N/A
Result of example program:
Note: A1:B13 is the original data, which gets copied with the subsequent range(??)=testdata
Here's a worked-out example of reading a range of data from a worksheet, operating on the array, and then writing it back out to the same worksheet.
Sub RangeArray()
Dim Rng As Range
Dim Arr()
Dim ArrItem
Dim i As Long, j As Long
Dim rUB as Long, cUB as Long
Set Rng = Worksheets("Sheet1").Range("A1:G19")
rUB = Rng.Rows.Count 'Row upper bound
cUB = Rng.Columns.Count ' Column upper bound
ReDim Arr(1 To rUB, 1 To cUB)
'Read worksheet range into array
For i = 1 To rUB
For j = 1 to cUB
Arr(i, j) = Rng.Cells(i, j).Value
Next
Next
'Do something to array
For i = 1 To rUB
For j = 1 To cUB
If i <> j Then
Arr(i, j) = Arr(i, j) / (i * j)
End If
Next
Next
'Write array back to worksheet
Set Rng = Worksheets("Sheet1").Range("I1")
For i = 1 To rUB
For j = 1 To cUB
Rng.Offset(i - 1, j - 1).Value = Arr(i, j)
Next
Next
End Sub
Yes, an Excel range can be assigned to a 2D array in one single assignment. In C++/CLI it looks like this:
cli::array<Object^, 2>^ arrData = safe_cast<cli::array<Object^, 2>^>(rg->Value[Excel::XlRangeValueDataType::xlRangeValueDefault]);
In c# or visual basic it would look considerably simpler (see here for example https://www.automateexcel.com/vba/assign-range-to-array/, btw in dotnet the object is now playing the role of the variant data type). Note that it must be a two dimensional array and the returned array has a one-based indexing, and not a zero based indexing.
For large data sets this method is a lot faster than the looping. The looping generates lots of COM objects. I compared the two methods with a 33000 row Excel range and the data import into the array was almost instantaneous while with the looping it took very long and it heated up the CPU.
One way to loop through a range is to use the For...Next loop with the Cells property. Using the Cells property, you can substitute the loop counter (or other variables or expressions) for the cell index numbers. In the following example, the variable counter is substituted for the row index. The procedure loops through the range C1:C20, setting to 0 (zero) any number whose absolute value is less than 0.01.
Sub RoundToZero1()
For Counter = 1 To 20
Set curCell = Worksheets("Sheet1").Cells(Counter, 3)
If Abs(curCell.Value) < 0.01 Then curCell.Value = 0
Next Counter
End Sub