Filling array with listbox multiple selected items - arrays

I am sharing my code because other code found online either does not work because it was created for excel and not access, as syntax is a little different, or is missing the key function needed, that being based off multi selection.
That said... this code does the following:
having a list box that's row source is query results the code simply puts multiple selected items from a list box in an array to be used in later code.
The difference from excel to access is .list works in excel while .Column(0, i) works in access
Dim i As Integer
Dim x As Variant
Dim MultiArr()
If Me.lbMultiEdit.ListIndex <> -1 Then
For i = 0 To Me.lbMultiEdit.ListCount - 1
If Me.lbMultiEdit.Selected(i) Then
ReDim Preserve MultiArr(x)
MultiArr(x) = Me.lbMultiEdit.Column(0, i)
x = x + 1
End If
Next i
End If
'sanity check....
For i = 0 To x - 1
MsgBox MultiArr(i)
Next i

Your code is non-optimized. It resizes the array for every item that's added. A ReDim Preserve is a very intensive operation, because it essentially creates a new array of the desired size, and then moves all items over.
A more optimized variant, that never uses ReDim Preserve:
Dim i As Integer
Dim x As Variant
Dim MultiArr()
If Me.lbMultiEdit.ItemsSelected.Count = 0 Then Exit Sub 'No items selected
ReDim MultiArr(0 To Me.lbMultiEdit.ItemsSelected.Count - 1)
If Me.lbMultiEdit.ListIndex <> -1 Then 'Why?
For i = 0 To Me.lbMultiEdit.ListCount - 1
If Me.lbMultiEdit.Selected(i) Then
MultiArr(x) = Me.lbMultiEdit.Column(0, i)
x = x + 1
End If
Next i
End If

Rather than iterating over all items and testing whether each item is selected, you could merely iterate over only the selected items, for example:
Dim i As Integer, v, MultiArr()
ReDim MultiArr(0 To Me.lbMultiEdit.ItemsSelected.Count - 1)
For Each v In Me.lbMultiEdit.ItemsSelected
MultiArr(i) = Me.lbMultiEdit.ItemData(v)
i = 1 + i
Next v
Or, using a With statement:
Dim i As Integer, v, MultiArr()
With Me.lbMultiEdit
ReDim MultiArr(0 To .ItemsSelected.Count - 1)
For Each v In .ItemsSelected
MultiArr(i) = .ItemData(v)
i = 1 + i
Next v
End With

Related

Looping through Unique Data Set to Return Matching Value on a Different Sheet

I have searched for something similar to what I am asking, and unfortunately there is nothing close to what I am looking for.
I have a unique data set here on Sheet(2): The goal is to return the values in the highlighted blue columns if it matches the same "Item#" for the box selected in a dropdown list of the box names on Sheet(1). Please see Sheet(1) here: Sheet(1) Set-Up.
The Item#'s on Sheet(1) are located in B3:B12 on Sheet(1). - I've added also another list where I would like my code to run In the column next to this is a blank where the matching items in blue would post.
I am trying to use For Loops to accomplish this. I understand that the data set is weird, but I want to keep it like that for the mere challenge of it (and also because I have a larger data set similar and am just using this as a test run)... My code so far is as follows:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim KeyCells As Range
' In order to run code on sheet without a button or enabling in a module
Set KeyCells = Range("A1")
If Not Application.Intersect(KeyCells, Range(Target.Address)) Is Nothing Then
Dim i, j As Long
Dim n As Long
Dim box As String
Set sh2 = ThisWorkbook.Sheets(2)
Set rn2 = sh2.UsedRange
box = Sheets(1).Cells.Range("A1")
Dim k1 As Long
k1 = rn2.Rows.Count + rn2.Row - 1
n = 0
For i = 1 To k1
If Sheets(2).Cells(1, i) = box Then
If n = 0 Then
Sheets(1).Cells(3, 3).Value = Sheets(2).Cells(i, 2)
n = n + 1
End If
ElseIf n > 0 Then
For j = 3 To n + 2
If Sheets(2).Cells(2, i).Value = Sheets(1).Cells(j, 2).Value Then
If Sheets(2).Cells(2, i).Value <> Sheets(1).Cells(j, 2).Value Then
x = x
Else
x = x + 1
End If
End If
Next
If x = 0 Then
Sheets(1).Cells(3 + n, 3).Value = Sheets(2).Cells(2, i).Value
n = n + 1
End If
End If
x = 0
Next
End If
End Sub
Please let me know what you experts think!
Edit 2; the macro finds Sheet1.Range("A1").Value in Sheet2 row 1. It then loops through each cell below the found value in Sheet2. It then finds each cells value in Sheet1. It will then copy the cells value in Sheet2 from the next cell to the right, and place the value in the cell in Sheet1 to the next cell to the right. It then loops down to the next cell in sheet2, and performs the same task, etc.
Private Sub Worksheet_Change(ByVal target As Range) 'Works
Dim fndTrgt As Range, fndCel As Range
If target.Address = "$A$1" Then
Set fndTrgt = Sheets("Sheet2").Rows(1).Find(target.Value)
If Not fndTrgt Is Nothing Then
For i = 1 To 5
Set fndCel = Sheets("Sheet1").Range("A2:D12").Find(fndTrgt.Offset(i).Value)
If Not fndCel Is Nothing Then
fndCel.Offset(, 1).Value = fndTrgt.Offset(i, 1).Value
End If
Next i
End If
End If
End Sub

