I initially asked this question How to loop through a specific row of a 2-dimensional Array?
and #FaneDuru was kind enough to supply a solution but now I am hoping I can take it one step further and use a 3 dimensional array in order to obtain the item numbers needed for the second iteration I will be required to do. Initially I thought I would asssume the second iteration was the same as the first and just multiply my results by 2 but I would prefer using a 3-d Array in my solution. Here is what I got. I do not know how to display the results of the other index/iteration?
Dim SWArray() As Variant
ReDim SWArray(1 To 5, 1 To 10, 1 To 2)
SWArray(1, 1) = "Bay1"
SWArray(1, 2) = "Bay2"
SWArray(1, 3) = "Bay3"
SWArray(1, 4) = "Bay4"
SWArray(1, 5) = "Bay5"
SWArray(1, 6) = "Bay6"
SWArray(1, 7) = "Bay7"
SWArray(1, 8) = "Bay8"
SWArray(1, 9) = "Bay9"
SWArray(1, 10) = "Bay10"
SWArray(2, 1) = Bay1
SWArray(2, 2) = Bay2
SWArray(2, 3) = Bay3
SWArray(2, 4) = Bay4
SWArray(2, 5) = Bay5
SWArray(2, 6) = Bay6
SWArray(2, 7) = Bay7
SWArray(2, 8) = Bay8
SWArray(2, 9) = Bay9
SWArray(2, 10) = Bay10
'Loop through bays to assign purlin, girt and
'formboard item numbers per the dimension
For k = LBound(SWArray, 3) To UBound(SWArray, 3)
For i = LBound(SWArray, 2) To UBound(SWArray, 2)
If SWArray(2, i) = 0 Then
SWArray(2, i) = 0
SWArray(3, i) = 0
SWArray(4, i) = 0
ElseIf SWArray(2, i) > 6 And SWArray(2, i) <= 10 Then
SWArray(2, i) = 2035
SWArray(3, i) = 2754
SWArray(4, i) = 2004
ElseIf SWArray(2, i) > 10 And SWArray(2, i) <= 12 Then
SWArray(2, i) = 2036
SWArray(3, i) = 2755
SWArray(4, i) = 2005
ElseIf SWArray(2, i) > 12 And SWArray(2, i) <= 14 Then
SWArray(2, i) = 2037
SWArray(3, i) = 2756
SWArray(4, i) = 2006
ElseIf SWArray(2, i) > 14 And SWArray(2, i) <= 16 Then
SWArray(2, i) = 2038
SWArray(3, i) = 2757
SWArray(4, i) = 2007
End If
Next i
Next k
Worksheets("Data").Range("A55").Resize(UBound(SWArray),
UBound(SWArray, 2)).Value = SWArray
The next piece of code will show how a 3D array is loaded and how its elements will be extracted by iteration. In order to make the example eloquent, please prepare two Excel sheets, in the workbook keeping the next code (ThisWorkbook), named Test_1 and Test_2. Please, place 10 (different) headers on their first row and fill 5 rows of each with different values. Then, copy the next code in a standard module and run it:
Sub testIterate3DArrayExcelExample()
Dim SWArray(1 To 5, 1 To 10, 1 To 2)
Dim wb As Workbook, iRow As Long, iCol As Long, iSht As Long
Set wb = ThisWorkbook
For iRow = 1 To UBound(SWArray, 1)
For iCol = 1 To UBound(SWArray, 2)
For iSht = 1 To UBound(SWArray, 3)
SWArray(iRow, iCol, iSht) = wb.Worksheets("Test_" & iSht).cells(iRow, iCol)
Next iSht
Next iCol
Next iRow
Dim i As Long, j As Long, k As Long
For i = 1 To UBound(SWArray, 1)
For j = 1 To UBound(SWArray, 2)
For k = 1 To UBound(SWArray, 3)
Debug.Print "Sheet Test_" & k & ", Column " & j & ", Row " & i & ": " & SWArray(i, j, k)
Next k
Next j
Next i
End Sub
You can see that for the last dimension all the previous two dimension elements must exist.
So SWArray(1, 1) = "Bay1" does not make any sense..
I am waiting for your clarification regarding what you want accomplishing and I will try helping with a different solution.
If something not clear enough in the above code/sheets preparations, do not hesitate to ask for clarifications.
Edited:
Looking to the previous question and your comments, I tried deducing what you really want accomplishing and I would like to propose the next solution. It involves extending the second array dimension (columns) with an element (which can be 1 or 2) (I mean 11 columns instead of 10 and the last one to be the ID for selecting between the two situations), iterate by columns excepting the last one, and fill two separate arrays according to this last element value. The processed result for each array will be returned starting from "M1") (first processed array) and starting from "X1" the second one:
Sub analizeBaysTwoOptions()
Dim sh As Worksheet, SWArray(), SWArray1(), SWArray2(), i As Long
Dim k1 As Long, k2 As Long
Set sh = ActiveSheet: k1 = 1: k2 = 1
'last column element (in K:K) column, should be the idendifier for the two situations:
SWArray = sh.Range("A1:K4").value 'only to easily test the concept
ReDim SWArray1(1 To UBound(SWArray), 1 To UBound(SWArray, 2) - 1) '- 1 to except the last element
ReDim SWArray2(1 To UBound(SWArray), 1 To UBound(SWArray, 2) - 1) '- 1 to except the last element
For i = LBound(SWArray, 2) To UBound(SWArray, 2) - 1 '- 1 to exclude last column from iteration
If SWArray(1, UBound(SWArray, 2)) = 1 Then
If SWArray(1, i) <= 10 Then
SWArray1(1, k1) = SWArray(1, i)
SWArray1(2, k1) = 2035
SWArray1(3, k1) = 2005
SWArray1(4, k1) = 1005: k1 = k1 + 1
ElseIf SWArray(1, i) > 10 And SWArray(1, i) <= 12 Then
SWArray1(1, k1) = SWArray(1, i)
SWArray1(2, k1) = 2022
SWArray1(3, k1) = 1032
SWArray1(4, k1) = 4344: k1 = k1 + 1
End If
Else
'Stop
'use a different lagorithm (or not) and load SWArray2()
If SWArray(1, i) <= 10 Then
SWArray2(1, k2) = SWArray(1, i)
SWArray2(2, k2) = 2035
SWArray2(3, k2) = 2005
SWArray2(4, k2) = 1005: k1 = k1 + 1
ElseIf SWArray(1, i) > 10 And SWArray(1, i) <= 12 Then
SWArray2(1, k2) = SWArray(1, i)
SWArray2(2, k2) = 2022
SWArray2(3, k2) = 1032
SWArray2(4, k2) = 4344: k2 = k2 + 1
End If
End If
Next i
'drop the processed arrays content:
sh.Range("M1").Resize(k1 - 1, UBound(SWArray1, 2)).value = SWArray1
sh.Range("X1").Resize(k2 - 1, UBound(SWArray1, 2)).value = SWArray2
End Sub
The code can easily be adapted to return in different sheets.
It is not tested (no time to build the sheet environment...), but this should be the concept. If something goes wrong, please specify what error on what code line.
Now I need to go out. Please, examine the about supposed solution and send some feedback. If my assumption is not a correct one, please better define your need and I will try helping when I will be back.
Related
I'm just starting out with VBA and am trying to output an array into a range, but when I set the range to the array, I get blank cells. If I do set the range to a specific index like "titlearray(1, 3)" then it does print the correct output.
This is my full code below..
Sub GenerateList()
baseyr = 2019
mnthct = 1
mnthyr = InputBox("Actuals up to: (xx/xxxx format)")
Sheets("Parameters").Cells(4, 2) = mnthyr
yr = Right(mnthyr, 4)
mnthcols = 12 * (yr - baseyr + 2)
dtarray = Sheet3.Cells(1, 1).CurrentRegion
dtcols = UBound(dtarray, 2) - LBound(dtarray, 2)-1
totalcols = dtcols + mnthcols
ReDim titlearray(1, totalcols)
For i = 1 To totalcols
If i <= dtcols Then
titlearray(1, i) = dtarray(1, i)
Else
titlearray(1, i) = mnthct & "/1/" & baseyr
mnthct = mnthct + 1
If mnthct = 13 Then
baseyr = baseyr + 1
mnthct = 1
End If
End If
Next
'Sheets("Test").Range(Cells(1, 1), Cells(1, totalcols)) = titlearray
End Sub
If i do 'Sheets("Test").Range(Cells(1, 1), Cells(1, totalcols)) = titlearray(1,3), it'll print the correct value.. I feel like this is a really simple mistake but I don't know what it is. Thanks and appreciate your help!
When you Redim an Array, by default it's 0 based (but that can be overridden with Option Base 1)
So, your line
ReDim titlearray(1, totalcols)
is the same as
ReDim titlearray(0 To 1, 0 To totalcols)
Change that to
ReDim titlearray(1 To 1, 1 To totalcols)
i am trying to make a loop to go through an array(47193, 4) and an array 2 named attack(41892,1). The idea here is that the attack array has the values in order from the sheet i want to later on add the values to the next column, this is why i add the values to a third array. So the loop is going to go one by one the value from attack array while looping through arr array to find the common data. i tried copying the values directly to the sheet but excel freezes a lot. Now with this way, excel still freezes at this point. Is there anything wrong with it?
Dim arr3() As Variant
Dim dee As Long
ReDim arr3(UBound(attacks, 1), 1)
For k = 0 To UBound(attacks, 1)
j = 0
For j = 0 To UBound(arr, 1)
If attacks(k, 0) = arr(j, 0) And attacks(k, 1) = arr(j, 2) Then
arr3(dee, 0) = attacks(k, 0)
arr3(dee, 1) = attacks(k, 1)
de = dee + 1
End If
Next j
Next k
Here's some code showing how to use a Dictionary:
Sub Tester()
Const SZ As Long = 10000 'size of test arrays
Dim arr1(1 To SZ, 1 To 2)
Dim arr2(1 To SZ, 1 To 2)
Dim arr3(1 To SZ, 1 To 2) '<<matches go here
Dim n As Long, m As Long, i As Long, t, dict, k
t = Timer
'fill test arrays with random data
For n = 1 To SZ
arr1(n, 1) = CLng(Rnd * 200)
arr1(n, 2) = CLng(Rnd * 200)
arr2(n, 1) = CLng(Rnd * 200)
arr2(n, 2) = CLng(Rnd * 200)
Next n
Debug.Print "Filled test arrays", Timer - t
t = Timer
'test the nested loop approach
For n = 1 To SZ
For m = 1 To SZ
If arr1(n, 1) = arr2(m, 1) And arr1(n, 2) = arr2(m, 2) Then
i = i + 1
arr3(i, 1) = arr1(n, 1)
arr3(i, 2) = arr1(n, 2)
End If
Next m
Next n
Debug.Print "Finished nested loop", Timer - t, i & " matches"
t = Timer
'create a lookup using a dictionary
Set dict = CreateObject("scripting.dictionary")
For n = 1 To SZ
k = arr1(n, 1) & "|" & arr1(n, 2)
dict(k) = dict(k) + 1
Next n
Debug.Print "Filled dictionary", Timer - t
t = Timer
i = 0
Erase arr3
'Perform the match against arr2 using the dictionary
For m = 1 To SZ
k = arr2(m, 1) & "|" & arr2(m, 2)
If dict.exists(k) Then
i = i + 1
arr3(i, 1) = arr2(m, 1)
arr3(i, 2) = arr2(m, 2)
End If
Next m
Debug.Print "Finished dictionary loop", Timer - t, i & " matches"
End Sub
Output:
Filled test arrays 0
Finished nested loop 9.101563 2452 matches
Filled dictionary 0.03125
Finished dictionary loop 0.0078125 2177 matches
Note the # of matches is slightly different - the nested loop catches duplicate matches but the Dictionary only counts unique matches. You might need to make adjustments depending on your use case.
this is part of my code that i am working with and I have one problem. I have array with values (masyvas) and i started new loop to find other values by using masyvas(i,1) values and after that i need that new values would be printed in masyvas(i,2) and i need to group them. It need to look like this:
991988 Gaz.duon.sk"Giros"gaiv.g.1,5L 5_PETØFLAT1,5
PALINK
117388 Silp.gaz.nat.min.v"Tiche'1,5L 5_PETØFLAT1,5
PALINK
RIMI LIETUVA
ŠIAULIŲ TARA
111388 Gaz.nat.min.v"Tiche" 1,5L pet 5_PETØFLAT1,5
PALINK
AIBĖS LOGISTIKA
AIBĖS LOGISTIKA
RIMI LIETUVA
ŠIAULIŲ TARA
How it looks now from marked 1 it goes wrong
Data sheet where i get array values
Here is part of my code where i have this problem now it prints new values next to masyvas(i,2) but not below as I need.
lastrow2 = Sheets("lapas").Cells(Rows.Count, 1).End(xlUp).Row
rub = lastrow2
cub = 3
ReDim masyvas(1 To rub, 1 To cub)
For i = 1 To rub
For j = 1 To cub
masyvas(i, j) = Sheets("lapas").Cells(i, j).Value 'array gets values from filtered data in AKCIJOS sheet
Next
Next
Sheets("lapas").Range("A1:C100").Clear
For i = 1 To rub Step 1
Set rng2 = grafikas.Cells(6 + h, 2)
prekeskodas = masyvas(i, 1)
For m = 2 To lastrow
If akcijos.Cells(m, 8) >= laikas And akcijos.Cells(m, 8) <= laikas2 Then
If prekeskodas = akcijos.Cells(m, 4) Then
grafikas.Cells(7 + r, 2).EntireRow.Select
Selection.Insert Shift:=xlDown
grafikas.Cells(7 + r, 3) = akcijos.Cells(m, 3)
r = r + 1
h = r
End If
End If
Next m
For j = 1 To cub Step 1
rng2.Offset(i - 1, j - 1).Value = masyvas(i, j)
Next
Next
You didn't provide any screenshot of your data, so it's hard to say what exactly is your problem and desired output, but try the code below. I marked changed lines.
For i = 1 To rub
prekeskodas = masyvas(i, 1)
For m = 2 To lastrow
If akcijos.Cells(m, 8) >= laikas And akcijos.Cells(m, 8) <= laikas2 Then
If prekeskodas = akcijos.Cells(m, 4) Then
'masyvas(i, 2) = masyvas(i, 2) & akcijos.Cells(m, 3)
masyvas(i, m) = masyvas(i, m) & akcijos.Cells(m, 3) '<------
End If
End If
Next
For j = 1 To cub
rng2.Offset(j - 1, i - 1).Value = masyvas(i, j) '<-----
Next
Next
I have a 2-dimensional range (i, j) like this:
1 2 3 4 5
6 7 8 9 0
I want to copy&paste it to another sheet like this:
1 6 2 7 3 8 4 9 5 0
I need to recalculate the 2-dim range many times and store the results on another sheet, where each row stores one iteration.
Right now I store all calculations in a array (N, i*j) using two for-loops and then paste all itearations on another sheet.
Is there a faster way to do that?
Current code:
Dim a(1 To 100, 1 To 10) As Double
For iter = 1 To 100
Calculate
For i = 1 To 2
For j = 1 To 5
a(iter, i + j * (i - 1)) = Cells(i, j)
Next j
Next i
Next iter
With Sheets("results")
Range(.Cells(1, 1), .Cells(100, 2 * 5)) = a
End With
UPD:
After each "calculate" the values of the initial range change. The example just illustrates how the values from 2-d range should be stored in one row.
UPD2:
Corrected my current code
Something like this should work for you:
Sub tgr()
Dim rData As Range
Dim iter As Long
Dim lNumIterations As Long
Dim i As Long, j As Long, k As Long
Dim a() As Double
Dim aAfterCalc As Variant
Set rData = Sheets("Data").Range("A1:E2")
lNumIterations = 100
ReDim a(1 To lNumIterations, 1 To rData.Rows.Count * rData.Columns.Count)
For iter = 1 To lNumIterations
k = 0
Calculate
aAfterCalc = rData.Value
For j = 1 To rData.Columns.Count
For i = 1 To rData.Rows.Count
k = k + 1
a(iter, k) = aAfterCalc(i, j)
Next i
Next j
Next iter
Sheets("results").Range("A1").Resize(lNumIterations, UBound(a, 2)).Value = a
End Sub
Try this. It gives your desired output and only uses two loops (instead of three)
' For loop
Dim i As Long, j As Long
' Initalise array
Dim tmp(1 To 100, 1 To 10) As Variant
'Loop through all rows in already initalised array
For i = LBound(tmp, 1) To UBound(tmp, 1)
'Calculate to get updated row contents
Calculate
'Loop through each column in row
'The Round and divided by two is to calculate the number of columns concerned instead of the number in the array
For j = LBound(tmp, 2) To Round((UBound(tmp, 2) + 0.1) / 2)
'First row
tmp(i, (j + j - 1)) = Cells(1, j).Value2
'Second row
' If incase the array is initalised to an odd number otherwise this would be out of range
If j * 2 <= UBound(tmp, 2) Then
tmp(i, j * 2) = Cells(2, j).Value2
End If
Next j
Next i
' Write back to sheet
With Sheets("results").Cells(1, 1)
Range(.Offset(0, 0), .Offset(UBound(tmp, 1) - 1, UBound(tmp, 2) - 1)) = tmp
End With
Not sure I get you, but something like this
Sub test()
Dim a() As Variant
Dim b() As Variant
a = Range("a1:e1").Value
b = Range("a2:e2").Value
For x = 1 To 5
Range("H1").Offset(0, x).Value = a(1, x)
Range("H1").Offset(0, 5 + x).Value = b(1, x)
Next x
End Sub
Private Sub this()
Dim this As Variant, counter As Long, that As Integer, arr() As Variant
counter = 0
this = ThisWorkbook.Sheets("Sheet3").UsedRange
For i = LBound(this, 2) To UBound(this, 2)
counter = counter + 2
ReDim Preserve arr(1 To 1, 1 To counter)
arr(1, counter - 1) = this(1, i)
arr(1, counter) = this(2, i)
Next i
ThisWorkbook.Sheets("Sheet4").Range(ThisWorkbook.Sheets("Sheet4").Cells(1, 1), ThisWorkbook.Sheets("Sheet4").Cells(1, counter)).Value2 = arr
End Sub
*******Background*******
For reasons not up for debate I am using Excel 2010 and VBA to program a scientific model that involves a user inputting data to a worksheet from an online data bank for each species involved. The number of species may change from simulation to simulation. As the program iterates, it calls many functions multiple times (some in the tens of thousands of times). The execution speed of the program is becoming too long so I would like to try and speed it up. In my mind two easy things to increase the execution speed are to decrease the number of worksheet calls and to minimize the number of variants I have to use.
Also of note that several of the functions share the same constants.
I've looked elsewhere on stackexchange and other sites but I still haven't found what I'm looking for ♪. Maybe I just don't have the right search terms.
The question(s)
Because the number of species is not constant and because I would like to make a few arrays of constants available to multiple functions is there away that I can define say a global variable that is an double (or single) precision array that is dimensionalized when a sub is run, reads the constants from the excel sheet once, and then is destroyed when my "main" sub is finished executing?
I could create the array(s) in the main sub and pass it as an argument but several of my functions call other functions as arguments already and several of my lines are extremely long and hard to read. By making these constants arguments to pass to the functions only increases the length of these lines.
Second question if I can't create a global array variable is there away to call the worksheet once (like I have done) but to make the variable a double instead of a variant? I couldn't get the following to work because of the type mismatch error.
Dim C() As Double
redim c(1 to 7, 1 to n)
C = Application.Transpose(Worksheets("Viscosity2").Range("J10:p19"))
Function example: I have a function that I am running in VBA. This function is called tens of thousands of times in the course of my program. I would like to make the C(1 to 7, 1 to n) array and the mw(1 to n) array double precision arrays that call the worksheet once and then are available to multiple functions.
Below is that example function:
Function mumx(y, T)
'this function calculates the mixture viscosity using the Chapman Enskog Wilke method
'using the mol fraction vector, y, and the temperature T
n = UBound(y, 1) - LBound(y, 1) + 1 'number of species
'***********Get Equation Parameters from Worksheet**************
Dim C() As Variant
C = Application.Transpose(Worksheets("Viscosity2").Range("J10:p19"))
Dim mw As Variant
mw = Application.Transpose(Worksheets("Viscosity2").Range("g10:g19"))
'***************************************************************
Dim mu() As Double
ReDim mu(1 To n)
For i = 1 To n Step 1
mu(i) = (C(1, i) * (T ^ C(2, i))) / (1 + C(3, i) / T + (C(4, i) / (T ^ 2)))
Next i
Dim phi() As Double
ReDim phi(1 To n, 1 To n)
For i = 1 To n
For j = 1 To n
phi(i, j) = 1 / 8 ^ 0.5 * (1 + mw(i) / mw(j)) ^ -0.5 * (1 + (mu(i) / mu(j)) ^ 0.5 * (mw(j) / mw(i)) ^ 0.25) ^ 2
test = 1
Next j
Next i
Dim denom As Double
Dim mumix As Double
denom = 0
mumix = 0
For i = 1 To n
For j = 1 To n
denom = denom + y(j) * phi(i, j)
Next j
mumix = mumix + y(i) * mu(i) / denom
denom = 0
Next i
mumx = mumix
'where the units on mumx are in units of cP (which are 1 gm/(m*s))
End Function
'************Example constants are as follows********
'PS should someone stumble on this looking for say viscosity data
'the following constants just example constants
'
'
'C(1, 1) = 0.00018
'C(1, 2) = 0.000017
'C(1, 3) = 0.001113
'C(1, 4) = 0.00215
'C(1, 5) = 0.0005255
'C(1, 6) = 0.0011
'C(1, 7) = 0.0006559
'C(1, 8) = 0.00005
'C(1, 9) = 0.00026
'C(1, 10) = 0.002079
'
'C(2, 1) = 0.69
'C(2, 2) = 1.115
'C(2, 3) = 0.534
'C(2, 4) = 0.46
'C(2, 5) = 0.59
'C(2, 6) = 0.563
'C(2, 7) = 0.608
'C(2, 8) = 0.90
'C(2, 9) = 0.68
'C(2, 10) = 0.4163
'
'C(3, 1) = -0.59
'C(3, 2) = 0
'C(3, 3) = 94.7
'C(3, 4) = 290.
'C(3, 5) = 106.
'C(3, 6) = 96.3
'C(3, 7) = 54.7
'C(3, 8) = 0
'C(3, 9) = 98.9
'C(3, 10) = 353.
'
'C(4, 1) = 140.
'C(4, 2) = 0
'C(4, 3) = 0
'C(4, 4) = 0
'C(4, 5) = 0
'C(4, 6) = 0
'C(4, 7) = 0
'C(4, 8) = 0
'C(4, 9) = 0
'C(4, 10) = 0
'
'
'C(5, 1) = 0
'C(5, 2) = 0
'C(5, 3) = 0
'C(5, 4) = 0
'C(5, 5) = 0
'C(5, 6) = 0
'C(5, 7) = 0
'C(5, 8) = 0
'C(5, 9) = 0
'C(5, 10) = 0
'
'C(6, 1) = 300
'C(6, 2) = 300
'C(6, 3) = 300
'C(6, 4) = 300
'C(6, 5) = 300
'C(6, 6) = 300
'C(6, 7) = 300
'C(6, 8) = 300
'C(6, 9) = 300
'C(6, 10) = 300
'
'C(7, 1) = 1000
'C(7, 2) = 1000
'C(7, 3) = 1000
'C(7, 4) = 1000
'C(7, 5) = 1000
'C(7, 6) = 1000
'C(7, 7) = 1000
'C(7, 8) = 1000
'C(7, 9) = 1000
'C(7, 10) = 1000
'
'
'
'mw(1) = 2.0158
'mw(2) = 18.0148
'mw(3) = 28.01
'mw(4) = 44.009
'mw(5) = 16.0426
'mw(6) = 31.998
'mw(7) = 28.014
'mw(8) = 44.0962
'mw(9) = 30.0694
'mw(10) = 28.0536
'
''******************************
Yes, you can and should* use an array to store the constants entered by the user and yes, you can make it global so that it doesn't have to be passed to the other functions.
Here's an example; note that the data is read into a Variant first, then transferred to the array--this was your missing step that lead to the Type Mismatch error. While this may look like too much code for the effort, transferring the data into the Double array will be many times faster than reading the cells one-by-one.
Public C() As Double
Public Sub PopulateArrayC(n As Integer)
ReDim C(1 To 7, 1 To n)
Dim v As Variant
v = Application.Transpose(Worksheets("Viscosity2").Range("J10:P" & n + 10 - 1))
Dim i As Integer, j As Integer
For i = 1 To 7
For j = 1 To n
C(i, j) = v(i, j)
Next j
Next i
End Sub
*Reading from and writing to cells is very time consuming. Whenever possible, limit the number of reads and writes by*
storing frequently used values in variables, and
reading/writing whole ranges at a time.