VBA::consolidating arrays - arrays

I am creating a search function that allows users to search up to 3 different properties at the same time in a database (prop1,2 and 3) and I have created this sub in VBA by putting the results for a searched prop into an array. However, now that I have up to 3 arrays I need to consolidate the arrays so that only the data that is duplicated in the arrays are displayed in the results. Is there any advice on how to 1) only look at the arrays for the properties that the user is searching for and 2) take only the data that is repeated into a final array so I can display it in a results range? Any help is greatly appreciated! Thanks!

Assuming that your entries are directly from a database and therefore are unique for one property, I can think of following steps for a simple solution:
Merge Arrays together (prop1, prop2, prop3 > temp)
Count occurrences for each element (in this example code tempCount)
Based on the knowledge about the occurrences, create the final array (here called result)
Dim prop1() As Variant
Dim prop2() As Variant
Dim prop3() As Variant
Dim temp() As Variant
Dim tempCount() As Integer
Dim result() As Variant
ReDim temp(UBound(prop1) + UBound(prop2) + UBound(prop3) + 1)
'merge arrays
Dim i As Integer
On Error Resume Next
For i = 0 To UBound(temp)
temp(i * 3) = prop1(i)
temp(i * 3 + 1) = prop2(i)
temp(i * 3 + 2) = prop3(i)
Next i
'count occurences
ReDim tempCount(UBound(temp) + 1)
Dim j As Integer
For i = 0 To UBound(temp)
tempCount(i) = 1
For j = 0 To i - 1
'comparison of elements
If temp(i) = temp(j) Then
tempCount(i) = tempCount(i) + 1
End If
Next j
Next i
ReDim result(UBound(temp) + 1)
'if an element occurs 3 times, add it to result
Dim count As Integer
count = 0
For i = 0 To UBound(tempCount)
If tempCount(i) = 3 Then
result(count) = temp(i)
count = count + 1
End If
Next i
To check for some samples I added this to the code. It simply prints out the arrays temp, result and tempCount to the columns A, B and C.
'some sample arrays
prop1 = Array("a", "b", "c", "d", "e")
prop2 = Array("b", "c", "f")
prop3 = Array("b", "c", "d", "g")
'some sample Output
'temp
Cells(1, 1).Value = "temp:"
For i = 0 To UBound(temp)
Cells(i + 2, 1).Value = temp(i)
Next i
'result
Cells(1, 2).Value = "result:"
For i = 0 To UBound(result)
Cells(i + 2, 2).Value = result(i)
Next i
'count:
Cells(1, 3).Value = "count:"
For i = 0 To UBound(tempCount)
Cells(i + 2, 3).Value = tempCount(i)
Next i
Notes: tempCount just holds the cumulative number of occurrences at the point the element is watched at.

Related

VBA - Conditionally populate array from existing array