VBA - Loop Through Array, Assign Value to Different Array

I'm creating two arrays based on a range in my Excel sheet:
AdjustedProductionValues,
ProductionTargetValues
I'm creating a third array to hold new values:
FinalProductionValues
I want to loop through AdjustedProductionValues; if the value is 0 I want to assign the value of ProductionTargetValues to a new array FinalProductionValues. Otherwise, I want to assign the value of AdjustedProductionValues to FinalProductionValues.
I keep getting an error of Subscript out of range. I've tried ReDim a couple different ways with no success. I get the error at the If statement.
How do I fix this?
Sub TEST()
Dim AdjustedProductionValues() As Variant
Dim ProductionTargetValues() As Variant
Dim FinalProductionValues() As Variant
ReDim FinalProductionValues(1 To 1) As Variant
Dim i As Integer
'Assigning Adjusted Production and Production Target numbers into lists
Worksheets("SUMMARY").Activate
AdjustedProductionValues = Range(Range("E35"), Range("E35").End(xlToRight))
ProductionTargetValues = Range(Range("E34"), Range("E34").End(xlToRight))
'checking each Adjusted Production value
'if it's 0 then assigns the Adjusted Production value to a new list
'if it's not 0 it assignes the Production Target to the new list
For i = 0 To UBound(AdjustedProductionValues)
ReDim Preserve FinalProductionValues(1 To UBound(FinalProductionValues) + 1) As Variant
If AdjustedProductionValues(i) = 0 Then
FinalProductionValues(UBound(FinalProductionValues)) = ProductionTargetValues(i)
Else
FinalProductionValues(UBound(FinalProductionValues)) = AdjustedProductionValues(i)
End If
Next i
End Sub
UPDATE W/ FIX
I fixed my code with this:
For i = 1 To UBound(AdjustedProductionValues, 2)
ReDim Preserve FinalProductionValues(1 To UBound(FinalProductionValues) + 1) As Variant
If AdjustedProductionValues(1, i) = 0 Then
FinalProductionValues(UBound(FinalProductionValues)) = ProductionTargetValues(1, i)
Else
FinalProductionValues(UBound(FinalProductionValues)) = AdjustedProductionValues(1, i)
End If
Next i
I checked my work by using the following:
Worksheets("Sheet2").Activate
Dim NumRows As Long
Dim NumCols As Long
NumRows = 1
NumCols = UBound(FinalProductionValues, 1) - LBound(FinalProductionValues, 1) + 1
Range("A1").Resize(NumRows, NumCols).Value = FinalProductionValues
Got help checking my code with this link.

Condensing Excel data with overlapping index/repetitive word occurrence

