Randomize a list in Microsoft Word - vba
I am a teacher and I have been making a number of multiple choice tests for students using Microsoft Word. Is there a way for me to automatically shuffle the questions so that I can have multiple versions of the test without needing to copy and paste the questions around my test?
Looking online I found a one solution posted by Steve Yandl in which he used macro after putting each question on a separate row in a table. I am trying to get his macro to work but it has and error. I know next to nothing about coding, so I am stuck. Here is his code:
Sub ShuffleQuestions()
Dim Tmax As Integer
Dim strCell As String
Dim strQ As Variant
Dim strText As String
Dim I As Integer
Dim Z As Integer
Dim intQsLeft As Integer
Dim rndQ As Integer
Dim Q As Integer
Dim vArray As Variant
Dim strNew As String
Set objDict = CreateObject("Scripting.Dictionary")
Tmax = ThisDocument.Tables(1).Rows.Count
For I = 1 To Tmax
strCell = ThisDocument.Tables(1).Cell(I, 1).Range.Text
strQ = Left(strCell, Len(strCell) - 1)
objDict.Add strQ, strQ
Next I
ReDim arrQs(I - 1)
intQsLeft = I - 2
Z = 0
Do While intQsLeft = 0
Randomize
rndQ = Int((intQsLeft + 1) * Rnd)
intQsLeft = intQsLeft - 1
vArray = objDict.Items
strText = vArray(rndQ)
arrQs(Z) = strText
Z = Z + 1
objDict.Remove strText
Loop
For Q = 1 To Tmax
strNew = arrQs(Q - 1)
strNew = Left(strNew, Len(strNew) - 1)
ThisDocument.Tables(1).Cell(Q, 1).Range.Text = strNew
Next Q
End Sub
The error message I get says "run time error 5941 the requested member of the collection does not exist"
When I choose the 'Debug' button it brings me to the line of code in the macro that says "Tmax = ThisDocument.Tables(1).Rows.Count"
Ultimately I just want to reorder the questions, but I would be delighted if there was also a way to reorder my multiple choice options for each question.
Does your document have a table?
Where did you put the sub (ShuffleQuestions)?
Are you sure you added it to your document and didn't add it to the document template (probably normal).
If, after running the code, reaching the error and clicking debug, you highlight ThisDocument.Tables, right clicking on the highlighted text and select "Add Watch" from the popup menu you should be able to see if ThisDocument.Tables contains any data.
I suspect it will be empty. It will be empty if:
You haven't added a table to your document
You have added the sub to normal.dot in which case ThisDocument will refer to the normal template and not the document you are actually editing.
So, the solution is either:
Make sure your sub is in the document you are editing (and not the document template)
That you have a table in your document.
There are also some programming errors in the sub ShuffleQuestions (e.g. Do While intQsLeft = 0 should be something like Do While intQsLeft > 0).
The following code works (and is a lot simpler):
Sub ShuffleQuestions()
Dim numberOfRows As Integer
Dim currentRowText As String
Dim I As Integer
Dim doc As Document
Set doc = ActiveDocument
'Find the number of rows in the first table of the document
numberOfRows = doc.Tables(1).Rows.Count
'Initialise (seed) the random number generator
Randomize
'For each row in the table
For I = 1 To numberOfRows
'Find a new row number (any row in the table)
newRow = Int(numberOfRows * Rnd + 1)
'Unless we're not moving to a new row
If newRow <> I Then
'Get the current row text
currentRowText = CleanUp(doc.Tables(1).Cell(I, 1).Range.Text)
'Overwrite the current row text with the new row text
doc.Tables(1).Cell(I, 1).Range.Text = CleanUp(doc.Tables(1).Cell(newRow, 1).Range.Text)
'Put the current row text into the new row
doc.Tables(1).Cell(newRow, 1).Range.Text = currentRowText
End If
Next I
End Sub
Function CleanUp(value As String) As String
'Remove control characters from the end of the string (the cell text has a 'BELL' character and CR at the end)
While (Len(value) > 0 And Asc(Right(value, 1)) < 32)
value = Left(value, Len(value) - 1)
Wend
CleanUp = value
End Function
For those who wants to randomize all the paragraphs in a document.
To make it work, put your cursor at the end of your document with no selections.
Sub ran_para()
n = ActiveDocument.Paragraphs.Count
ReDim a(1 To 2, 1 To n)
Randomize
For i = 1 To n
a(1, i) = Rnd
a(2, i) = i
Next
For i = 1 To n - 1
For j = i + 1 To n
If a(1, j) > a(1, i) Then
t = a(2, i)
a(2, i) = a(2, j)
a(2, j) = t
End If
Next
Next
'Documents.Add
For i = 1 To n
Set p = ActiveDocument.Paragraphs.Add
p.Range.Text = ActiveDocument.Paragraphs(a(2, i)).Range.Text
Next
End Sub
Related
Manipulating Collections and Arrays in Excel VBA to accommodate missing values and error handling
I did not know how to explain the question so I will attach images for explaining my situation. Here is the view of my Excel Sheet: My Excel Sheet The highlighted cells contain multiple values called ID's and are associated with respective Versions in the columns beside them. I use the following macro (details with great explanation here) to split these values into multiple rows in the same sheet. Option Explicit Private Const ID_IDX As Long = 0 Private Const VER_IDX As Long = 1 Private Const RNG_IDX As Long = 2 Private Sub RunMe() Dim data As Variant, cols As Variant, items As Variant Dim r As Long, c As Long, i As Long, n As Long Dim ids() As String, vers() As String Dim addItems As Collection, concatItems As Collection Dim dataRng As Range, rng As Range Dim writeID() As Variant, writeVer() As Variant, writeConcat() As Variant Dim dataStartRow As Long On Error Resume Next 'Define the range we're interested in and read into an array. With Sheet1 'adjust for your worksheet object Set dataRng = Application.InputBox(prompt:="Select the Range of cells:", Type:=8) End With data = dataRng.Value2 dataStartRow = 2 'Find the two target columns cols = AcquireIdAndVerCol(data, 3, 8) If IsEmpty(cols) Then MsgBox "Unable to find Id and Ver columns." Exit Sub End If With dataRng 'Add a column next to the version number column. .Columns(cols(VER_IDX)).Offset(, 1).Insert Shift:=xlShiftToRight, CopyOrigin:=xlFormatFromLeftOrAbove 'Add a column to our range. 'This is to cover the case that the rightmost column is the version number column. Set dataRng = .Resize(, .Columns.Count + 1) End With 'Find the rows that need to be split and concatenate the target strings. Set addItems = New Collection Set concatItems = New Collection For r = dataStartRow To UBound(data, 1) ids = Split(data(r, cols(ID_IDX)), vbLf) vers = Split(data(r, cols(VER_IDX)), vbLf) n = IIf(UBound(ids) >= UBound(vers), UBound(ids), UBound(vers)) If n = 0 Then 'it's just one line of text. 'Add concatenated text to list. concatItems.Add data(r, cols(ID_IDX)) & " " & data(r, cols(VER_IDX)) ElseIf n > 0 Then 'it's multiple lines of text. 'Transpose the id array. ReDim writeID(1 To UBound(ids) + 1, 1 To 1) For i = 0 To UBound(ids) writeID(i + 1, 1) = ids(i) Next 'Transpose the version array. ReDim writeVer(1 To UBound(vers) + 1, 1 To 1) For i = 0 To UBound(ids) writeVer(i + 1, 1) = vers(i) Next 'Add concatenated text to list. For i = 0 To n concatItems.Add (IIf(UBound(ids) <= n And UBound(vers) <= n, ids(i) & " " & vers(i), Empty)) Next 'Add the range to be split to the collection. addItems.Add Array(writeID, writeVer, dataRng.Rows(r + 1).Resize(n)) Else 'it's an empty cell 'Add empty item to concatenated list in order to keep alignment. concatItems.Add Empty End If Next Application.ScreenUpdating = False 'Split the ranges in the list. If addItems.Count > 0 Then For Each items In addItems 'Add the rows. With items(RNG_IDX) .Insert Shift:=xlDown, CopyOrigin:=xlFormatFromLeftOrAbove Set rng = .Offset(-.Rows.Count - 1).Resize(.Rows.Count + 1) 'Note: format your rng Range obect as desired here. End With 'Write the id and version values. rng.Columns(cols(ID_IDX)).Value = items(ID_IDX) rng.Columns(cols(VER_IDX)).Value = items(VER_IDX) Next End If 'Write the concatenated values. If concatItems.Count > 0 Then ReDim writeConcat(1 To concatItems.Count + dataStartRow - 1, 1 To 1) 'Header to array. writeConcat(1, 1) = "Concat values" 'Values from the collection to array. i = dataStartRow For Each items In concatItems writeConcat(i, 1) = items i = i + 1 Next 'Output array to range. With dataRng.Columns(cols(VER_IDX) + 1) .Value = writeConcat .AutoFit End With End If Application.ScreenUpdating = True End Sub Private Function AcquireIdAndVerCol(data As Variant, minCol As Long, maxCol As Long) As Variant Dim result(1) As Long Dim r As Long, c As Long, i As Long Dim items() As String 'Check we're not operating outside bounds of data array. If minCol < LBound(data, 2) Then minCol = LBound(data, 2) If minCol > UBound(data, 2) Then minCol = UBound(data, 2) If maxCol < LBound(data, 2) Then maxCol = LBound(data, 2) If maxCol > UBound(data, 2) Then maxCol = UBound(data, 2) 'Loop through data to find the two columns. 'Once found, leave the function. For r = 1 To UBound(data, 1) For c = minCol To maxCol items = Split(data(r, c), vbLf) For i = 0 To UBound(items) If result(ID_IDX) = 0 Then If IsDocId(items(i)) Then result(ID_IDX) = c If result(VER_IDX) = 0 Then Exit For Else AcquireIdAndVerCol = result Exit Function End If End If End If If result(VER_IDX) = 0 Then If IsDocVer(items(i)) Then result(VER_IDX) = c If result(ID_IDX) = 0 Then Exit For Else AcquireIdAndVerCol = result Exit Function End If End If End If Next Next Next End Function Private Function IsDocId(val As String) As Boolean Dim n As Long n = TryClng(val) IsDocId = (n > 9999 And n <= 999999999) End Function Private Function IsDocVer(val As String) As Boolean Dim n As Long, m As Long Dim items() As String items = Split(val, ".") If UBound(items) <> 1 Then Exit Function n = TryClng(items(0)) m = TryClng(items(1)) IsDocVer = (n > 0 And n <= 99) And (m >= 0 And m <= 9) End Function '------------------------------------------------------------------- 'Converts a variant to a Long or returns a fail value as a Long 'if the conversion failed. '------------------------------------------------------------------- Private Function TryClng(expr As Variant, Optional fail As Long = -1) As Long Dim n As Long n = fail On Error Resume Next n = CLng(expr) On Error GoTo 0 TryClng = n End Function It gives the following output with an addition column named, Concat Values, which contains combined values of Id's and corresponding Versions: Output Problem: It works flawlessly if all the ID's have corresponding Versions specified in the sheet separately as I mentioned above. However in cases, where there is only one Version number, and it's bound to 4 or more Id's, i.e. Same Version number is applicable for all the ID's, like such: The output in the column Concat Values gets disoriented because we are using an array to output the Concat Values and the array is not accommodating the missing Versions for corresponding Id's. It looks like this: Dislocated row values I am trying to learn and figure out a way to update the collection and the array with new Concat Values before Outputting it to the column, so that each Concat Value gets placed in their corresponding ID and Version location. I hope that it makes sense. Please let me know for more clarification. EDIT: I will try and list all the possible Cases and Expected Output, including the worst case scenarios: Here is the link to my excel sheet. Usual Scenarios Number of Id's = Number of Versions (Works perfectly, Concat Values get aligned in corresponding rows in the columns) Multiple Id's - Single Version (In such cases, the Version # applicable to all the ID's is same i.e. one Version should be applied to all the ID's.) Issue: The Macro does the task of splitting the columns into rows, except the part where Concat values get misaligned. Worst Case Scenarios Multiple Id's - Multiple Versions, but less than total #ID's (In such cases, Versions should align to the topmost ID's and fill the ID's below with blanks) Issue: The Macro does the task of splitting the columns into rows, except the part where Concat values get misaligned. Here 4 ID's have been given only 3 Versions, so Top 3 ID's are assigned 3 Versions and the 4th ID has no Version linked to it. Similarly, Here 4 ID's have been given only 2 Versions, so Top 2 ID's are assigned 2 Versions and the 3rd and 4th ID's have no Version linked to them. Multiple Id's - No Version (In such cases, columns should split into rows based on #ID's and corresponding Version rows should be filled with blanks) Issue: The Macro does the task of splitting the columns into rows, except the part where Concat values get misaligned.
The complexity of the solution will depend on the complexity and variety of 'special cases'. Given your scenarios, it seems as if you could just take the last of the given versions and, for any versions missing below that line, just use that last used version. When I gave my first answer, I anticipated this kind of issue, so changes to the code are trivial. Firstly add an additional declaration in the RunMe Sub: Dim curVer As String and then you just need to adjust the ElseIf n > 0 case. Replace the code with this: ElseIf n > 0 Then 'it's multiple lines of text. 'Resize the output arrays to max ('n') ReDim writeID(1 To n + 1, 1 To 1) ReDim writeVer(1 To n + 1, 1 To 1) 'Loop through the arrays to align id and versions. For i = 0 To n If i <= UBound(ids) Then writeID(i + 1, 1) = ids(i) End If If i <= UBound(vers) Then curVer = vers(i) End If writeVer(i + 1, 1) = curVer Next 'Add concatenated text to list. For i = 0 To n concatItems.Add writeID(i + 1, 1) & " " & writeVer(i + 1, 1) Next 'Add the range to be split to the collection. addItems.Add Array(writeID, writeVer, dataRng.Rows(r + 1).Resize(n))
Too much code for me to read but I came up with my solution if I understood you problem correctly. I guess it could be a good solution if you modify it. With my code it will be easier to produce a new table instead of adding rows I guess. Then you could just add the formatting which should be very easy. Sub Test() Dim xRange As Range Dim xArrRange() As Variant Dim xNewArrRange() As Variant Dim xNewArrRangeResize() As Variant Dim xNumberColumns As Long Dim i As Long Dim j As Long Dim k As Long Dim l As Long Dim ii As Long Dim jj As Long Set xRange = Range("A2:C5") xNumberColumns = 3 xArrRange = xRange.Value2 ReDim xNewArrRange(xRange.Rows.Count + 10, xNumberColumns) ' "xNumberColumns - 1" to have the number of columns ' "xNumberColumns" is one more For i = LBound(xArrRange, 1) To UBound(xArrRange, 1) Dim xTempArrVer As Variant Dim xTempArrID As Variant xTempArrVer = Split(xArrRange(i, 3), vbLf) If UBound(xTempArrVer) = -1 Then ' If there are no version, initialize it with "" ReDim xTempArrVer(0) xTempArrVer(0) = "" End If xTempArrID = Split(xArrRange(i, 2), vbLf) For j = LBound(xTempArrID, 1) To UBound(xTempArrID, 1) If j > UBound(xTempArrVer, 1) Then l = UBound(xTempArrVer, 1) Else l = j End If xNewArrRange(k, 0) = xArrRange(i, 1) xNewArrRange(k, 1) = xTempArrID(j) xNewArrRange(k, 2) = xTempArrVer(l) If xTempArrVer(l) <> "" Then xNewArrRange(k, 3) = xTempArrID(j) & " " & xTempArrVer(l) Else xNewArrRange(k, 3) = xTempArrID(j) End If k = k + 1 If k + 1 > UBound(xNewArrRange, 1) Then ReDim Preserve xNewArrRange(UBound(xNewArrRange, 1) + 30, xNumberColumns) End If Next j Next i ReDim xNewArrRangeResize(k - 1, xNumberColumns) ' "xNumberColumns - 1" to have the number of columns ' "xNumberColumns" is one more For ii = LBound(xNewArrRangeResize, 1) To UBound(xNewArrRangeResize, 1) For jj = LBound(xNewArrRangeResize, 2) To UBound(xNewArrRangeResize, 2) xNewArrRangeResize(ii, jj) = xNewArrRange(ii, jj) Next jj Next ii Range(Cells(2, 6), Cells(UBound(xNewArrRangeResize, 1) + 1, 6 + UBound(xNewArrRangeResize, 2))).Value2 = xNewArrRangeResize Debug.Print "Finish" End Sub This code produces this: If your code produces good number of rows for each id etc, the most lazy solution would be just to populate columns of your table with part of my array which is produced at the end. Edit: I see there is something missing but that is because I calculated wrongly that Range. Range(Cells(2, 6), Cells(UBound(xNewArrRangeResize, 1) + 1, 6 + UBound(xNewArrRangeResize, 2))).Value2 = xNewArrRangeResize
VBA to extract names from a list at random isn't working
I want to randomly extract 280 names from a list (dynamic range) of thousands of authors, then concatenate them into 1 string with each name separated by OR. So far I'm stuck at the first part. When I run the following code, nothing happens. Can anyone explain why? Thanks. Sub Authors() Dim InputRange As Range Dim AuthorCount As Long Dim AuthorRange As Excel.Range Set InputRange = Range("A:A") AuthorCount = Application.WorksheetFunction.CountA(InputRange) - 1 Set AuthorRange = ThisWorkbook.Worksheets("Sheet").Range("A2:A" & AuthorCount) Const nItemsToPick As Long = 280 Dim Authorlist As Variant Dim i As Long ReDim Authorlist(1 To nItemsToPick) For i = 1 To nItemsToPick Authorlist(i) = AuthorRange.Cells(Int(AuthorCount * Rnd + 1), 1) Next i End Sub
Using the below code Sub PickSample(intSampleSize As Integer, intPicks As Integer) Dim arr() As String Dim intCounter As Integer ReDim arr(intPicks) For intCounter = 0 To UBound(arr) arr(intCounter) = CStr(Int((intSampleSize - (i - 1)) * Rnd + 1) + (i - 1)) Next intCounter Debug.Print Join(arr(), ",") End Sub I ran this on the following, PickSample(10,5) and got the following, showing the duplicate possibilities, this will become less as the difference between picks and samples increases. 9,9,6,10,10,2 5,3,6,7,2,3 10,4,5,8,0,6 9,8,4,10,9,0 0,8,8,7,0,4 7,5,6,3,3,8 If your selection is 280, but the data set is only 300, dupes still arise PickSample 300,280 228,**92**,248,216,269,66,**107**,166,**107**,61,174,189,41,18,190,252,192,127,56,149,292,231,114,145,164,202,11,194,270,102,35,128,232,**107**,124,225,131,216,152,52,83,26,294,85,186,**92**,256,96,239,52,90,21,148,136,179,9,95,40,98,228,188,290,249,166,182,57,271,95,180,179,230,215,206,228,77,165,153,170,84,125,105,292,156,175,139,9,113,41,196,46,59,112,28,185,211,132,126,101,210,64,13,266,13,138,222,227,247,63,141,261,249,123,139,105,75,242,163,162,188,26,214,77,17,82,289,98,194,109,111,98,64,63,127,185,72,206,177,181,23,13,46,74,120,175,86,251,270,158,116,0,75,49,194,295,93,72,264,39,171,182,183,208,255,29,118,247,80,204,119,251,130,251,65,220,270,65,295,290,262,157,195,137,47,193,184,257,110,15,152,16,112,135,89,291,3,195,184,160,8,215,94,295,87,109,96,106,70,178,211,80,173,173,298,280,75,243,231,122,189,148,150,40,291,53,177,205,32,195,222,234,129,24,150,172,17,124,35,43,94,298,181,82,125,141,19,137,131,284,82,52,152,103,154,119,78,20,192,109,164,265,127,178,114,17,32,43,43,228,79,41,12,208,254,155,240,296,157,20,188,99,83 4,50,49,153,122,31,83,193,255,149,56,269,112,97,232,65,134,71,264,183,112,117,259,176,280,155,99,261,77,78,53,104,0,223,253,83,211,121,244,223,131,23,123,102,213,93,240,45,178,287,73,282,34,296,190,180,271,173,73,258,22,132,228,73,113,119,158,81,174,63,23,269,33,196,271,69,285,254,132,148,231,251,115,58,98,124,45,186,29,61,208,151,55,298,141,1,128,86,226,268,247,53,32,3,45,113,56,294,262,175,219,43,77,8,249,235,238,100,135,167,241,169,61,62,109,172,103,158,128,172,15,164,62,289,280,298,252,123,242,297,77,52,209,5,102,208,33,33,87,120,168,93,88,243,93,113,120,253,123,218,198,122,286,194,155,67,175,225,137,272,85,200,267,84,110,4,88,296,229,174,182,80,152,238,258,28,163,125,22,135,210,150,122,284,296,178,160,185,26,55,85,5,45,126,165,168,235,12,122,17,93,181,155,179,99,273,231,173,129,220,49,17,73,228,286,103,205,238,10,239,145,62,181,273,284,196,4,199,290,2,287,22,88,175,243,12,16,169,94,124,153,220,135,97,22,123,172,229,174,196,243,125,239,217,208,219,57,232,21,74,286,246,66,55,71,278,77,77,215,200,232 209,294,73,160,32,300,203,4,173,30,31,240,85,13,89,114,90,285,294,120,83,48,49,194,123,124,214,98,190,62,55,175,24,137,272,78,236,114,87,276,190,188,128,29,168,209,275,251,6,163,275,129,204,151,154,139,106,121,81,16,73,294,18,117,109,147,46,142,77,189,163,47,282,197,152,117,32,235,138,226,179,250,5,63,22,31,99,38,0,161,197,163,249,24,57,204,136,107,45,212,279,159,26,228,120,139,148,62,99,28,177,51,279,29,133,82,262,225,82,202,77,27,9,97,237,89,70,144,76,102,13,145,62,260,177,227,279,99,163,24,190,123,289,34,277,186,104,44,144,66,299,39,8,103,164,277,162,122,255,248,202,217,300,102,149,124,209,53,127,163,245,162,128,153,68,186,147,204,266,111,91,88,45,159,67,175,109,263,143,57,205,224,184,235,48,243,60,287,19,18,238,114,139,35,34,52,14,215,160,168,65,140,224,226,120,271,224,26,191,214,4,129,120,82,296,241,209,125,221,83,107,130,284,36,194,104,31,55,23,130,288,163,148,292,65,114,119,84,151,41,155,290,167,273,197,132,208,19,227,210,149,46,67,98,236,15,155,227,241,97,292,242,203,272,263,125,37,287,239,209,120 Using a dictionary to handle the dupes, using this code Sub PickSample(intSampleSize As Integer, intPicks As Integer) Dim dicPicked As New Scripting.Dictionary Dim arr() As String Dim intCounter As Integer Dim intPicked As Integer ReDim arr(intPicks) For intCounter = 0 To UBound(arr) RetryPick: intPicked = CStr(Int((intSampleSize - (i - 1)) * Rnd + 1) + (i - 1)) If dicPicked.Exists(CStr(intPicked)) Then GoTo RetryPick Else dicPicked.Add CStr(intPicked), 1 arr(intCounter) = intPicked End If Next intCounter Debug.Print Join(arr(), ",") dicPicked.RemoveAll Set dicPicked = Nothing End Sub Gives results, hopefully dupe free PickSample 300,280 203,125,69,114,26,208,39,219,36,174,220,113,24,74,104,282,128,112,223,205,200,147,44,143,152,162,157,300,70,54,108,177,13,276,153,91,7,168,89,145,127,12,16,257,187,229,61,213,117,214,254,171,59,242,23,51,224,52,185,165,193,189,21,296,63,173,160,280,190,232,235,141,256,56,87,98,32,5,267,195,77,120,197,82,288,68,57,136,132,182,122,15,47,48,261,96,110,258,49,105,155,86,186,97,225,80,264,140,11,46,199,230,275,19,34,83,222,66,116,294,298,259,292,271,272,84,115,101,124,43,183,71,289,291,25,188,55,158,150,216,243,92,58,0,290,148,255,149,250,167,27,233,228,265,9,299,65,283,62,88,207,240,109,179,161,178,268,278,175,139,237,234,169,297,269,281,184,262,270,164,202,279,253,295,196,212,8,274,159,75,172,163,130,38,154,73,99,247,249,263,273,67,40,20,221,138,14,33,218,286,227,251,94,166,209,156,211,37,137,90,131,111,107,2,215,85,146,100,293,204,231,285,79,53,126,60,239,260,248,78,4,217,29,64,121,226,201,210,45,206,134,17,1,192,246,3,35,191,236,93,28,41,244,287,129,277,142,118,6,81,18,135,181,241,180,103,50,252,31,95,30 44,278,132,10,232,56,146,193,284,276,236,155,79,117,102,61,119,200,229,131,138,133,235,173,204,34,7,98,3,202,167,143,130,30,126,206,13,262,221,166,174,298,111,116,39,288,263,76,47,170,89,268,154,253,52,91,217,148,12,22,83,33,77,264,85,214,55,127,279,251,101,86,230,35,172,59,198,62,286,296,220,29,191,242,271,5,54,84,297,158,38,270,231,107,95,110,57,129,9,273,53,269,68,4,234,228,211,207,70,153,151,194,179,128,169,63,142,109,145,58,186,24,245,60,87,0,17,246,225,222,218,184,258,26,161,226,247,31,144,178,223,122,88,124,137,210,293,94,99,213,190,281,80,72,104,40,6,123,290,259,254,45,78,66,227,289,261,141,65,135,8,274,69,257,203,168,196,42,248,67,73,125,37,11,287,181,92,291,238,108,212,1,118,28,216,244,164,249,240,150,46,74,277,36,189,188,255,224,195,260,15,175,267,280,49,180,27,165,50,113,243,201,237,149,205,156,199,292,136,48,71,75,285,41,81,239,209,185,266,160,176,152,171,163,100,2,32,183,16,97,19,294,187,20,282,272,157,182,121,140,106,112,265,295,51,21,256,64,241,114,162,90,252,115,25,82,103,23,299,120,197 57,241,105,1,247,289,284,72,89,68,101,225,295,242,290,5,291,217,267,87,62,80,24,106,103,38,285,197,286,300,151,222,219,254,201,113,195,245,243,15,179,98,145,192,74,118,142,109,70,58,11,4,154,277,129,115,250,202,293,163,181,168,288,268,281,112,79,49,60,175,236,23,266,186,59,167,190,187,41,228,174,157,48,231,165,253,227,171,66,176,135,238,120,258,19,110,194,164,131,296,91,206,159,255,8,189,124,148,114,13,75,121,95,272,119,214,50,117,279,213,205,133,96,196,137,173,246,218,233,77,27,264,141,184,193,263,102,83,244,210,78,127,36,63,188,42,0,152,198,271,169,298,207,111,3,158,182,282,30,226,199,17,273,297,191,166,46,144,252,55,25,26,200,86,162,237,211,299,212,287,161,12,40,45,69,216,54,125,71,47,132,93,22,20,76,126,51,262,107,229,234,257,2,39,278,235,84,37,280,208,153,251,67,136,104,28,221,149,248,276,170,140,14,269,180,16,108,34,94,43,92,52,204,65,134,85,183,7,146,143,97,270,64,259,139,160,260,223,32,81,177,33,178,185,90,292,9,232,209,265,10,88,283,147,123,99,261,240,6,138,274,122,61,203,249,155,256,44,294,116,35
this function picks nItemsToPick random elements up from arr array and returns them into an array: Function PickNRandomElementsFromArray(arr As Variant, nItemsToPick As Long) As Variant Dim arrSize As Long Dim i As Long, iPick As Long Dim temp As String arrSize = UBound(arr) '<--| count the values as the array size If nItemsToPick > arrSize Then nItemsToPick = arrSize '<--| the items to be picked cannot exceed array size For i = 1 To nItemsToPick iPick = Int((arrSize - (i - 1)) * Rnd + 1) + (i - 1) '<--| pick a random number between 'i' and 'arrSize' ' swap array elements in slots 'i' and 'iPick' temp = arr(iPick) arr(iPick) = arr(i) arr(i) = temp Next i ReDim Preserve arr(1 To nItemsToPick) '<--| resize the array to first picked items PickNRandomElementsFromArray = arr '<--| return the array End Function which you can exploit as follows: Option Explicit Sub Authors() Dim Authors As Variant, AuthorsList As Variant With ThisWorkbook.Worksheets("Sheet") '<--| reference your relevant worksheet Authors = Application.Transpose(.Range("A2", .Cells(.rows.count, 1).End(xlUp)).Value) '<--| fill 'Authors' array with its column "A" values from row 2 down to its last not empty one AuthorsList = PickNRandomElementsFromArray(Authors, 280) '<--| fill 'AuthorsList' array with a selection of 280 random elements of 'Authors' .Range("B1").Value = Join(AuthorsList, " OR ") '<--| fill cell "B1" with a string build by concatenating 'AuthorsList' values delimited with " OR " End With End Sub that can be quite contracted (and made as much less readable) to: Sub Authors() With ThisWorkbook.Worksheets("Sheet") '<--| reference your relevant worksheet .Range("B1").Value = Join(PickNRandomElementsFromArray(Application.Transpose(.Range("A2", .Cells(.rows.count, 1).End(xlUp)).Value), 280), " OR ") End With End Sub
From your comments, you seem to want to concatenate the array of strings, then put it back into excel. This, put just before the End Sub, will put it into cell B1 for example: ThisWorkbook.Worksheets("Sheet1").Range("B1").Value = Join(Authorlist, "OR")
VBA-Excel / How to randomly pick up a word from a dictionary?
Lets say I have a database of words in Sheet2; it goes from A1 to B200. I need to randomly select one of those words; and show it in Sheet1. Moreover, I need to have on blank cell between each letter of the word. Example: The randomly selected word is COLD; it has to appear like this: A1: C A3: O A5: L A7: D How can I code this?
try this code: Option Explicit Sub main() Dim word As String word = GetRandomWord(Worksheets("Sheet2").Range("A1:B200")) '<--| get content of a random cell in passed range Worksheets("Sheet1").Range("a1").Resize(2 * Len(word) - 1).Value = Application.Transpose(SeparatedChars(word)) '<--| write it down from given worksheet cell A1 down skipping every two cells End Sub Function SeparatedChars(strng As String) As Variant Dim i As Long ReDim chars(0 To Len(strng) - 1) As String '<--| size a 'String' array to the length of passed word For i = 1 To Len(strng) chars(i - 1) = Mid$(strng, i, 1) '<--| fill array elements with word single characters Next SeparatedChars = Split(Join(chars, " "), " ") '<--| return an array whose elements are those of the 'String' array and interposed spaces End Function Function GetRandomWord(rng As Range) As String Randomize GetRandomWord = rng.Cells(Int((rng.Count) * Rnd() + 1)).Text End Function
Assuming the words are written in column A of sheet2 you could do the following (part of this solution comes from here: Sub randomWord() Dim rndWordRow As Integer Dim arr() As String Dim buff() As String 'Select row between 1 and 200 randomly' rndWordRow = Int((200 - 1 + 1) * Rnd + 1) 'Write text of the randomly selected row into variable' rndWord = Sheets("Sheet2").Cells(rndWordRow, 1) 'Write letters of text into array' ReDim buff(Len(rndWord) - 1) For i = 1 To Len(rndWord) buff(i - 1) = Mid$(rndWord, i, 1) Next 'Loop through array and write letters in single cells' For i = 0 To UBound(buff) Sheets("Sheet1").Cells(i + 1, 1) = buff(i) Next i End Sub
Sub Test() Dim x As Long Dim aWord With Worksheets("Sheet1") For x = 1 To 15 aWord = getRandomWord .Cells(1, x).Resize(UBound(aWord)).value = aWord Next End With End Sub Function getRandomWord() Dim Source As Range Dim result Dim i As Integer Set Source = Worksheets("Sheet2").Range("A1:B200") i = Int((Rnd * Source.Cells.Count) + 1) result = StrConv(Source.Cells(i).Text, vbUnicode) result = Split(Left(result, Len(result) - 1), vbNullChar) getRandomWord = Application.Transpose(result) End Function
Here's a simple solution to your problem. This routine gives you a blank cell between two letters with the first letter in the first cell. R1 = Int(Rnd() * 200) R2 = Int(Rnd() * 2) anyword = Sheet2.Cells(R1, R2) x = Len(anyword) n = -1: i = 1 Do n = n + 2 Sheet1.Cells(n, 1) = Mid(anyword, i, 1) i = i + 1 Loop Until n > x * 2
excel vba add a set number of items to a collection
I have the following set of code: For loopCounter = 2 To endRow Dim item As Variant Dim lineArray() Dim myString As String myString = Cells(loopCounter, 3).Value ReDim lineArray(1 To endColumn) If Not (Left(myString, 1) = "P" Or Left(myString, 1) = "I" Or myString = "RESW" Or myString = "REPC") Then For x = 1 To endColumn lineArray(x) = dataArray(loopCounter, x) Next x itemCollection.Add lineArray End If Next loopCounter For the purpose of keeping this question short and to the point, I have a bunch of excel worksheets that contains hundreds of rows of data. To make a long story short, the code above add items from the worksheet to the collection, but it's adding every row (minus the conditions I set - if statement). However, I only want to add a fix amount of items to the collection, 15. I can't seem to figure it out and haven't found any help online. My question for help is, does anyone know how I can make it that once 15 items are added to the collection it will exit out of the loop and finish the rest of the subroutine? I purposely didn't add the whole code because it's quite lengthy and not necessary. Thank you.
Sure thing, just check on the collection.Count > 14 then exit for For loopCounter = 2 To endRow Dim item As Variant Dim lineArray() Dim myString As String myString = Cells(loopCounter, 3).Value ReDim lineArray(1 To endColumn) If Not (Left(myString, 1) = "P" Or Left(myString, 1) = "I" Or myString = "RESW" Or myString = "REPC") Then For x = 1 To endColumn lineArray(x) = dataArray(loopCounter, x) Next x itemCollection.Add lineArray if itemCollection.Count = 15 then exit for End If Next loopCounter if it would have been an array, then use if UBOUND(array) = 14 then exit for
Randomise numbers without repeating the number
My end result is to output the names in column A to column B in random order. I have been researching but cant seem to find what I need. So far I can kinda of randomise the numbers but its still giving me repeated numbers + the heading (A1). I need it to skip A1 because this is the heading\title of the column and start at A2. I assume once that is working correctly I add the randomNumber to a random name to Worksheets("Master Sheet").Cells(randomNumber, "B").Value ...something like that...? OR if there is a better way of doing this let me know. Sub Meow() Dim CountedRows As Integer Dim x As Integer Dim i As Integer Dim PreviousCell As Integer Dim randomNumber As Integer i = 1 PreviousCell = 0 CountedRows = Worksheets("Master Sheet").Range("A" & Rows.Count).End(xlUp).Row If CountedRows < 2 Then ' If its only the heading then quit and display a messagebox No_People_Error = MsgBox("No People entered or found, in column 'A' of Sheetname 'Master Sheet'", vbInformation, "Pavle Says No!") Exit Sub End If Do Until i = CountedRows randomNumber = Int((Rnd * (CountedRows - 1)) + 1) + 1 If Not PreviousCell = randomNumber Then Debug.Print randomNumber i = i + 1 End If PreviousCell = randomNumber Loop Debug.Print "EOF" End Sub
Here's a quick hack... Sub Meow() 'On Error GoTo err_error Dim CountedRows As Integer Dim x As Integer Dim i As Integer Dim PreviousCell As Integer Dim randomNumber As Integer Dim nums() As Integer PreviousCell = 0 CountedRows = Worksheets("Master Sheet").Range("A" & Rows.Count).End(xlUp).Row ReDim nums(CountedRows - 1) If CountedRows < 2 Then ' If its only the heading then quit and display a messagebox No_People_Error = MsgBox("No People entered or found, in column 'A' of Sheetname 'Master Sheet'", vbInformation, "Pavle Says No!") Exit Sub End If For i = 1 To CountedRows rand: randomNumber = randomNumbers(1, CountedRows, nums) nums(i - 1) = randomNumber Worksheets("Master Sheet").Range("B" & randomNumber) = Range("A" & i) Next i Exit Sub err_error: Debug.Print Err.Description End Sub Public Function randomNumbers(lb As Integer, ub As Integer, used As Variant) As Integer Dim r As Integer r = Int((ub - lb + 1) * Rnd + 1) For Each j In used If j = r Then r = randomNumbers(lb, ub, used) Else randomNumbers = r End If Next End Function
I've managed something similar previously using two collections. Fill one collection with the original data and leave the other collection empty. Then keep randomly picking an index in the first collection, adding the value at that index to the second collection and delete the value from the original collection. Set that to loop until the first collection is empty and the second collection will be full of a randomly sorted set of unique values from your starting list. ***Edit: I've thought about it again and you don't really need the second collection. You can pop a random value from the first collection and write it directly to the worksheet, incrementing the row each time: Sub Meow() Dim lst As New Collection Dim rndLst As New Collection Dim startRow As Integer Dim endRow As Integer Dim No_People_Error As Integer startRow = 2 endRow = Worksheets("Master Sheet").Cells(startRow, 1).End(xlDown).Row If Cells(startRow, 1).Value = "" Then ' If its only the heading then quit and display a messagebox No_People_Error = MsgBox("No People entered or found, in column 'A' of Sheetname 'Master Sheet'", vbInformation, "Pavle Says No!") Exit Sub End If ' Fill a collection with the original list Dim i As Integer For i = startRow To endRow lst.Add Cells(i, 1).Value Next i ' Create a randomized list collection ' Use i as a row counter Dim rowCounter As Integer rowCounter = startRow Dim index As Integer Do While lst.Count > 0 'Find a random index in the original collection index = Int((lst.Count - 1 + 1) * Rnd + 1) 'Place the value in the worksheet Cells(rowCounter, 2).Value = lst(index) 'Remove the value from the list lst.Remove (index) 'Increment row counter rowCounter = rowCounter + 1 Loop End Sub P.S. I hope there's an excellent story behind naming your sub Meow() :P