I've done some search and tried new codes since last night but haven't yet found the answer I was looking for.
I'm working with multiple arrays but am only looking for duplicates in one array at a time. Having duplicates across different arrays doesn't matter; only duplicates within a single array matters.
Each array has between 5 and 7 elements.
Each element is an integer between 1 and 10.
Some sample arrays can be
Array1 = (5, 6, 10, 4, 2)
Array2 = (1, 1, 9, 2, 5)
Array3 = (6, 3, 3, 3, 6)
Array4 = (1, 2, 3, 3, 3, 3, 2)
etc.
For each array, I would like to know how many duplicates there are. That is,
For Array1, I would like a resulting array of (1) indicating there is no duplicate and each element is unique. DuplicateCount (Array1) = (1).
For Array2, the resulting array should (2, 1) indicating there are 2 duplicates of 1 and the rest of the elemets are unique. DuplicateCount (Array2) = (2, 1).
For Array3, I would like a resulting array of (3, 2) indicating there are 3 duplicates of 3 and 2 duplicates of 6. DuplicateCount (Array3) = (3, 2).
For array 4, I would like a resulting array of (4, 2, 1) as there are 4 duplicates of 3, 2 duplicates of 2, and 1 unique 1. DuplicateCount (Array4) = (4, 2, 1).
I really appreciate all your help.
Thanks.
I think a dictionary might be a good solution for you, because it can store each unique number of array as key and their count as value. If the number exists in the dictionary, then its count will be incremented. Here's my implementation:
Function DuplicateCount(nums As Variant) As Scripting.Dictionary
Dim dict As New Scripting.Dictionary
For Each num In nums
If dict.Exists(num) Then
dict(num) = dict(num) + 1
Else
dict(num) = 1
End If
Next
Set DuplicateCount = dict
End Function
Before using the above code in your application, please ensure that the reference Microsoft Scripting Runtime is enabled (go to Tools -> References and check its box). Now you're ready to go, you can see the full script here:
Sub Main()
Dim array1() As Variant: array1 = Array(5, 6, 10, 4, 2)
Dim array2() As Variant: array2 = Array(1, 1, 9, 2, 5)
Dim array3() As Variant: array3 = Array(6, 3, 3, 3, 6)
Dim array4() As Variant: array4 = Array(1, 2, 3, 3, 3, 3, 2)
Dim result1 As New Scripting.Dictionary
Dim result2 As New Scripting.Dictionary
Dim result3 As New Scripting.Dictionary
Dim result4 As New Scripting.Dictionary
Set result1 = DuplicateCount(array1)
Set result2 = DuplicateCount(array2)
Set result3 = DuplicateCount(array3)
Set result4 = DuplicateCount(array4)
For Each k In result1.Keys()
If result1(k) > 1 Then
'(Nothing)
Debug.Print k & "," & result1(k)
End If
Next
Debug.Print
For Each k In result2.Keys()
If result2(k) > 1 Then
'1,2
Debug.Print k & "," & result2(k)
End If
Next
Debug.Print
For Each k In result3.Keys()
If result3(k) > 1 Then
'6,2
'3,3
Debug.Print k & "," & result3(k)
End If
Next
Debug.Print
For Each k In result4.Keys()
If result4(k) > 1 Then
'2,2
'3,4
Debug.Print k & "," & result4(k)
End If
Next
End Sub
Function DuplicateCount(nums As Variant) As Scripting.Dictionary
Dim dict As New Scripting.Dictionary
For Each num In nums
If dict.Exists(num) Then
dict(num) = dict(num) + 1
Else
dict(num) = 1
End If
Next
'Debug: Enable the below lines to print the key-value pairs
'For Each k In dict.Keys()
' Debug.Print k & "," & dict(k)
'Next
Set DuplicateCount = dict
End Function
Sub tester()
Debug.Print Join(RepCount(Array(5, 6, 10, 4, 2)), ",")
Debug.Print Join(RepCount(Array(1, 2, 3, 3, 3, 3, 2)), ",")
Debug.Print Join(RepCount(Array(6, 3, 3, 3, 6)), ",")
Debug.Print Join(RepCount(Array(6, 6, 3, 3, 3, 6)), ",")
End Sub
Function RepCount(arrIn)
Dim rv(), rv2(), i, m, mp, n
ReDim rv(1 To Application.Max(arrIn))
ReDim rv2(0 To UBound(rv) - 1)
For i = 0 To UBound(arrIn)
rv(arrIn(i)) = rv(arrIn(i)) + 1
Next i
For i = 1 To UBound(rv)
m = Application.Large(rv, i) 'i'th largest rep count
If IsError(m) Then Exit For 'error=no more reps
If m <> mp Then 'different from the previous
rv2(n) = m
n = n + 1
End If
mp = m
Next i
ReDim Preserve rv2(0 To n - 1) 'size array to fit content
RepCount = rv2
End Function
Related
I am trying to sum a multi-dimensional array in VBA.
What I currently have
My goal is to have the sum of MyArray(2,2) = 121, and of MyArray(3,1) = 129, all of this stored in "MyNewArray".
I tried using Application.Worksheetfunction.Sum but I guess this wouldn't work unless I printed my values to Excel.
Any ideas of how I could go about it?
Appreciate your help.
You need to loop through all the elements of the vector that interests you and sum up the values one by one. It looks cumbersome but it's very fast.
Private Sub Test()
Dim Arr As Variant
Dim i As Long
Dim Sum As Double
ReDim Arr(1 To 5, 1 To 2, 1 To 9)
Arr(2, 2, 2) = 1
Arr(2, 2, 3) = 120
Arr(3, 1, 4) = 1
Arr(3, 1, 6) = 59
Arr(3, 1, 7) = 69
For i = LBound(Arr, 3) To UBound(Arr, 3)
Sum = Sum + Arr(3, 1, i)
Next i
MsgBox "Total = " & Sum
End Sub
I was wondering if there is a possibility to use countif on arrays.
Currently there are two arrays. One is the Array with the Range (RangeArray) and the other the Criteria array (CritArray) which comes from another workbookbut is saved in an array. I'm trying to use the countif method in VBA using arrays if and store the countif values in a cell. So I don't need to loop between workbooks all the time.
Dim RangeArray, CritArray as Variant
RangeArray = Array(1,2,3,4,2,4,2,5,7,1,7,1,2)
CritArray = Array(1,2)
For i = 1 To LastRow
Cells(i, 1).Value = WorksheetFunction.CountIf(RangeArray, CriteriaArray)
Next i
When I try to do something amongst these lines it keeps giving the error "object required".
Any help would be kindly appreciated!
Kind regards,
Sub test()
Dim RangeArray, CritArray As Variant
Dim Counts As New Collection
RangeArray = Array(1, 2, 3, 4, 2, 4, 2, 5, 7, 1, 7, 1, 2, 11)
CritArray = Array(1, 2)
For i = 0 To UBound(CritArray)
Count = 0
For j = 0 To UBound(RangeArray)
If CritArray(i) = RangeArray(j) Then
Count = Count + 1
End If
Next
Counts.Add Count
Next
For k = 1 To Counts.Count
Cells(k, 1) = Counts(k)
Next
End Sub
This has been answered... have to wait 2 days to accept own answer
Background:
I have an array ("arr") generated from a dataset in Excel; I use that array to populate another array ("zrr"), where one aspect of that population is to use a Dictionary ("dcdept").
The dictionary was populated appropriately (tested via debug.print dcdept(ActualKey); was populated such that dcdept(4000)="Value" and tested debug.print dcdept(4000) printed the word "Value" in the immediate window.
I was originally using the source dataset via .cells(i,) references, but with several hundred thousand lines, I tried to keep activities in VBA to speed it up.
There are no errors/alerts generated from my code.
Issue:
When attempting to populate an element in zrr (zrr(i-1,3)) using the dictionary key from arr (dcdept(arr(i-2,16))), I get no value output.
Question:
Does anyone have any suggestions/solutions to resolve the issue with the given data?
Code in question:
Public arr As Variant, brr As Variant, crr As Variant, drr As Variant, lrs As Long
Private Sub changes()
Dim i As Long, x As Long, y As String, z As String, dcdept As Scripting.Dictionary, zrr As Variant, a As Long
'set-up dictionary for department
Set dcdept = New Scripting.Dictionary
dcdept(4000) = "Value"
'generate array to store new values
With Sheets("Conversion")
.Columns(16).NumberFormat = "0"
lrs = .Cells(.Rows.Count, 1).End(xlUp).Row
arr = .Range(.Cells(2, 1), .Cells(lrs, 17)).Value '17 = Q
ReDim zrr(lrs, 4)
For i = 2 To lrs
ReDim Preserve zrr(lrs, 4)
Select Case Left(arr(i - 1, 17), 3)
Case "QTE"
x = 7
Case "ZNA"
x = 5
End Select
zrr(i - 2, 0) = Right(arr(i - 1, 17), x)
If InStr(arr(i - 1, 9), " Milestone ") Then
y = Left(arr(i - 1, 9), 2) & " " & arr(i - 1, 10)
Else
y = arr(i - 1, 9) & " " & arr(i - 1, 10)
End If
zrr(i - 2, 1) = y
If IsEmpty(arr(i - 1, 14)) Then
zrr(i - 2, 2) = "N"
Else
zrr(i - 2, 2) = "Y"
End If
a = Val(arr(i - 1, 16))
z = dcdept(a)
zrr(i - 2, 3) = z
Debug.Print a
Debug.Print z
Next i
'append data to sheet
.Cells(2, "R").Resize(lrs, 3).Value = zrr 'SHOULD BE Resize(lrs,4), per answer
End With
End Sub
OK this is not an answer, but an illustration of my comment. I didn't expect this to happen. I set up a simple scenario which I hope is similar to yours:
Sub x()
Dim oDic As Object, v1(1 To 2), v2(1 To 2), v, i As Long
Set oDic = CreateObject("Scripting.Dictionary")
v1(1) = "Fred"
v1(2) = 1000
oDic(1) = v1(1) 'key 1, item "Fred
oDic(2) = v1(2) 'key 2, item 1000
The locals window after this looks like this
Then add this line
v2(1) = oDic(v1(1))
and the immediate window reads thus:
Add this line
v2(2) = oDic(v1(2))
and the immediate window reads thus:
I'm an idiot...
.Cells(2, "R").Resize(lrs, 3).Value = zrr
should be
.Cells(2, "R").Resize(lrs, 4).Value = zrr
Can't accept my own answer for 2 days; pardon the "unanswered" question in the meantime.
I am currently trying to combine 46 arrays in to a single array. I have scoured the internet, to no prevail and am hoping someone here can help. I did find the below page, but I need to be able to look through each element of the new array in a nested for loop, so using the method below doesn't quite get me to my end goal.
Excel vba - combine multiple arrays into one
Basically, I need to combine my set of 46 arrays in such a way that I can then loop through each element using a nested for loop. ie.
Set of arrays:
myArray1 = (1, 2, 3, 4)
myArray2 = (5, 6, 7)
myArray3 = (8, 9)
myArray4 = (10, 11, 12, 13, 14)
.
.
.
myArray46 = (101, 102, 103)
Combine them to form new array:
myNewArray = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14... 101, 102, 103)
Loop through in nested for loop to check each element against my main array:
For i = LBound(mainArray) to UBound(mainArray)
For j = LBound(myArray) to UBound(myArray)
If mainArray(i) = myArray(j) Then
'do something
End If
Next j
Next i
Any help and/ or guidance with this is greatly appreciated!
Since you write in your comments that your end goal is to create an array of unique elements, you might be best served using a dictionary, where you can test for uniqueness as you add each element to dictionary. Something like:
Option Explicit
Function uniqueArr(ParamArray myArr() As Variant) As Variant()
Dim dict As Object
Dim V As Variant, W As Variant
Dim I As Long
Set dict = CreateObject("Scripting.Dictionary")
For Each V In myArr 'loop through each myArr
For Each W In V 'loop through the contents of each myArr
If Not dict.exists(W) Then dict.Add W, W
Next W
Next V
uniqueArr = dict.keys
End Function
Sub tester()
Dim myArray1, myArray2, myArray3, myArray4, myArray5
myArray1 = Array(1, 2, 3, 4)
myArray2 = Array(5, 6, 7, 8)
myArray3 = Array(9, 10, 11, 12, 13, 14)
myArray4 = Array(15, 16)
myArray5 = Array(1, 3, 25, 100)
Dim mainArray
mainArray = uniqueArr(myArray1, myArray2, myArray3, myArray4, myArray5)
End Sub
If you run Tester, you will see mainArray contains:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
25
100
Using your data this is how to create one array out of many:
Public Sub TestMe()
Dim myA, myB, myC, myD, myE
myA = Array(1, 2, 3, 4)
myB = Array(5, 6, 7)
myC = Array(8, 9)
myD = Array(10, 11, 12, 13, 14)
myE = Array(101, 102, 103)
Dim myCombine As Variant
Dim myNew() As Variant
Dim myElement As Variant
Dim myArr As Variant
Dim cnt As Long
myCombine = Array(myA, myB, myC, myD, myE)
For Each myArr In myCombine
For Each myElement In myArr
ReDim Preserve myNew(cnt)
myNew(cnt) = myElement
cnt = cnt + 1
Next
Next
For cnt = LBound(myNew) To UBound(myNew)
Debug.Print myNew(cnt)
Next cnt
End Sub
The "building" of the new array is facilitated through ReDim Preserve, which keeps the old values in the array whenver the dimension of the array changes. And if you want to do something with these arrays, you may use 3 nested loops (a bit slow) and have some check:
Dim cnt2 As Long
For cnt = LBound(myNew) To UBound(myNew)
For cnt2 = LBound(myCombine) To UBound(myCombine)
For Each myElement In myCombine(cnt2)
If myElement = myNew(cnt) Then
Debug.Print myElement & vbTab & " from " & vbTab & cnt2
End If
Next myElement
Next cnt2
Next cnt
This is what you get on the immediate window:
1 from 0
2 from 0
3 from 0
4 from 0
5 from 1
6 from 1
7 from 1
8 from 2
9 from 2
10 from 3
11 from 3
12 from 3
13 from 3
14 from 3
101 from 4
102 from 4
103 from 4
Alternate 'brick-by-brick' approach.
Option Explicit
Sub combineArrays()
Dim myArray1 As Variant, myArray2 As Variant, myArray3 As Variant
Dim myArray4 As Variant, myArray46 As Variant
ReDim mainArray(0) As Variant
myArray1 = Array(1, 2, 3, 4)
myArray2 = Array(5, 6, 7)
myArray3 = Array(8, 9)
myArray4 = Array(10, 11, 12, 13, 14)
'...
myArray46 = Array(101, 102, 103)
mainArray = buildMainArray(myArray1, mainArray)
mainArray = buildMainArray(myArray2, mainArray)
mainArray = buildMainArray(myArray3, mainArray)
mainArray = buildMainArray(myArray4, mainArray)
mainArray = buildMainArray(myArray46, mainArray)
ReDim Preserve mainArray(UBound(mainArray) - 1)
Debug.Print Join(mainArray, ",")
End Sub
Function buildMainArray(arr As Variant, marr As Variant)
Dim i As Long
For i = LBound(arr) To UBound(arr)
marr(UBound(marr)) = arr(i)
ReDim Preserve marr(UBound(marr) + 1)
Next i
buildMainArray = marr
End Function
The issue with using Redim Preserve to combine arrays is it can be an expensive operation, since you're basically re-creating the array everytime it's called. Since you have 46 arrays you're combining, you may very well be waiting a while.
Instead, you can loop over the arrays to figure out the total number of elements you need, dimension out your master array, then loop over the arrays again to do the actual assignment/merging. Something like this:
' encapsulates code to determine length of an individual array
' note that because arrays can have different LBounds in VBA, we can't simply use
' Ubound to determine array length
Public Function GetArrayLength(anArray As Variant) As Integer
If Not IsArray(anArray) Then
GetArrayLength = -1
Else
GetArrayLength = UBound(anArray) - LBound(anArray) + 1
End If
End Function
Public Function CombineArrays(ParamArray arraysToMerge() As Variant) As Variant
' index for looping over the arraysToMerge array of arrays,
' and then each item in each array
Dim i As Integer, j As Integer
' variable to store where we are in the combined array
Dim combinedArrayIndex As Integer
' variable to hold the number of elements in the final combined array
Dim CombinedArrayLength As Integer
' we don't initialize the array with an array-length until later,
' when we know how long it needs to be.
Dim combinedArray() As Variant
' we have to loop over the arrays twice:
' First, to figure out the total number of elements in the combined array
' second, to actually assign the values
' otherwise, we'd be using Redim Preserve, which can get quite expensive
' because we're creating a new array everytime we use it.
CombinedArrayLength = 0
For i = LBound(arraysToMerge) To UBound(arraysToMerge)
CombinedArrayLength = CombinedArrayLength + GetArrayLength(arraysToMerge(i))
Next i
' now that we know how long the combined array has to be,
' we can properly initialize it.
' you can also use the commented code instead, if you prefer 1-based arrays.
ReDim combinedArray(0 To CombinedArrayLength - 1)
' Redim combinedArray(1 to CombinedArrayLength)
' now that the combinedarray is set up to store all the values in the arrays,
' we can begin actual assignment
combinedArrayIndex = LBound(combinedArray)
For i = LBound(arraysToMerge) To UBound(arraysToMerge)
For j = LBound(arraysToMerge(i)) To UBound(arraysToMerge(i))
combinedArray(combinedArrayIndex) = arraysToMerge(i)(j)
combinedArrayIndex = combinedArrayIndex + 1
Next j
Next i
' assign the function to the master array we've been using
CombineArrays = combinedArray
End Function
To use this function, you'd do something like the following:
Public Sub TestArrayMerge()
Dim myArray1() As Variant
Dim myArray2() As Variant
Dim myArray3() As Variant
Dim myArray4() As Variant
Dim combinedArray As Variant
myArray1 = Array(1, 2, 3, 4)
myArray2 = Array(5, 6, 7)
myArray3 = Array(8, 9)
myArray4 = Array(10, 11, 12, 13, 14)
combinedArray = CombineArrays(myArray1, myArray2, myArray3, myArray4)
If IsArray(combinedArray) Then
Debug.Print Join(combinedArray, ",")
End If
End Sub
Regarding your last bit, that you're using an inner loop to combine the values in your final combined array: Your inner loop doesn't need to start at LBound(myArray). For any value of i, you've already compared it to the elements before it (e.g., when i = 2, it's already been compared to the first element). So you really just need:
For i = LBound(combinedArray) To UBound(combinedArray) - 1
For j = i + 1 To UBound(combinedArray)
' do whatever you need
Next j
Next i
Perhaps this ...
'To determine if a multi-dimension array is allocated (or empty)
'Works for any-dimension arrays, even one-dimension arrays
Public Function isArrayAllocated(ByVal aArray As Variant) As Boolean
On Error Resume Next
isArrayAllocated = IsArray(aArray) And Not IsError(LBound(aArray, 1)) And LBound(aArray, 1) <= UBound(aArray, 1)
Err.Clear: On Error GoTo 0
End Function
'To determine the number of items within any-dimension array
'Returns 0 when array is empty, and -1 if there is an error
Public Function itemsInArray(ByVal aArray As Variant) As Long
Dim item As Variant, UBoundCount As Long
UBoundCount = -1
If IsArray(aArray) Then
UBoundCount = 0
If isArrayAllocated(aArray) Then
For Each item In aArray
UBoundCount = UBoundCount + 1
Next item
End If
End If
itemsInArray = UBoundCount
End Function
'To determine the number of dimensions of an array
'Returns -1 if there is an error
Public Function nbrDimensions(ByVal aArray As Variant) As Long
Dim x As Long, tmpVal As Long
If Not IsArray(aArray) Then
nbrDimensions = -1
Exit Function
End If
On Error GoTo finalDimension
For x = 1 To 65536 'Maximum number of dimensions (size limit) for an array that will work with worksheets under Excel VBA
tmpVal = LBound(aArray, x)
Next x
finalDimension:
nbrDimensions = x - 1
Err.Clear: On Error GoTo 0
End Function
'****************************************************************************************************
' To merge an indefinite number of one-dimension arrays together into a single one-dimension array
' Usage: mergeOneDimArrays(arr1, arr2, arr3, ...)
' Returns an empty array if there is an error
' Option Base 0
'****************************************************************************************************
Public Function mergeOneDimArrays(ParamArray infArrays() As Variant) As Variant
Dim x As Long, y As Long, UBoundCount As Long, newUBoundCount As Long
Dim tmpArr As Variant, allArraysOK As Boolean
UBoundCount = 0
allArraysOK = True
For x = LBound(infArrays) To UBound(infArrays)
If Not IsArray(infArrays(x)) Or Not nbrDimensions(infArrays(x)) = 1 Then
allArraysOK = False
Exit For
End If
UBoundCount = UBoundCount + itemsInArray(infArrays(x))
Next x
If allArraysOK Then
ReDim tmpArr(0 To UBoundCount - 1)
UBoundCount = 0
For x = LBound(infArrays) To UBound(infArrays)
For y = LBound(infArrays(x)) To UBound(infArrays(x))
tmpArr(UBoundCount) = infArrays(x)(y)
UBoundCount = UBoundCount + 1
Next y
Next x
newUBoundCount = itemsInArray(tmpArr)
If newUBoundCount = UBoundCount Then
mergeOneDimArrays = tmpArr
Else
mergeOneDimArrays = Array()
End If
Erase tmpArr
Else
mergeOneDimArrays = Array()
End If
End Function
If you are working with one-dimensional arrays you could use a collection instead. It is much better at handling dynamic sizing.
You can declare a collection and then add each of the elements in the arrays to it. Then you will have one large list with all of the values.
Dim coll As New Collection
coll.Add MyArray(j)
Here is a good to collections introduction:
https://excelmacromastery.com/excel-vba-collections/
So this might be an easy one but I just couldn't work my head around it.I am working on VBA.
I have the following array:
temp=(9,4,9,3,8,4,9,8)
and i want to sort it but instead of returning
temp=(9,9,9,8,8,4,4,3)
i want it to return the index of the value like
temp=(1,3,7,5,8,2,6,4).
Any help is appreciated. Thank you in advance!
Try this:
Sub Tester()
Dim arr, v, i, arr2()
arr = Array(9, 4, 9, 3, 8, 4, 9, 8)
ReDim arr2(LBound(arr) To UBound(arr))
Debug.Print "Original", Join(arr, ",")
For i = LBound(arr2) To UBound(arr2)
arr2(i) = Application.Large(arr, i + 1)
Next i
Debug.Print "Sorted", Join(arr2, ",")
For i = LBound(arr2) To UBound(arr2)
v = Application.Match(arr2(i), arr, 0)
arr2(i) = v 'save the position
arr(v - 1) = vbNull 'remove the found value
Next i
Debug.Print "Positions", Join(arr2, ",")
End Sub
EDIT: without the intermediate sort
Sub Tester2()
Dim arr, v, i, arr2()
arr = Array(9, 4, 9, 3, 8, 4, 9, 8)
ReDim arr2(LBound(arr) To UBound(arr))
For i = LBound(arr) To UBound(arr)
v = Application.Match(Application.Large(arr, 1), arr, 0)
arr(v - 1) = vbNull
arr2(i) = v
Next i
Debug.Print "Positions", Join(arr2, ",")
End Sub
Here is another algo using only native VBA functions, i.e. no Excel functions such as Application.Match etc., which should be much faster for large arrays. Takes about 5 seconds for an array of ca. 9000 elements. It returns the array of indices sort_idx as well as the array of sorted values arr_sorted. Note: the array here is 2D with any number of rows and 1 column, taken from column A on sheet "1". Can be easily adapted for a 1D array.
Sub cost_min()
'Get data
arr = Range(Sheets("1").Range("A1"), Sheets("1").Range("A1").End(xlDown)).Value2
'Sort price curve & get indices
Dim sort_idx(), arr_sorted()
ReDim sort_idx(1 To UBound(arr)), arr_sorted(1 To UBound(arr))
arr_2 = arr 'create copy to edit while sorting
For i = 1 To UBound(arr)
'Get max, record idx & value
max_val = arr_2(1, 1)
j = 1
sort_idx(i) = j
arr_sorted(i) = max_val
For j = 1 To UBound(arr_2)
If arr_2(j, 1) > max_val Then
max_val = arr_2(j, 1)
sort_idx(i) = j
arr_sorted(i) = max_val
End If
Next j
'Replace max found with null
arr_2(sort_idx(i), 1) = vbNull
Next i
End Sub