I have an excel sheet that is formatted like so:
I would like to format it to be something like this:
It is about 40,000 cells of information, so is there any way to do this that isn't manually?
You could probably use =SUMIF to achieve this, since you appear to have numbers as values.
Create a new sheet, copy column A from your data sheet to your new sheet and remove duplicates. Copy row 1 from your data sheet to your new sheet.
Use this formula in sheet 2 cell B2:
=SUMIF(Sheet1!$A:$A;Sheet2!$A2;Sheet1!B:B)
Drag the formula to the right, then down.
I am by no means an excel expert, and this is going to be my first answer ever. Take this into account please.
I've checked it and it works.
I've add a command button in Sheet1 (where the original data is), and when clicked this code writes formatted data into Sheet2.
No need to manually remove duplicates!
Dim dU1 As Object, cU1 As Variant, iU1 As Long, lrU As Long
Dim MyArray() As Variant
Dim i As Integer
Dim j As Integer
Dim k As Integer
Dim h As Integer
Private Sub CommandButton1_Click()
'Get unique indexes
Set dU1 = CreateObject("Scripting.Dictionary")
lrU = Cells(Rows.Count, 1).End(xlUp).Row 'number of rows
cU1 = Range("A2:A" & lrU) 'Assuming your data starts in A2
For iU1 = 1 To UBound(cU1, 1)
dU1(cU1(iU1, 1)) = 1
Next iU1
'Now dU1 contains indexes as unique values (about, absence, etc.)
For i = 0 To dU1.Count - 1 'for each index
ReDim MyArray(1 To 1) As Variant 'starts a "new" array
For j = 2 To 9 'each of the columns with values (D1-D8)
a = 0
For k = 2 To lrU 'all rows
If (Worksheets("Sheet1").Cells(k, 1).Value = dU1.keys()(i) And Worksheets("Sheet1").Cells(k, j).Value <> "") Then
MyArray(UBound(MyArray)) = Worksheets("Sheet1").Cells(k, j).Value 'add value to array
ReDim Preserve MyArray(1 To UBound(MyArray) + 1) As Variant 'resize array (now is 1 element longer)
a = a + 1
End If
Next
If a = 0 Then 'if no value found, add an element to array anyway
MyArray(UBound(MyArray)) = "" 'add value to array
ReDim Preserve MyArray(1 To UBound(MyArray) + 1) As Variant 'resize array (now is 1 element longer)
End If
Next
Worksheets("Sheet2").Cells(i + 2, 1) = dU1.keys()(i) 'write indexes in another sheet
For h = 2 To UBound(MyArray)
Worksheets("Sheet2").Cells(i + 2, h) = MyArray(h - 1)
Next
Next
End Sub

Cleaning up an array

