VBA Trying to make a function that will randomly assign names to each other without repeating, getting Compile error: Expected array - excel

I am trying to make a button that will take a list of names and match them up with each other over multiple weeks without matching the same people up twice. With the code I have written I get an error that says "Compile Error: Expected Array"
Below is the code that I have so far. Any help would be appreciated
Sub nameMatcher()
Dim column As Integer
Dim cellsDown As Integer
Dim randomNumber As Integer
Dim names As String
Dim i As Byte
Dim arI As Byte
Dim myRange As Range
Dim myCell As Range
Dim numNames As Integer
Worksheets("Sheet1").Activate
cellsDown = 3
column = Application.InputBox(Prompt:="What meeting number is this for?", Type:=1)
numNames = Application.CountA(Range("A:A")) - 1
i = 1
Do While i <= numNames
randomNum:
randomNumber = Application.RandBetween(2, numNames + 1)
For arI = LBound(names) To UBound(names)
If names(arI) = Cells(randomNumber, 1).Value Then
GoTo randomNum
End If
Next arI
names(i) = Cells(randomNumber, 1).Value
i = i + 1
Loop
Worksheet
For arI = LBound(names) To UBound(names)
Cells(cellsDown, column) = names(arI)
cellsDown = cellsDown + 1
Next arI
Application.ScreenUpdating = True
End Sub

Like #Warcupine suggested - your main problem is your not declaring your array as a variant or an array. Both will work, but I think it's simplest to declare as a variant and then assign your column of values to create the array
Change
Dim names As String
To
Dim names As Variant
Then after the line (are you subtracting 1 to ignore header??)
numNames = Application.CountA(Range("A:A")) - 1
Add this line to fill in your array (assuming top row is ignored header)
names = Range("A2:A" & numNames+1)
And then you'll have to change all your array references to use the second dimension as 1
So names(arI) becomes names(arI,1)
and names(i) becomes names(i,1)

Related

Pick random names from different lists excel VBA

I would like to pick random names from columns in excel like this :
-In the first sheet "Inscrp" is where the lists are, and the second sheet "Tirage" is where the results of the picking.
-Column A in the sheet "Tirage" should pick random names from column A in the sheet "Inscrp" and the same for the column B, C , till the number of columns I chose
I managed to do this with only the first column and here is the code :
Sub PickNamesAtRandom()
Dim HowMany As Integer
Dim NoOfNames As Long
Dim RandomNumber As Integer
Dim Names() As String 'Array to store randomly selected names
Dim i As Byte
Dim CellsOut As Long 'Variable to be used when entering names onto worksheet
Dim ArI As Byte 'Variable to increment through array indexes
Application.ScreenUpdating = False
HowMany = 5
CellsOut = 8
ReDim Names(1 To HowMany) 'Set the array size to how many names required
NoOfNames = Application.CountA(Worksheets("Inscrp").Range("A3:A100")) - 1 ' Find how many names in the list
i = 1
Do While i <= HowMany
RandomNo:
RandomNumber = Application.RandBetween(3, NoOfNames + 1)
'Check to see if the name has already been picked
For ArI = LBound(Names) To UBound(Names)
If Names(ArI) = Worksheets("Inscrp").Cells(RandomNumber, 1).Value Then
GoTo RandomNo
End If
Next ArI
Names(i) = Worksheets("Inscrp").Cells(RandomNumber, 1).Value ' Assign random name to the array
i = i + 1
Loop
'Loop through the array and enter names onto the worksheet
For ArI = LBound(Names) To UBound(Names)
Worksheets("Tirage").Cells(CellsOut, 1) = Names(ArI)
CellsOut = CellsOut + 1
Next ArI
Application.ScreenUpdating = True
End Sub
Please, test the next code. If I correctly understand your nee, it will extract HowMany random numbers from each column (nrCol) of "Inscrip" sheet and placed starting from CellsOut in sheet "Tirage". The already extracted name is eliminated from the array where it used to exist (to avoid repeated names). The ranges ar placed in arrays and due to that, the code should be very fast mostly working in memory, even for large ranges:
Sub PickNamesAtRandom()
Dim shI As Worksheet, lastR As Long, shT As Worksheet, HowMany As Long
Dim rndNumber As Integer, Names() As String, i As Long, CellsOut As Long
HowMany = 5: CellsOut = 8
Set shI = Worksheets("Inscrp")
Set shT = Worksheets("Tirage")
Dim col As Long, arrCol, filt As String, nrCol As Long
nrCol = 2 'number of columns to be returned. It can be changed and also be calculated...
For col = 1 To nrCol
lastR = shI.cells(shI.rows.count, col).End(xlUp).Row 'last row in column to be processed
If lastR >= HowMany + 2 Then '+ 2 because the range is build starting with the third row...
arrCol = Application.Transpose(shI.Range(shI.cells(3, col), shI.cells(lastR, col)).Value2) 'place the range in a 1D array
ReDim Names(1 To HowMany) 'Set the array size to how many names required
For i = 1 To UBound(Names)
tryAgain:
Randomize
rndNumber = Int((UBound(arrCol) - LBound(arrCol) + 1) * Rnd + LBound(arrCol))
If arrCol(rndNumber) = "" Then GoTo tryAgain
Names(i) = arrCol(rndNumber)
filt = arrCol(rndNumber) & "##$$#": arrCol(rndNumber) = filt
arrCol = filter(arrCol, filt, False) 'eliminate the already used name from the array
Next i
shT.cells(CellsOut, col).Resize(UBound(Names), 1).Value2 = Application.Transpose(Names)
End If
Next col
MsgBox "Ready..."
End Sub
If something unclear, do not hesitate to ask for clarifications...