I'm creating an array from a text file and want to create a "subarray" from the main one.
The main array has the form
And I want to extract the A and B.
I create the "sub array" by splitting the strings from each row
For n = LBound(MainArray) To UBound(MainArray)
If Split(MainArray(n), " ")(0) = "Data" Then
ReDim SubArray(X)
SubArray(X) = Split(MainArray(n), " ")(1)
X = X + 1
End If
Next n
but doing this just returns the array (written as a vector now) (" ", B).
Why does A get overwritten by an empty space after the for loop finds the B?
Thanks and Happy Easter!
Note the example above is just a minimalist version of the real array.
This answer is predicated on Main array being a single dimension array.
The problem you are having is that you are nott creating new sub arrays each time tou get a new 'Data xxx" and consequently just keep overwriting the previous subarray.
You will be better served in you endeavour by using a dictionary of dictionaries.
To use dictionaries you either have to add a reference to the Microsoft Scripting Runtime or use 'CreateObject("Scripting.Dicitonary"). The first option is preferred when developing code or when you are a newbie because you get intellisense. You don't get intellisense when you use late bound objects (created by CreateObject).
Scripting.Dictionaries should be preferred over collections with keys because Dictionaries allow you to retreive the Keys or Items as arrays in their own right.
Here is your code modified to use scripting Dictionaries
Dim myD As Scripting.Dictionary
Set myD = New Scripting.Dictionary
Dim mySubDName As String
mySubDName = "Unknown"
Dim myItem As Variant
For Each myItem In MainArray
If InStr(myItem, "Data") > 0 Then
mySubDName = Trim(myItem)
If Not myD.exists(SubDName) Then
' Create a new sub dictionary with key 'Data XXXX'
myD.Add mySubDName, New Scripting.Dictionary
End If
Else
Dim myArray As Variant
myArray = Split(Trim(myItem), " ")
myD.Item(mySubDName).Add myArray(0), myArray(1)
End If
Next
Dictionary myD will have Keys of "Data A", Data B" etc.
You retrieve a sub dictionary using
'Where XXXX is A,B,C etc
set mySubD = myD.Item("Data XXXX")
The sub dictionary has the structure (using 00000007 700 as an example) of Key=00000007 and Item = 700
If you enumerate a Dictionary using for each it returns the Key as the control variable.
You can get an array of the Keys using the .Keys method
you can Get an array of the Items using the .Items Method
E.g.
myD.Keys gives the array ("Data A", "Data B", "Data C", ....."Data XXX"
myD.Item("Data B").Items will give the array ("0000005", "0000006",.....,"00000010, etc"
Please do take the ttime to read up on Scripting.Dictionaries as part of understanding the above.
Good luck with your coding.
Since you do not answer the clarification questions, please try the next code, which processes a 2D array, resulting two 2D arrays, corresponding to 'Data A' and 'Data B':
Sub Split2DArray()
Dim MainArray, arrA, arrB, n As Long, iA As Long, iB As Long, boolFirst As Boolean
'for exemplification place the picture content in A:A column, then place it in a (2D) array:
MainArray = Range("A1:A13").value
ReDim arrA(1 To 1, 1 To UBound(MainArray)): iA = 1
ReDim arrB(1 To 1, 1 To UBound(MainArray)): iB = 1
For n = LBound(MainArray) To UBound(MainArray)
If MainArray(n, 1) <> "" Then
If Split(MainArray(n, 1), " ")(0) = "Data" Then
If Not boolFirst Then
boolFirst = True
arrA(1, iA) = MainArray(n, 1): iA = iA + 1
Else
boolFirst = False
arrB(1, iB) = MainArray(n, 1): iB = iB + 1
End If
ElseIf boolFirst Then
arrA(1, iA) = MainArray(n, 1): iA = iA + 1
Else
arrB(1, iB) = MainArray(n, 1): iB = iB + 1
End If
End If
Next n
If iA > 1 Then ReDim Preserve arrA(1 To 1, 1 To iA - 1) 'only the second dimension can be preserved
If iB > 1 Then ReDim Preserve arrB(1 To 1, 1 To iB - 1)
Range("C1").Resize(UBound(arrA, 2), 1).value = Application.Transpose(arrA)
Range("D1").Resize(UBound(arrB, 2), 1).value = Application.Transpose(arrB)
End Sub
The code can be easily adapted to process 1D arrays. If this is the case I can show you how to proceed. If many such 'Data x' slices exist, you should use a Dictionary keeping each array.
The same processing way for 1D arrays. Using the same visual elocvent way of testing:
Sub Split1DArray()
Dim MainArray, arrA, arrB, n As Long, iA As Long, iB As Long, boolFirst As Boolean
'for exemplification place the picture content in A:A column, then place it in a (2D) array:
MainArray = Application.Transpose(Range("A1:A13").value) 'obtaining a 1D array from the same reange...
ReDim arrA(1 To UBound(MainArray)): iA = 1
ReDim arrB(1 To UBound(MainArray)): iB = 1
For n = LBound(MainArray) To UBound(MainArray)
If MainArray(n) <> "" Then
If Split(MainArray(n), " ")(0) = "Data" Then
If Not boolFirst Then
boolFirst = True
arrA(iA) = MainArray(n): iA = iA + 1
Else
boolFirst = False
arrB(iB) = MainArray(n): iB = iB + 1
End If
ElseIf boolFirst Then
arrA(iA) = MainArray(n): iA = iA + 1
Else
arrB(iB) = MainArray(n): iB = iB + 1
End If
End If
Next n
If iA > 1 Then ReDim Preserve arrA(1 To iA - 1) 'only the second dimension can be preserved
If iB > 1 Then ReDim Preserve arrB(1 To iB - 1)
Range("C1").Resize(UBound(arrA), 1).value = Application.Transpose(arrA)
Range("D1").Resize(UBound(arrB), 1).value = Application.Transpose(arrB)
End Sub
And a version using a dictionary, processing as many as `Data x' slices exist:
Sub Split1DArrayDict()
Dim MainArray, n As Long, x As Long, arrIt, dict As Object
'for exemplification place the picture content in A:A column, then place it in a (2D) array:
MainArray = Application.Transpose(Range("A1:A18").value) 'obtaining a 1D array from the same range...
Set dict = CreateObject("Scripting.Dictionary")
For n = LBound(MainArray) To UBound(MainArray)
If MainArray(n) <> "" Then
If Split(MainArray(n), " ")(0) = "Data" Then
x = x + 1
dict.Add x, Array(MainArray(n))
arrIt = dict(x)
Else
ReDim Preserve arrIt(UBound(arrIt) + 1)
arrIt(UBound(arrIt)) = MainArray(n)
dict(x) = arrIt
End If
End If
Next n
For n = 0 To dict.count - 1
cells(1, 3 + n).Resize(UBound(dict.items()(n)) + 1, 1).value = Application.Transpose(dict.items()(n))
Next n
End Sub

How to exctract first and last numbers in a sequence

i foung similiar topic, but i can t apply completily that solution to my needs... I want to upgrade excel workbook at my job by making it more auto-entry capable.
Mostly i use excel functions, but sometimes i need some VBA coding, which im not very familiar with. So my problem is, that i need something like this mentioned on this thread. How to get the first and last numbers in a sequence
I have box numbers in different sequince in ascening order starting from "A4" to X on
Sheet1. Example Box numbers: M004935149,M004935150,M004935151,M004935202,M004935203,M004935204,M004935205, is it possible when i copy&paste(values) to sheet2 from "A4" to X (depenting on number of boxes copied) to make a string, sentence or whatever is called in specific form in some other cells. M004935149-151 // M004935202-205. I used code from topic in link above, it can make half job done but i can t figure it out how to make entry from desired cell range and display them on worksheet, and to display values in desired format. Link of screen shoots from my example is following:
I hope that someone can help. Thanks in advance.
Check this
Option Explicit
Sub test2()
Dim ws As Worksheet
Dim arr() As String, result As String, letter As String, cellValue As String, tempLastElement As String
Dim lastColumn As Long, counter As Long
Dim firstColumn As Integer, targetRow As Integer, i As Integer
Set ws = Worksheets("Sheet1")
firstColumn = 1 'number of first column with target data
targetRow = 1 'number of row with target data
lastColumn = ws.Range(ws.Cells(targetRow, firstColumn), ws.Cells(targetRow, Columns.Count).End(xlToLeft).Columns).Count
ReDim arr(1 To lastColumn - firstColumn + 1)
letter = Left(ws.Cells(targetRow, firstColumn).Value, 1) 'if count of character in start of string will be more 1, replace 1 on to count of characters
For i = 1 To UBound(arr)
cellValue = ws.Cells(targetRow, i).Value
arr(i) = Right(cellValue, Len(cellValue) - 1) 'if count of character in start of string will be more 1, replace 1 on to count of characters
Next i
ReDim sequenceArr(1 To UBound(arr))
sequenceArr(1) = arr(1)
counter = 2
For i = 1 To UBound(arr) - 1
If CLng(arr(i)) + 1 = CLng(arr(i + 1)) Then
tempLastElement = arr(i + 1)
sequenceArr(counter) = tempLastElement
Else
counter = counter + 1
sequenceArr(counter) = arr(i + 1)
counter = counter + 1
End If
Next
ReDim Preserve sequenceArr(1 To counter)
result = ""
counter = 1
For i = 1 To UBound(sequenceArr) - 1
If counter > UBound(sequenceArr) Then Exit For
If result = "" Then
result = letter & sequenceArr(counter) & "-" & Right(sequenceArr(counter + 1), 3)
counter = counter + 2
Else
result = result & "//" & letter & sequenceArr(counter) & "-" & Right(sequenceArr(counter + 1), 3)
counter = counter + 2
End If
Next
ws.Range("D4").Value = result
End Sub
Result on

Pasting Formula from an Array in VBA into an excel table

So I am trying to make a VBA scripts that changes all indirect formula in a selection into direct reference, aim is to improve performance of my excel workbook. Below is the code:
Call manual
Dim continue As Integer
continue = MsgBox("This cannot be undone. Continue anyway?", vbOKCancel)
If continue <> vbOK Then Exit Sub
Dim formula_array() As Variant
row_cnt = Selection.Rows.count
col_cnt = Selection.Columns.count
ReDim formula_array(1 To row_cnt, 1 To col_cnt)
If row_cnt = 1 And col_cnt = 1 Then
formula_array(1, 1) = Selection.formula
Else
formula_array = Selection.formula
End If
'for some reason formula_array = Selection.formula gives an error when I select only one cell
count = 0
Dim i As Integer, y As Integer
For i = 1 To row_cnt
For y = 1 To col_cnt
frmula = formula_array(i, y)
oldfunc = find_full_formula(frmula, "indirect(")
Do While (oldfunc <> "")
newfunc = Application.Evaluate(oldfunc)
If IsError(newfunc) Then
newfunc = ""
End If
oldfunc = "indirect(" & oldfunc & ")"
formula_array(i, y) = Replace(formula_array(i, y), oldfunc, newfunc, 1, -1, vbTextCompare)
frmula = formula_array(i, y)
oldfunc = find_full_formula(frmula, "indirect(")
count = count + 1
Loop
Next y
Next i
Dim temp As String
Selection.formula = formula_array
MsgBox count
Call auto
Here the find_full_formula function gives arguments of any function, input is the start of that function and the whole formula. So if you have a formula"Indirect("A1:B2")" then the result of this function would be "A1:B2".
The whole script works very well for normal ranges except when I try to run in on a column of an excel table where the selection also includes the first cell of the column (first cell of data, so not the header) then the result is that all cells in that column have the same formula as the first cell. What is also interesting is that if I select all cells of a column of the table except the first one then the result is fine but only when the first cell is also involved then the problem arises. It obviously looks like some auto-fill feature but I have turned off all such settings that I could find and still this issue isn't solved.
okay, I am adding below a much simpler version of VBA code to highlight my problem:
Dim arr(1 To 4, 1 To 1) As Variant
arr(1, 1) = "2+2"
arr(2, 1) = "=3+2"
arr(3, 1) = "=4+2"
arr(4, 1) = "=5+2"
Range("A2:A5").Formula = arr
this code above works just fine, however the one below results in "=2+2" as formula for each cell of my table.
Dim arr(1 To 4, 1 To 1) As Variant
arr(1, 1) = "=2+2"
arr(2, 1) = "=3+2"
arr(3, 1) = "=4+2"
arr(4, 1) = "=5+2"
Range("A2:A5").Formula = arr
Table in excel looks something like this:
Excel Table
I found a solution that works in all cases I checked out, but it's not beautiful - consider it as a workaround:
set Application.AutoCorrect.AutoFillFormulasInLists = False
set formula to cells by looping them (one by one)
None of these alone sets the formulas as expected if selection matches a ListObject.DataBodyRange.
Sub Test()
' select a range that fits
' the following arrays dimensions
Dim arr(1 To 4, 1 To 2) As Variant
arr(1, 1) = "=2+2": arr(1, 2) = "=12+2"
arr(2, 1) = "=3+2": arr(2, 2) = "=13+2"
arr(3, 1) = "=4+2": arr(3, 2) = "=14+2"
arr(4, 1) = "=5+2": arr(4, 2) = "=15+2"
' deactivate AutoFillFormulasInLists; store setting to restore
Dim bAutoFill As Boolean
bAutoFill = Application.AutoCorrect.AutoFillFormulasInLists
Application.AutoCorrect.AutoFillFormulasInLists = False
Selection.ClearContents
' `Selection.FormulaR1C1 = arr` does NOT work in case of
' Selection = ListObject.DataBodyRange
' => loop cells (slower and more lines of code)
Dim i As Long, j As Long
For i = 1 To UBound(arr, 1)
For j = 1 To UBound(arr, 2)
Selection(i, j).FormulaR1C1 = arr(i, j)
Next j
Next i
Application.AutoCorrect.AutoFillFormulasInLists = bAutoFill
End Sub
Hopefully somebody else will paste a more straightforward solution!

CountIF within an Array VBA

This should be easy and I think I am almost there. I would like to count how many times an entry repeats itself within a certain array. The array will be populated from a range. Eventually if the number of the count is more than 4, I would like to insert "Excess", otherwise if less than 4, I would like to insert "Insufficient", else is "complete". Unfortunately, even though I have learnt to do these calculations without using Arrays, I find some difficulties when switching to Arrays.
How the code should look like
Sub test()
Dim MyArray() As Variant, Countarrays() As Variant, Result() As Variant
Dim r As Range
Dim rows As Integer
Worksheets("Sheet1").Activate
Set r = Range("B2", Range("B1").End(xlDown))
MyArray = Range("B2", Range("B1").End(xlDown))
rows = Range("B2", Range("B1").End(xlDown)).Count
For i = 0 To rows
For j = 0 To rows
Countarrays(i, 1) = WorksheetFunction.CountIf(r, MyArray(i))
If (Countarrays(i, 1).value) > 4 Then Result(j, 1) = "Excess"
ElseIf (Countarrays(i, 1).value) < 4 Then Result(j, 1) = "Insufficient"
ElseIf (Countarrays(i, 1).value) = 4 Then Result(j, 1) = "Complete"
Next j
Next i
End Sub
This should do the trick:
Option Explicit
Sub Test()
Dim MyArray, DictDuplicates As New Scripting.Dictionary, i As Long
With ThisWorkbook.Sheets("Sheet1") 'change if needed
MyArray = .Range(.Cells(2, 1), .Cells(2, 2).End(xlDown))
For i = LBound(MyArray) To UBound(MyArray) 'loop to store all the items and how many times do they repeat
If Not DictDuplicates.Exists(MyArray(i, 2)) Then 'if doesn't exists will store it
DictDuplicates.Add MyArray(i, 2), 1
Else 'if it does exists will increment its item value
DictDuplicates(MyArray(i, 2)) = DictDuplicates(MyArray(i, 2)) + 1
End If
Next i
For i = LBound(MyArray) To UBound(MyArray) 'loop to give back the result
Select Case DictDuplicates(MyArray(i, 2))
Case Is > 4
MyArray(i, 1) = "Excess"
Case Is = 4
MyArray(i, 1) = "Complete"
Case Is < 4
MyArray(i, 1) = "Insufficient"
End Select
Next i
.Range(.Cells(2, 1), .Cells(2, 2).End(xlDown)) = MyArray
End With
End Sub
Note that for the DictDuplicates to work, you need to check the Microsoft Scripting Runtime library.

Specific referenc on one Array-element in an 2D-Array in VBA

i wanna have a reference on one element in a 2 dimensional Array like this:
dim ary(5,5) as String
ary(1) = "Michael, Thomas, Bill, Mike, Pascal"
ary(2) = "Iphone,HTCOne,SGS4,SGS3"
'... and so on
can i write sth like this:?
For i = 0 To UBound(ary)
activMan = ary(i)
Sheets("Example").Cells(1,1) = activMan(i)
'activMan is now Michael
' so i dont have to use two variables...
End If
Next i
' in the next round activMan is going to be HTCOne
Now activMan should be a reference on ary(i) in the first Dimension and i have access on all the elements in the second dimension.
Is that possilbe or completly wrong?
EDIT:
I'il give out:
1.: Mike -> arr(0,0)
2.: Ipod -> arr(1,1)
3.: .... -> arr(2,2)
But i realized it's possible with only one variable...^^
That is completely wrong :p
Analyse this bud :)
Option Explicit
Sub build2DArray()
' 2D 5 element array
' elements 1st 2nd 3rd 4th 5th
' index 0 [0, 0][1, 0][2, 0][3, 0][4, 0]
' index 1 [0, 1][1, 1][2, 1][3, 1][4, 1]
Dim arr(0 To 5, 0 To 1) as String ' same as Dim arr(5, 1)
arr(0, 0) = "Mike"
arr(1, 0) = "Tom"
arr(2, 0) = "Bill"
arr(3, 0) = "Michael"
arr(4, 0) = "Pascal"
arr(0, 1) = "IPhone"
arr(1, 1) = "Ipod"
arr(2, 1) = "Mac"
arr(3, 1) = "ITunes"
arr(4, 1) = "IArray"
Dim i As Long, j As Long
Dim activeMan As String
For i = LBound(arr) To UBound(arr) - 1
activeMan = arr(i, 0)
Debug.Print "loop no. " & i & " activeMan: " & activeMan
Cells(i + 1, 1).Value = activeMan
Cells(i + 1, 2).Value = arr(i, 1)
Next i
End Sub
Edit: its possible to use types and a custom function to achieve the same result, have a look
Private Type yourType
tName As String
tPhone As String
End Type
Sub example()
Dim yType(3) As yourType
yType(0).tName = "Michael"
yType(0).tPhone = "iPhone"
yType(1).tName = "Tom"
yType(1).tPhone = "Blackberry"
yType(2).tName = "Dave"
yType(2).tPhone = "Samsung"
Dim i&
For i = LBound(yType) To UBound(yType)
Debug.Print get_yType(yType, i)
Next i
End Sub
Private Function get_yType(arr() As yourType, i&) As String
get_yType = arr(i).tName & " " & arr(i).tPhone
End Function

Resources