I have a very large array in VBA which includes a lot of 0 values that I'd like to remove. Something like this:
A B C 12345
D E F 848349
G H I 0
J K L 0
M N O 0
P Q R 4352
S T U 0
V W X 0
I would like to be able to quickly/easily strip out all rows from this array that have a zero in the 4th column, resulting in something like this:
A B C 12345
D E F 848349
P Q R 4352
This array has 100,000 or so rows, that hopefully gets down to a number closer to 20,000 or 30,000 rows instead after processing.
I assume iterating through every entry will prove very time-consuming.
Is there another way that is faster?
I'm not aware of any other way in VBA than to loop through the array and write another array/list.
What makes it trickier is that your array looks to be two-dimensional and VBA will only allow you to redim the last dimension. From the look of your data, you'd want to redim the first dimension as you iterate through your array.
There are several solutions:
Iterate your data twice - once to get the array size (and probably to store the relevant row numbers) and a second time to transfer the raw data into your new data.
Iterate once and just reverse your dimensions (ie row is last).
Use an array of arrays, so that each array only has one dimension).
Use a Collection which doesn't need to be dimensioned - this would be my preferred option.
Option 4 would look like this (I've assumed your array is zero based):
Dim resultList As Collection
Dim r As Long
Set resultList = New Collection
For r = 0 To UBound(raw, 1)
If raw(r, 3) <> 0 Then
resultList.Add Array(raw(r, 0), raw(r, 1), raw(r, 2), raw(r, 3))
End If
Next
If you have to write to a new array, then here's an example of Option 1:
Dim rowList As Collection
Dim result() As Variant
Dim r As Long
Dim c As Long
Dim v As Variant
Set rowList = New Collection
For r = 0 To UBound(raw, 1)
If raw(r, 3) <> 0 Then
rowList.Add r
End If
Next
ReDim result(rowList.Count - 1, 3) As Variant
c = 0
For Each v In rowList
result(c, 0) = raw(v, 0)
result(c, 1) = raw(v, 1)
result(c, 2) = raw(v, 2)
result(c, 3) = raw(v, 3)
c = c + 1
Next
Okay, it's all off-sheet, so all the arrays are zero-based. To test this set-up, I created a worksheet with four columns, as per your data and using random numbers in the fourth column. I saved this to a text file (TestFile.txt), then read it in to be able to get a zero-based array (Excel ranges are 1-based when you take them into an array). I saved 150000 rows to the text file to properly stress the routine. Yes, I have an SSD and that would affect the 2s run time, but I'd still expect it to run in <10s on a spinning HDD, I think.
Anyway, here's the code (requires a VBA reference to Microsoft Scripting Runtime purely to read in the file):
Public Function ReturnFilteredArray(arrSource As Variant, _
strValueToFilterOut As String) As Variant
Dim arrDestination As Variant
Dim lngSrcCounter As Long
Dim lngDestCounter As Long
ReDim arrDestination(UBound(arrSource, 1) + 1, UBound(arrSource, 2) + 1)
lngDestCounter = 1
For lngSrcCounter = LBound(arrSource, 1) To UBound(arrSource, 1)
' Assuming the array dimensions are (100000, 3)
If CStr(arrSource(lngSrcCounter, 3)) <> strValueToFilterOut Then
' Hit an element we want to include
arrDestination(lngDestCounter, 1) = arrSource(lngSrcCounter, 0)
arrDestination(lngDestCounter, 2) = arrSource(lngSrcCounter, 1)
arrDestination(lngDestCounter, 3) = arrSource(lngSrcCounter, 2)
arrDestination(lngDestCounter, 4) = arrSource(lngSrcCounter, 3)
lngDestCounter = lngDestCounter + 1
End If
Next
ReturnFilteredArray = arrDestination
End Function
Sub TestRun()
Dim fso As FileSystemObject
Dim txs As TextStream
Dim arr As Variant
Dim arr2 As Variant
Dim lngCounter As Long
Debug.Print Now()
Set fso = New FileSystemObject
Set txs = fso.OpenTextFile("E:\Users\Thingy\Desktop\TestFile.txt", ForReading)
arr = Split(txs.ReadAll, vbNewLine)
ReDim arr2(UBound(arr), 3)
For lngCounter = 0 To UBound(arr) - 1
arr2(lngCounter, 0) = Split(arr(lngCounter), vbTab)(0)
arr2(lngCounter, 1) = Split(arr(lngCounter), vbTab)(1)
arr2(lngCounter, 2) = Split(arr(lngCounter), vbTab)(2)
arr2(lngCounter, 3) = Split(arr(lngCounter), vbTab)(3)
Next
arr2 = ReturnFilteredArray(arr2, "0")
Range("L2").Resize(UBound(arr2, 1), 5) = arr2
Debug.Print Now()
End Sub
There are a number of assumptions in there, not least the dimensions. Note the difference in the second dimension counter between arrDestination and arrSource. That's to do with Excel being 1-based and normal arrays being 0-based.
Also, when I'm writing out the array, I needed to bump up the second dimension to 5 in order to get all of the array out to the sheet. I wasn't able to trim off the empty elements since ReDim Preserve only works on the uppermost dimension (columns) and it's the first dimension (rows) that's changing here.
Anywho, this should serve as a reminder that despite its faults Excel is pretty amazing.

Comparing two large lists with multiple columns (same number in each list) in excel VBA and do...more stuff

I've searched far and wide and I can't quite find anything to fit my needs.
The situation:
I have two lists of data with the same type data in each column (10 columns but the last 2 are useless), but the lists are of varying length (currently 55k in one, 18k in the other). The longer list is going to be a running list of items with the most up to date data in each column for the unique ID # in column A. The other list is linked to a SharePoint list that I update a couple times each day.
The need:
I need the list that updates from SharePoint to be compared to the running list. If there are matching Unique ID #'s in the lists, then the running list needs to be updated to the pulled data. If the running list doesn't contain a Unique ID that is in the pulled list, the new line needs to be added to the running list (which will be sorted later).
I first tried doing this with cell references in two for loops and for only 10 rows this worked fine. When I tried running it for every line, I had problems. So I tried using arrays instead, but this is new territory for me. The code seems to be working, but it's taking a really long time to run (I've let it go for 10 minutes before force stopping). I've tried adding some efficiency increases like turning off screen updating and calculations, but they shouldn't have any effect since I'm using arrays and not actually updating the cells until the array comparison is finished. If arrays are more efficient, great, but I don't know how to combine the data from the pulled list's array to the running list's array.
Here is the code that I have so far:
Sub Data_Compile_Cells()
Dim sdata As Worksheet, spull As Worksheet
Dim p As Long, d As Long, c As Long
Dim lrdata As Long, lrpull As Long
Dim rdata As Range, rpull As Range
Dim Newvalue As Boolean
Dim apull As Variant, adata As Variant
Dim nrows As Long, ncols As Integer
Set sdata = Sheets("Data")
Set spull = Sheets("Data Pull")
Newvalue = "FALSE"
i = 1
apull = spull.Range("A1").CurrentRegion
adata = sdata.Range("A1").CurrentRegion
'lrdata = sdata.Range("A" & Rows.Count).End(xlUp).Row
'lrpull = spull.Range("A" & Rows.Count).End(xlUp).Row
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
sdata.Activate
'*****UniqueID Check******
'Run through list of Unique ID's pulled from SharePoint
For p = 2 To UBound(apull, 1)
'I tried to add a status bar to see if the code was actually running
'Application.StatusBar = "Progress: " & p & " of " & UBound(apull, 1) & " : " & Format(p / UBound(apull, 1), "0%")
'Compare each one to the Unique ID's already listed
For d = 2 To UBound(adata, 1)
'Check for matching Unique ID's
If adata(d, 1) = apull(p, 1) Then
'Check each cell in the row with the matching Unique ID
For c = 2 To 10
'If a cell does not have the same data, replace the Data array value with the value from the Pull array
If adata(p, c) <> apull(d, c) Then
adata(d, c) = apull(p, c)
End If
Next c
'If a match is found, skip to the next p value
Exit For
Else
Newvalue = "TRUE"
'Need code to append new line to Data array
End If
Next d
Next p
'Sort the data
'Range("A2").CurrentRegion.Sort key1:=Range("A2"), order1:=xlAscending, Header:=xlYes
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
Any direction would be much appreciated.
This ran in <1 sec for me, using 20k rows "data", ~3k rows "pull" (mix of updates and new).
EDIT: tidied up and added some comments...
Sub tester()
Const NUM_NEW As Long = 20000 'large enough ?
Dim arrPull, arrData, arrDataId, arrNew()
Dim ubP As Long, ubD As Long
Dim numNew As Long, r As Long
Dim v, c As Long
Dim t, tmp, coll As Collection
t = Timer
'grab the current and new data
arrPull = Sheets("Pull").Range("A1").CurrentRegion.Value
arrData = Sheets("Data").Range("A1").CurrentRegion.Value
ubP = UBound(arrPull, 1)
ubD = UBound(arrData, 1)
numNew = 0
ReDim arrNew(1 To NUM_NEW, 1 To 10) 'array for new data
'create a collection to map ID to "row number"
Set coll = New Collection
For r = 1 To ubD
coll.Add Item:=r, Key:=arrData(r, 1)
Next r
For r = 1 To ubP
tmp = arrPull(r, 1)
v = 0
'collection has no "exists" function, so trap any error
On Error Resume Next
v = coll.Item(tmp)
On Error GoTo 0
If v > 0 Then
'Id already exists: update data
For c = 2 To 10
arrData(v, c) = arrPull(r, c)
Next c
Else
'new Id: add to the "new" array
numNew = numNew + 1
If numNew > NUM_NEW Then
MsgBox "Need larger `new` array!"
'a more sophisticated approach would be to dump the full
' array to the sheet and then redimension it for more
' data...
Exit Sub
End If
For c = 1 To 10
arrNew(numNew, c) = arrPull(r, c)
Next c
End If
Next r
'drop updated and new (if any) to the worksheet
With Sheets("Data")
.Range("A1").CurrentRegion.Value = arrData
If numNew > 0 Then
.Cells(ubD + 1, 1).Resize(numNew, 10).Value = arrNew
End If
End With
Debug.Print "Done in " & Timer - t & " sec"
End Sub
You would be better off using MSAccess to do this. Link to both tables and then do an inner join on the id field or which ever field links the items in the two lists.

Resources