vba Multiply Range with Range

I'd like to multiply the cells of column P with the cells in column M and replace the content of column P with the respective product. Afterwards I want to do the exact same thing with columns Q and N.
I've been trying to look this issue up and the closest solution was: VBA multiply two named ranges
Unfortunately, after running through the first column and calculating it, Excel gives me a runtime error 13 - type mismatch.
My code:
Sub rechnen_mod()
Dim aud_y As Range
Dim soc_r As Range
Dim mp_y As Range
Dim mp_r As Range
Set aud_y = Sheets("MRP score template").[P11:P1000]
Set soc_r = Sheets("MRP score template").[Q11:Q1000]
Set mp_y = Sheets("MRP score template").[M11:M1000]
Set mp_r = Sheets("MRP score template").[N11:N1000]
For i = 1 To Range("P").End(xlDown).Row
aud_y(i, 1) = aud_y(i, 1) * mp_y(i, 1)
Next i
For j = 1 To Range("Q").End(xlDown).Row
soc_r(j, 1) = soc_r(j, 1) * mp_r(j, 1)
Next j
End Sub
Any help would be very appreciated.
EDIT: After reading <stackoverflow.com/a/22056347/11231520> I changed the code to:
Public Sub array1()
Dim x As Long
Dim arr
Dim arr_e
Dim arrf
Dim arrf_e
Dim results
Dim r As Range
arr = Sheets("MRP score template").[P11:P473]
arrf = Sheets("MRP score template").[M11:M473]
ReDim results(1 To UBound(arr) * UBound(arrf))
For Each arr_e In arr
For Each arrf_e In arrf
x = x + 1
results(x) = arr_e * arrf_e
Next arrf_e
Next arr_e
Set r = Sheets("calc").Range("A1:A" & UBound(results))
r = Application.Transpose(results)
End Sub
Excel gives me a runtime error 13 - type mismatch with the explanation that arrf_e = error 2402. After a quick research this should mean that the array contains #NA - but it doesn't.
After clicking on debugging, the marked line is
results(x) = arr_e * arrf_e
Try to use below code instead. I also added comments to explain each step :)
Option Explicit
Public Sub rechnen_mod()
Dim mp_y() As Variant
Dim mp_r() As Variant
Dim aud_y() As Variant
Dim soc_r() As Variant
Dim arrResult_P() As Variant
Dim arrResult_Q() As Variant
Dim iLastRow As Integer
Dim iSizeArrays As Integer
Dim iIndexSearch As Integer
With ThisWorkbook.Worksheets("MRP score template")
' Find last row of table, replace it with fixed value if you prefer
iLastRow = .UsedRange.Rows(.UsedRange.Rows.Count).Row
' Store data in arrays
mp_y = .Range("M11", "M" & iLastRow).Value
mp_r = .Range("N11", "N" & iLastRow).Value
aud_y = .Range("P11", "P" & iLastRow).Value
soc_r = .Range("Q11", "Q" & iLastRow).Value
' Calculate size of arrays
iSizeArrays = UBound(mp_y) - LBound(mp_y) + 1
' ReDim result arrays according to iSizeArrays
ReDim arrResult_P(1 To iSizeArrays)
ReDim arrResult_Q(1 To iSizeArrays)
' Calculate result values
For iIndexSearch = 1 To iSizeArrays
arrResult_P(iIndexSearch) = mp_y(iIndexSearch, 1) * aud_y(iIndexSearch, 1)
arrResult_Q(iIndexSearch) = mp_r(iIndexSearch, 1) * soc_r(iIndexSearch, 1)
Next iIndexSearch
' Write results in the worksheet
.Range("P11", "P" & iLastRow) = Application.WorksheetFunction.Transpose(arrResult_P)
.Range("Q11", "Q" & iLastRow) = Application.WorksheetFunction.Transpose(arrResult_Q)
End With
End Sub
I tested it with random values on 250 rows and it worked fine.

