principleMinor
A 'flexible' VBA approach to lookups using arrays, scripting dictionary and user input
Viewed 0 times
arraysscriptinguserflexibleinputlookupsusingvbaanddictionary
Problem
In my previous post Optimise compare and match method using scripting.dictionary in VBA I wanted to address optimising the scripting.dictionary approach I was using and I feel I have achieved that (feel free to correct me though!). I am now implementing user input boxes which have also increased the complexity and now have duplicated code. I'm wondering if there might be a better way of doing this?
This is all one sub procedure and it is working well as far as I have been able to test it, I have just broken it up with comments.
With the below ReDim section, I am unsure of any method to write a single column from the worksheet to a single column within a 2d array, so have resorted to looping to populate the second column. Perhaps it isn't really needed to add them both into a single array?
The below three code sections are duplicates of the three above but using different variables, not sure how to address this?
```
Dim comparisonArray As Variant
Dim comparisonCell As Range
Dim comparisonLastRow As Long
Set comparisonC
This is all one sub procedure and it is working well as far as I have been able to test it, I have just broken it up with comments.
Public Sub arrayMatchv3()
Dim workingArray As Variant
Dim workingCell As Range
Dim workingLastRow As Long
Set workingCell = Application.InputBox("Select the first cell of your comparison column.", Type:=8)
workingLastRow = workingCell.End(xlDown).Row
With workingCell
workingArray = Range(.Address, Left(.Address, Len(.Address) - 1) & workingLastRow)
End With
Dim targetArray As Variant
Dim targetCell As Range
Set targetCell = Application.InputBox("Select the first cell of your target column.", Type:=8)
With targetCell
targetArray = Range(.Address, Left(.Address, Len(.Address) - 1) & workingLastRow)
End With
Dim i As LongWith the below ReDim section, I am unsure of any method to write a single column from the worksheet to a single column within a 2d array, so have resorted to looping to populate the second column. Perhaps it isn't really needed to add them both into a single array?
ReDim Preserve workingArray(1 To UBound(workingArray, 1), 1 To UBound(workingArray, 2) + 1)
For i = LBound(workingArray, 1) To UBound(workingArray, 1)
workingArray(i, UBound(workingArray, 2)) = targetArray(i, 1)
Next iThe below three code sections are duplicates of the three above but using different variables, not sure how to address this?
```
Dim comparisonArray As Variant
Dim comparisonCell As Range
Dim comparisonLastRow As Long
Set comparisonC
Solution
Signature
I like that the procedure is explicitly
If you're not specifying
Member names in VBA should be
I like that you're using
The name isn't ideal: "array match v3" is a noun - it's an "array match"... nouns make excellent module/class names:
I see you're having a version number in the name: this supposes there's an
Otherwise, you're storing versioning metadata in the code base itself - which is clutter: if
If you need versioning, use source control. You can create a repository on GitHub, and then use a tool such as Rubberduck to commit, revert, push, pull, create branches and merge them, straight from a docked toolwindow in the IDE. And then you can use GitHub to view any version, or git to fetch any specific version.
I guess that covers the method's signature =)
User input and responsibilities
it is working well as far as I have been able to test it
To the outside world, the procedure is a complete black box: it takes no input and returns no value. Beyond the name and without peeking at the source code, we have no idea what's happening in there when we read the calling code.
Collecting user input is a concern in its own right. All kinds of wild things can happen when a user gets involved. They can ESC out, or X-out of your dialogs, make typos, enter illegal values - many things can go wrong when a user needs to get involved.
Accessing
Take the collect user input concern out of the procedure: use parameters, and make it the caller's responsibility to figure out how to get that input - with an
Except... but we aren't actually working with
At this point things are starting to shape up:
-
Public Sub arrayMatchv3()I like that the procedure is explicitly
Public. Unlike a lot of other languages - including VB.NET, in VBA a member is public unless specified otherwise; being explicit is a good thing. I don't see Option Explicit atop your procedure, but I have it in my IDE and it compiles with it, so kudos for declaring all your variables!If you're not specifying
Option Explicit, you have to know that the slightest typo in a variable's name will make VBA "declare" an implicit Variant variable on-the-spot to receive the value you're assigning to what you intended to be that local variable you just declared: VBA happily compiles with undeclared identifiers, which inevitably will cause bugs that will be hard to find. With Option Explicit specified, the compiler will choke on a typo, finding them for you in a split second.Member names in VBA should be
PascalCase, so that the API you're writing feels like the API you're using when you write VBA code: the entire Excel object model, the VBA standard library, the scripting runtime library - pretty much every single VBA API out there, they all expose modules, classes, procedures, functions, properties, constants, enums, events, etc. - all in PascalCase.I like that you're using
camelCase for your locals though; I find it makes the code feel a bit easier to read, and I do it myself.The name isn't ideal: "array match v3" is a noun - it's an "array match"... nouns make excellent module/class names:
Worksheet, Workbook, Application. Procedures do something, they're verbs. If the procedure matches arrays then MatchArray would be a better name.I see you're having a version number in the name: this supposes there's an
arrayMatchv2 somewhere, and then an arrayMatchv1 or arrayMatch procedure not too far. If not, then just remove the v3 suffix.Otherwise, you're storing versioning metadata in the code base itself - which is clutter: if
arrayMatch usages have been replaced by references to arrayMatchv2, you now have as many places to revisit in the code to make sure it's now calling arrayMatchv3. If v3 fixed a bug in v2, your code base still has the bug even though v3 fixed it.If you need versioning, use source control. You can create a repository on GitHub, and then use a tool such as Rubberduck to commit, revert, push, pull, create branches and merge them, straight from a docked toolwindow in the IDE. And then you can use GitHub to view any version, or git to fetch any specific version.
I guess that covers the method's signature =)
User input and responsibilities
it is working well as far as I have been able to test it
To the outside world, the procedure is a complete black box: it takes no input and returns no value. Beyond the name and without peeking at the source code, we have no idea what's happening in there when we read the calling code.
Application.InputBox is called 4 times: there are 4 user inputs happening. By telling the outside world "I need 4 Range parameters to do my thing", the procedure's signature would already be telling us a lot more.Collecting user input is a concern in its own right. All kinds of wild things can happen when a user gets involved. They can ESC out, or X-out of your dialogs, make typos, enter illegal values - many things can go wrong when a user needs to get involved.
Set workingCell = Application.InputBox("Select the first cell of your comparison column.", Type:=8)
workingLastRow = workingCell.End(xlDown).RowAccessing
workingCell.End here means the program dies with a runtime error if you (/the user) X-out or otherwise cancel the InputBox.workingCell.End(xlDown) will stop at the first blank cell it finds: if the row can contain blank cells, your workingLastRow will not be the actual last row in the selected column. A more reliable way would be to start at the last row in workingCell's column, and go xlUp from there instead:With workingCell.Parent
workingLastRow = .Cells(.Rows.Count, workingCell.Column).End(xlUp).Row
End WithTake the collect user input concern out of the procedure: use parameters, and make it the caller's responsibility to figure out how to get that input - with an
InputBox... or perhaps just any arbitrary set of Range objects. Don't trust the input: either raise a descriptive error, or exit early if the input isn't valid (e.g. if any parameter is Nothing).Except... but we aren't actually working with
Range objects - what we really want is a workingArray, a targetArray, a comparisonArray, a returnArray... and we return a Dictionary. 4 parameters and a return value: the Sub becomes a Function, and the inputs and outputs become much clearer already. The fact that you felt the need to split it up in the OP speaks a lot about this concern, too.At this point things are starting to shape up:
- A function to collect user input; validates and returns a
Rangeobject
-
Code Snippets
Public Sub arrayMatchv3()Set workingCell = Application.InputBox("Select the first cell of your comparison column.", Type:=8)
workingLastRow = workingCell.End(xlDown).RowWith workingCell.Parent
workingLastRow = .Cells(.Rows.Count, workingCell.Column).End(xlUp).Row
End WithWith workingCell
workingArray = Range(.Address, Left(.Address, Len(.Address) - 1) & workingLastRow)
End WithFor i = LBound(workingArray, 1) To UBound(workingArray, 1)Context
StackExchange Code Review Q#142753, answer score: 4
Revisions (0)
No revisions yet.