I am trying to set up a workflow/UX where a CSV file is imported and each column of the imported file is assigned a "data type" using dropdowns at the top of each column. Once these data types are designated/assigned to each column, another macro populates a second sheet with the imported CSV data, where the location in the new sheet is dependent on the data type designated for each column of the imported data.
For example, if the first column of the imported data is of data type "DataA", the dropdown selection would be selected as such for this first column (from a total of 12 "data types" in the dropdown menu). This "DataA" data would then be populated in the second sheet in its fifth column.
Here is the code I have so far:
Dim DataA As Integer, DataB As Integer, DataC As Integer, DataD As Integer, DataE As Integer, DataF As Integer, DataG As Integer, DataH As Integer, DataI As Integer, DataJ As Integer, DataK As Integer, DataL As Integer
Dim ColArray(12) As Variant
For p = 1 To LastColImport 'This is a previously-defined/assigned variable
q = 1
Do While q <= 12
If ActiveSheet.DropDowns(p).Value = q Then
ColArray(q) = p
Exit Do
Else
q = q + 1
End If
Loop
Next p
This populates the ColArray array with an integer entry if the data type is selected, or an empty entry if it has not been selected. The next step I want to do is assign each ColArray entry value to a named variable, so that I can call the ColArray entry values by data type name instead of having to remember or look up what data type each ColArray integer value refers to.
I can't find a built-in "dropdown list range name" recall function anywhere, so what I would like to do is the following:
Dim ColArrayNames(12) As Variant
ColArrayNames(1) = DataA 'These variables were defined in the previous code block
ColArrayNames(2) = DataB
...
ColArrayNames(12) = DataL
ColArrayNames = ColArray
I realize that in this specific case, it would probably be easier to just assign the data type variables directly to the ColArray values, instead of putting them into an array and then equating the array values. I feel like populating an array with unassigned variables could be useful in other cases as well. My attempts at using this method of assigning variables have failed.
After changing the last line of code to:
For i = 1 to 12
ColArrayNames(i) = ColArray(i)
Next i
The ColArray values don't get assigned to the data type variables. That being said, the ColArrayNames entries are assigned the correct values, so the issue seems to be the "last step" in assigning the ColArray values to the data type variables by way of the ColArrayNames array of unassigned variables.
If anyone has suggestions for how to approach this "general" problem of using arrays of unassigned variables to assign values to each array entry (while preserving the ability to call these values using the entries' "original" variable names), or if there's a more efficient way of approaching this spreadsheet function altogether, please let me know!
EDIT 1: As requested by John Coleman, I'll elaborate a bit more on what I'm trying to do here.
Once I have the imported column numbers assigned to a data type, I want to send the data to a second sheet with some code in a manner such as:
For i = 2 to LastRow 'The LastRow variable value will be found using a simple xlDown search process
Worksheets(2).Cells(1,i).Value = Worksheets(1).Cells(DataA,i).Value
Worksheets(2).Cells(4,i).Value = Worksheets(1).Cells(DataB,i).Value
Etc.
Next i
Again, I realize that I could just as easily use
Worksheets(2).Cells(1,i).Value = Worksheets(1).Cells(ColArrayNames(1),i).Value
and so on, but I feel like if what I'm asking about is possible, I might be able to use it in another situation (even if it's not the most ideal method for this example).
For as best as I understand your problem, it seems to come down to effectively (and efficiently) defining a data map between the original source data and the formatted output (in this case, cells on a second worksheet).
When confronted with the data typing part of the problem, I realized the ultimate "holder" of the destination data type is the object that holds it -- in this case its the worksheet cell, not the VBA variable or array. Your focus seems to be on the VBA code that transfers/copies the data from one worksheet to another (or to/from variant arrays). Your own choice in efficiency should be your guide here. If you want to use relatively general purpose code for multiple data types, my suggestion is to use a Variant value or array and after copied to the destination cell, set the format of the destination cell (which effectively "types" it for whatever later use necessary).
A dictionary may not even be necessary if the mapping table can be flexibly scanned to accommodate any number of data types, formats, or columns.
Related
I am working on a program which performs calculations on several physical elements. There are about 5 types of elements, each requiring different inputs(with different datatypes) to calculations. There is no limit on the number of elements or order in which they can be calculated.
For example: the first set of data might have an element of type A, and element of type B and a second element of type A.
The second set of data might have an element of type C, an element of type B, and five elements of type A.
Element type A might have a type(string) input and a pressure(double) input.
Element type B might have a type(string) input, a length(double) input, number of fins(integer) input.
Element type C might have a length(double) input, diameter(double) input and quantity(integer) input
Since I don't know the number or type of elements ahead of time, I have been using a variant array which has a number of rows which can be assigned after elements are selected by the user, and a number of columns equal to the maximum number of data points needed by any element.
The problem is that when I then try to use that data in other functions (by calling Array(1,2)) I get a "byref argument type mismatch" error.
From what I can tell, this means that my program doesn't KNOW that the value contained in the slot of my array can/should be the correct datatype. it just sees "variant" as opposed to "double" or "string" and throws an error.
I have found a couple ways around this.
1)I can put the data in parenthesis when I call the function. Apparent this forces the data to conform to the expected datatype.
function(input1, (Array(1,2)), input3)
2) I can go into all my functions and change the expected data-types to variant.
The problem is I am performing an engineering calculation, and I care about maintaining a certain level of accuracy in my numbers. I also DON'T really want my functions to be able to run if the inputs don't make sense. It seems to me that option 1 carries a high risk of loosing important data during the forced conversion. I'm not sure that option 2 is any better, since vba doesn't seem to assign/track these datatypes like I was imagining it did.
Is there any better way for me to assign and transfer data of a variety of types?
It sounds like you should probably create some custom Types or Classes (where each class has members with specific datatypes) for your different element types and then you can add those to a Collection/array and pass around for processing.
For example using classes:
Couple of very basic classes (just public variables, no methods):
'class module clsA
Public MyType As String
Public Pressure As Double
'class module clsB
Public MyType As String
Public Length As Double
Public FinCount As Integer
In a regular code module:
Sub tester()
Dim colThings As New Collection, e
colThings.Add MakeTypeAThing("TypeA", 67.92)
colThings.Add MakeTypeBThing("TypeB", 19.56, 4)
colThings.Add MakeTypeAThing("TypeA", 0.38)
For Each e In colThings
Debug.Print TypeName(e)
Next e
End Sub
'Couple of "factory" functions to create instances of your classes
' and set the member fields
Function MakeTypeAThing(typ As String, Pressure As Double) As clsA
Dim rv As New clsA
rv.MyType = typ
rv.Pressure = Pressure
Set MakeTypeAThing = rv
End Function
Function MakeTypeBThing(typ As String, l As Double, Fins As Integer) As clsB
Dim rv As New clsB
rv.MyType = typ
rv.Length = l
rv.FinCount = Fins
Set MakeTypeBThing = rv
End Function
Problem:
I'm looking for an efficient data structure in VBA, which allows me to lookup a value in one 'column' and find a corresponding value in another column. All columns have the same fixed length.
Background
Essentially I have 2 Enums, each with n items, and an array of n strings; I'd like to pass the ith value from any of these sets, and return the ith value from another specified set
One option would be a Collection of Arrays; the Collection would have keys corresponding to the type of list (e.g. Enum1, Enum2, StringList), and I would be able to make a function which takes two list keys and a lookup value as argument, and returns the corresponding value in the second column with a loop:
Function findCorresponding(dataTable As Collection, header1 As String, header2 As String, lookupVal As Variant) As Variant
Set array1= dataTable(header1) 'pick out array from collection
For i = Lbound(array1) To Ubound(array1) 'loop through to find lookup val
If array1(i) = lookupVal Then Exit For
Next i
findCorresponding = dataTable(header2)(i) 'return corresponding val
End Function
And sure, I could replace the lookup arrays with un-Keyed Collections to avoid looping. But that doesn't seem like the most efficient way (I believe dictionaries Hash rather than loop, so would be faster on that front, but have a lot of extra baggage compared to an array)
What I really want is something like a Scripting.Dictionary, where you can access both values and keys, and use one to get the other. But with a third parameter that can be found using either of the other two, and can be used to find either of the other two.
If something extends to n columns that would also be useful
I have some strange behavior with a dynamic array. It seems like VBA denies assigning a value to a variable. Here is the code:
Private Sub Report_Load()
Dim db As Database
Dim reportArray() As Variant
Dim structre As Recordset
Dim columns, rows, i As Integer
'Open recordset
Set db = CurrentDb
Set structure = db.OpenRecordset("SELECT * FROM [tblstructure] ORDER BY [Rank]")
'Change array size dynamically
rows = structure.RecordCount
columns = 4
ReDim reportArray(rows, columns)
'Populate array
i = 0
Do Until structure.EOF
reportArray(i, 0) = structure![Name]
i = i + 1
structure.MoveNext
Loop
End Sub
When I open the report I get an error that subscript is out of range. When I debug my code I can see that value of i in the loop is 2, so my array must be smaller that that. When I hover over rows variable I see that its value is 1. So indeed I'm trying to access something that is out of range. But the strange part is that the value of structure.RecordCount is 23.
A screenshot:
Even if I use the code like this:
ReDim reportArray(structure.RecordCount, columns)
I get an array of size (1, 4). Why isn't VBA assigning value 23 to variable "rows" or why ReDim assigns the right value to second dimension, but not the first?
Read this: https://support.microsoft.com/en-us/kb/105976
CAUSE
For recordsets and snapshots, Microsoft Access does not
automatically return the number of records that exist in the
recordset. Rather, it returns the number of records accessed.
RESOLUTION
To determine the exact number of records in a recordset or snapshot,
use the MoveLast method before checking the RecordCount property.
STATUS
This behavior is by design.
As per microsoft , RecordCount returns the number of records accessed and not the number of total records.
So, in your case, this should work
structure.MoveLast
rows = structure.RecordCount
structure.MoveFirst
It's been a while since I've used Access, but I seem to recall that RecordCount doesn't work properly unless you've actually enumerated the entire resultset doing something like structure.MoveLast first (just remember to do structure.MoveFirst before looping through). Of course, that requires a recordset capable of moving in both directions.
Alternatively, you could put the ReDim in your loop, and just increase it by one each time.
I am creating a Word userform using VBA. I store several configuration using array in the program code, such as the following:
Public arrConfiguration[2, 3] as Integer
where index 2 represent type 0 to 2, and index 3 represent properties 0 to 3 for each type.
However, I planned to modify the program for larger amount of data (such as for 100 different types of data and 50 properties for each data).
My question is,
should I keep storing the data using array in the program, so that it will be
Public arrConfiguration[99, 49] as Integer
or store it in an Excel file, and make the program open the Excel file and access the cells repeatedly? Which one is better?
Thank you.
Please prefer excel. Sample example data image is appended here-under.
For creating two dimensional dynamic array in excel, follow the steps below:
◾Declare the two dimensional Array
◾Resize the array
◾Store values in array
◾Retrieve values from array
Sub FnTwoDimentionDynamic()
Dim arrTwoD()
Dim intRows
Dim intCols
intRows = Sheet1.UsedRange.Rows.Count
intCols = Sheet1.UsedRange.Columns.Count
ReDim Preserve arrTwoD(1 To intRows, 1 To intCols)
For i = 1 To UBound(arrTwoD, 1)
For j = 1 To UBound(arrTwoD, 2)
arrTwoD(i, j) = Sheet1.Cells(i, j)
Next
Next
MsgBox "The value is B5 is " & arrTwoD(5, 2)
End Sub
In the Message Box you will get the following output.
Further To visualize a two dimensional array we could picture a row of CD racks. To make things easier, we can imagine that each CD rack could be for a different artist. Like the CDs, the racks would be identifiable by number. Below we'll define a two dimensional array representing a row of CD racks. The strings inside of the array will represent album titles.
For multidimensional arrays it should be noted that only the last dimension can be resized. That means that given our example above, once we created the array with two CD racks, we would not be able to add more racks, we would only be able to change the number of CDs each rack held.
You can simplify #skkakkar code:
dim x as variant
x = range("A1").CurrentRegion
No Redim, no loops.
Depending on how you see things evolving, you might want to consider accessing your Excel data via ADO, rather than OLE Automation. That way, if you decide to change your storage system to Access, SQL Server or something else, you will have less work to do.
How To Use ADO with Excel Data from Visual Basic or VBA (Microsoft)
https://support.microsoft.com/en-gb/kb/257819
Read and Write Excel Documents Using OLEDB (Codeproject)
http://www.codeproject.com/Tips/705470/Read-and-Write-Excel-Documents-Using-OLEDB
I have a range with 170000 rows in it. I'm filtering column A for a single value and returning the corresponding values in column B.
I want these values to dump into an array so I can quickly toss them into a dictionary (with the key being the value I filtered column A with).
The problem is that SpecialCells(xlCellTypeVisible) is acting inconsistent.
If I do the same test on a smaller range, the values dump into the array just fine. But with a range as large as mine, it only returns the first value in the range. Also, I can use the same line to copy to another sheet. I just can't get it to populate the array.
foo = ws1.Range(tbl1Name & "[ID]").SpecialCells(xlCellTypeVisible)
Works with small ranges, but returns only the first result in a range as large as mine (less than 50 results.) foo becomes an array containing all the variables.
ws1.Range(tbl1Name & "[ID]").SpecialCells(xlCellTypeVisible).Copy ws2.Range("A1")
Works with the large range and copies all the relevant data successfully.
So my question: How do I populate the array without the extra step of copying to a blank worksheet when autofiltering a large table range? (Excel 2013)
EDIT: requires a reference to "Microsoft Forms 2.0 Object Library" (should be near the top of the list of available references). Or add a userform to your project and that will auto-add the reference (you can then remove the form...)
This should work for a single column:
Sub Tester()
Dim rng, txt As String, cb As New DataObject, arr
Set rng = ActiveSheet.Range("A2:A28").SpecialCells(xlCellTypeVisible)
rng.Copy
DoEvents
cb.GetFromClipboard
txt = cb.GetText
arr = Split(txt, vbCrLf)
Debug.Print LBound(arr), UBound(arr)
End Sub
If you had multiple columns you'd need to loop over each element of arr (splitting its value on tab) and transfer the values to a 2-d array.