Compare two sheets and highlight unmatched rows using unique ID only

I want to match rows from two different sheets and highlight only in the first column of the unmatched row or better still copy the unmatched rows into a new sheet. The code should compare the rows of the two Sheets and color the new rows in the second sheet. Sheet2 (say Jan 2020) contains more rows than Sheet1 (Dec 2019) as its the recently updated sheet and they both contain rows of over 22k with both having unique ID as the first column.
My below code tries to highlight all the unmatching cells and takes longer time to finish. What I wish is for the code to just color the unmatched in column A (the vb.Red) only(since its the unique ID) while ignoring the rest of the column/cells (vb.Yellow) and or if possible copy the highlighted rows into a new sheet.
Sub RunCompare()
Call compareSheets("Sheet1", "Sheet2") 'compareSheets("2019-01 Database", "2019-02 Database")
End Sub
Sub compareSheets(shtSheet1 As String, shtSheet2 As String)
Dim c As Integer, j As Integer, i As Integer, mydiffs As Integer, cnt1 As Integer, cnt2 As Integer
Dim noexist As Integer
cnt2 = Worksheets("Sheet2").Cells.SpecialCells(xlCellTypeLastCell).Row
cnt1 = Worksheets("Sheet1").Cells.SpecialCells(xlCellTypeLastCell).Row
'For each cell in sheet2 that is not the same in Sheet1, color it yellow
For i = 1 To cnt2
For j = 1 To cnt1
If ActiveWorkbook.Worksheets(shtSheet2).Cells(i, 1).Value = ActiveWorkbook.Worksheets(shtSheet1).Cells(j, 1).Value Then
For c = 2 To 22
If Not ActiveWorkbook.Worksheets(shtSheet2).Cells(i, c).Value = ActiveWorkbook.Worksheets(shtSheet1).Cells(j, c).Value Then
ActiveWorkbook.Worksheets(shtSheet2).Cells(i, c).Interior.Color = vbYellow
mydiffs = mydiffs + 1
End If
Next
Exit For
End If
If j = cnt1 Then
ActiveWorkbook.Worksheets(shtSheet2).Cells(i, 1).Interior.Color = vbRed
End If
Next
Next
'Display a message box to demonstrate the differences and if there is a new entry on the second sheet
'MsgBox mydiffs & ":differences found, " & noexist & ":no exist", vbInformation
ActiveWorkbook.Sheets(shtSheet2).Select
End Sub
Let's simplify the task and do it step by step.
This is how the input in the two sheets can look like:
Then, we may consider reading these and saving them to an array:
Set rangeA = ThisWorkbook.Worksheets(1).Range("A1:Z1")
Set rangeB = ThisWorkbook.Worksheets(2).Range("A1:ZZ1")
Dim arrayA As Variant
Dim arrayB As Variant
With Application
arrayA = .Transpose(.Transpose(rangeA))
arrayB = .Transpose(.Transpose(rangeB))
End With
Looping between the data in the two arrays is quite fast in vba. The writing to the third worksheet is done only once the two values from the two arrays match:
Dim myValA As Variant
Dim myValB As Variant
Dim currentRow As Long: currentRow = 1
For Each myValA In arrayA
For Each myValB In arrayB
If myValA = myValB Then
ThisWorkbook.Worksheets(3).Cells(currentRow, 1) = myValA
currentRow = currentRow + 1
End If
Next
Next
This is the result in the third worksheet, all matching values are in a single row:
This is how the whole code looks like:
Sub CompareTwoRanges()
Dim rangeA As Range
Dim rangeB As Range
Set rangeA = ThisWorkbook.Worksheets(1).Range("A1:Z1")
Set rangeB = ThisWorkbook.Worksheets(2).Range("A1:ZZ1")
Dim arrayA As Variant
Dim arrayB As Variant
With Application
arrayA = .Transpose(.Transpose(rangeA))
arrayB = .Transpose(.Transpose(rangeB))
End With
Dim myValA As Variant
Dim myValB As Variant
Dim currentRow As Long: currentRow = 1
For Each myValA In arrayA
For Each myValB In arrayB
If myValA = myValB Then
ThisWorkbook.Worksheets(3).Cells(currentRow, 1) = myValA
currentRow = currentRow + 1
End If
Next
Next
End Sub
Note - there will be another performance bonus, if the results are written to an array and then written from the array to the worksheet. Thus the writing would happen only once. This is the change, that needs to be implemented in the code, after the array declarations:
Dim myValA As Variant
Dim myValB As Variant
Dim resultArray() As Variant
ReDim Preserve resultArray(2 ^ 20)
Dim i As Long: i = 0
For Each myValA In arrayA
For Each myValB In arrayB
If myValA = myValB Then
resultArray(i) = myValA
i = i + 1
End If
Next
Next
ReDim Preserve resultArray(i)
ThisWorkbook.Worksheets(3).Cells(1, 1).Resize(UBound(resultArray)) = Application.Transpose(resultArray)
when you get cell value, it spends time.
so, you can target Range transfer 2d Variant
Dim endRow AS Long
Dim olderRange AS Range
Dim olderVariant AS Variant
endRow = olderSheet.cells(rows.count,1).end(xlup).row
Set olderRange = olderSheet.Range(olderSheet.Cells(startRow, startCol), olderSheet.Cells(endRow, endCol))
'Transfer
olderVariant = olderRange
For currentRow = 1 to UBound(olderVariant, 1)
'Loop
'if you want change real Cell value Or interior
'add row Or Col weight
if olderVariant(currentRow, currentCol) = newerVariant(currentRow, currentCol) THen
newerSheet.Cells(currentRow+10,currentCol+10).interior.colorIndex = 3
End if
Next currentRow
In case anyone has the same kind of problem, I have found an easier way to do it. Providing your sheet2 is the comparison sheet:
Dim Ary1 As Variant, Ary2 As Variant
Dim r As Long
Ary1 = Sheets("Sheet1").UsedRange.Value2
Ary2 = Sheets("Sheet2").UsedRange.Value2
With CreateObject("scripting.dictionary")
For r = 1 To UBound(Ary1)
.Item(Ary1(r, 1)) = Empty
Next r
For r = 1 To UBound(Ary2)
If Not .Exists(Ary2(r, 1)) Then Sheets("Sheet2").Cells(r, 1).Interior.Color = vbRed
Next r
End With

Search string in a range (text template) and replace from dynamic rows

Currently I have a template which is in range called rngP1.
And this contains a text below:
"This is to confirm that strTitle has been enacted on strDate for strCompany."
Basically, I have a data in another sheet that will be used to replace these 3 strings from my template:
So what I would like to happen is that in every row data it will search strings strTitle, strDate, and strCompany and replace them according to the data of each row.
I have a code already, however, it doesn't work as I expected:
Sub example()
Dim wsMain As Worksheet
Set wsMain = Sheets("Main")
Dim wsTemplate As Worksheet
Set wsTemplate = Sheets("Template")
Dim textToReplace As Variant
Dim array_example()
Dim Find_Text As Variant
Dim str As String
last_row = wsMain.Range("A1").End(xlDown).Row 'Last row of the data set
ReDim array_example(last_row - 1, 2)
Find_Text = Array("strTitle", "strDate", "strCompany")
str = wsTemplate.Range("rngP1").Value
'Storing values in the array
For i = 0 To last_row - 1
array_example(i, 0) = wsMain.Range("A" & i + 2)
array_example(i, 1) = wsMain.Range("C" & i + 2)
array_example(i, 2) = wsMain.Range("D" & i + 2)
Next
For i = LBound(array_example, 1) To UBound(array_example, 1)
For j = LBound(array_example, 2) To UBound(array_example, 2)
For a = 0 To UBound(Find_Text)
str = Replace(str, Find_Text(a), array_example(i, j))
Next a
Next j
MsgBox str
Next i
End Sub
Wrong Output:
It should be:
This is to confirm that Title1 has been enacted on 13-October-18 for Company X.
And next one would be the next row which is title 2. So on and so fort.
If you have an alternative way to do it, I appreciate it.
Here is a working example:
You can push the data range from a worksheet into an array with one line without looping
DataArr = wsMain.Range("A2:D" & LastRow).Value
You need only 2 loops for the replacing:
one to loop through the data rows
one to loop through the variables to replace
Your template str was not initialized within the loop, but you need a fresh template for every data row.
Note that the array loaded from the range starts counting from 1 but the variables array starts counting from 0.
Option Explicit
Sub Example()
Dim Template As String
Template = "This is to confirm that strTitle has been enacted on strDate for strCompany."
'load your template string from worksheet here!
Dim Variables As Variant 'variables to be replaced
Variables = Array("strTitle", "strDate", "strCompany")
Dim wsMain As Worksheet
Set wsMain = ThisWorkbook.Worksheets("Main")
Dim LastRow As Long 'this method is more reliable to find the last used row
LastRow = wsMain.Cells(wsMain.Rows.Count, "A").End(xlUp).Row
Dim DataArr As Variant 'load the complete data range into an array
DataArr = wsMain.Range("A2:D" & LastRow).Value
Dim Output As String
Dim iRow As Long, iVar As Long
For iRow = LBound(DataArr, 1) To UBound(DataArr, 1) '1 to LastRow
Output = Template 'initialize with the template!
For iVar = LBound(Variables) To UBound(Variables) ' 0 to 2
Output = Replace(Output, Variables(iVar), DataArr(iRow, iVar + 1))
Next iVar
Debug.Print Output
Next iRow
End Sub

Separate data in Column and rows, and link to value

As you can see in this excel example, column A has mixed data.
The bold M numbers are the "keys" and the blue text that is indented and blue is linked to it. I'm trying to figure out how to separate the blue text from the bold M numbers but keep them linked as I am trying to insert this data into a SQL database.
So for example, this is the output I am trying to get to, so I can insert it into my database table.
I've tried doing text to column, but I am having an issue linking them to the appropriate bold number. I'm thinking this is in call for excel-vba, but I don't know where to begin. Any help would be greatly appreciated.
You do not need VBA. If the BOLD values will always start with "M" then do the following:
B1: =A1
B2: =IF(LEFT(A2,1)="M",A2,B1)
C1: =IF(LEFT(A1,1)="M","",A1)
The result will have columns B and C populated with the data as you need.
If by any chance you need VBA, you may go for something like this:
Option Explicit
Public Sub TestMe()
Dim rngCell As Range
Dim colBold As New Collection
Dim colNormal As New Collection
Dim colTimes As New Collection
Dim varMy As Variant
Dim lngCounter As Long
Dim lngSum As Long
Dim lngCur As Long
Dim lngBoldCounter As Long
Dim lngMax As Long
Dim rngMyRange As Range
Set rngMyRange = Range("A1:A20")
For Each rngCell In rngMyRange
If rngCell.Font.Bold Then
colTimes.Add lngSum
colBold.Add rngCell
lngCounter = 0
Else
lngSum = lngSum + 1
lngCounter = lngCounter + 1
colNormal.Add rngCell
End If
Next rngCell
lngCounter = 1
lngSum = 0
lngBoldCounter = 1
lngMax = colBold.Count + 1
rngMyRange.Offset(, 4).Clear
rngMyRange.Offset(, 5).Clear
For Each varMy In colNormal
Cells(lngCounter, 5) = varMy
Cells(lngCounter, 6) = colBold(lngBoldCounter)
If lngCounter < colNormal.Count Then
If colTimes(lngBoldCounter + 1) <= lngCounter Then
lngBoldCounter = lngBoldCounter + 1
If lngBoldCounter = lngMax Then
lngBoldCounter = lngBoldCounter - 1
Debug.Print varMy.Address
End If
End If
End If
lngCounter = lngCounter + 1
Next varMy
End Sub
The code is not optimal as I have made it on the go, but it takes the bold into account and produces the columns 5 and 6, concerning them.

Resources