Option Compare Database
Option Explicit

Dim ws As DAO.Workspace
Dim rsWrite As DAO.Recordset
Dim stringArray() As String
Dim randomStringCount As Long
Dim sectionTimes(1 To 4) As Date

Private Sub Form_Load()
    
    CreateStringArray
    SetupSortedTable
    
    ClearForm

End Sub

Private Sub btnCreateStrings_Click()

    If rsWrite Is Nothing Then
        SetupSortedTable ' if rerunning without closing
    End If
    
    ClearForm
    GenerateRandomStrings
End Sub

Private Sub btnSortStrings_Click()
    
    ClearForm
    
    CreateStringArray
    
    If rsWrite Is Nothing Then SetupSortedTable ' if rerunning without closing
    
    Dim startRun As Date
    Dim endRun As Date
    
    startRun = Now
    txtStartTime.SetFocus: txtStartTime.Text = Format(CDate(startRun), "mm/dd/yy hh:MM:ss")
        
    sectionTimes(1) = Now
    lblBusy.Caption = "Geting Strings": DoEvents
    GetStrings
    
    sectionTimes(2) = Now
    lblBusy.Caption = "Sorting Strings": DoEvents
    Quicksort stringArray, LBound(stringArray), UBound(stringArray)
    
    sectionTimes(3) = Now
    lblBusy.Caption = "Removing Duplicates": DoEvents
    RemoveDuplicates
    
    sectionTimes(4) = Now
    lblBusy.Caption = "Writing Sorted": DoEvents
    WriteTheStrings
    
    endRun = Now
    lblBusy.Caption = "Done"
        
    txtEndTime.SetFocus: txtEndTime.Text = Format(CDate(endRun), "mm/dd/yy hh:MM:ss")
    txtDuration.SetFocus: txtDuration.Text = DateDiff("s", startRun, endRun)
    txtCount.SetFocus: Me.txtCount.Text = UBound(stringArray) - 1
    
    Debug.Print "Get Strings start " & Format(sectionTimes(1), "h:mm:ss")
    Debug.Print "Sort Strings start " & Format(sectionTimes(2), "h:mm:ss")
    Debug.Print "Remove Duplicates start " & Format(sectionTimes(3), "h:mm:ss")
    Debug.Print "Write Strings start " & Format(sectionTimes(4), "h:mm:ss")
    
End Sub

Private Sub CreateStringArray()

    ' create string array for sorting
    On Error Resume Next
        ' if there is already a tblRandom table get the count of records in it
        Dim rsCount As DAO.Recordset
        Set rsCount = CurrentDb.OpenRecordset("select count(TextString) from tblRandom", dbOpenDynaset)
        randomStringCount = rsCount(0)
        lblCreatedStringsCount.Caption = "There are " & randomStringCount & " random strings"
        rsCount.Close
        Set rsCount = Nothing
        
        ReDim stringArray((randomStringCount - 1))
    On Error GoTo 0

End Sub

Private Sub ClearForm()

    txtStartTime.SetFocus: txtStartTime.Text = ""
    txtEndTime.SetFocus: txtEndTime.Text = ""
    txtDuration.SetFocus: txtDuration.Text = ""
    txtCount.SetFocus: Me.txtCount.Text = ""

End Sub

Private Sub SetupSortedTable()

    ' set up the table
    Set ws = DBEngine.Workspaces(0)

    On Error Resume Next
    ' create the tblPrimes table
    CurrentDb.Execute "drop table tblSorted"
    CurrentDb.Execute "Create Table tblSorted (ID AUTOINCREMENT PRIMARY KEY, SortedString TEXT(150));"

    On Error GoTo 0
    ' create the recordset
    Set rsWrite = CurrentDb.OpenRecordset("tblSorted", dbOpenTable)   ' open the table to be written to
    
    DoEvents
End Sub

Private Sub GetStrings()

    ' get the strings into an array
    Dim rs As DAO.Recordset
    Dim arrayIndex As Long
    
    Set rs = CurrentDb.OpenRecordset("Select * from tblRandom", dbOpenForwardOnly)

    arrayIndex = 0
    Do While Not rs.EOF
        stringArray(arrayIndex) = rs(1)
        rs.MoveNext
        arrayIndex = arrayIndex + 1
    Loop
    
    rs.Close
    Set rs = Nothing
    
End Sub

Private Sub RemoveDuplicates()

    Dim arrayIndex As Long
    Dim removedCount As Long
    
    removedCount = 0
    For arrayIndex = UBound(stringArray) - 1 To 1 Step -1
        If stringArray(arrayIndex) = stringArray(arrayIndex - 1) Then
            ' this is a duplicate of the one above it, make this blank
            stringArray(arrayIndex) = ""
            removedCount = removedCount + 1
        End If
    
    Next arrayIndex
    
    If removedCount > 0 Then
        ' duplicates were found and blanked out - remove the blanks
        Dim tempArray() As String
        Dim tempArrayIndex As Long
        
        ' use a temp array to hold the new array with no duplicates
        ReDim tempArray((UBound(stringArray) - removedCount))
        
        For arrayIndex = 0 To UBound(stringArray) - 1
            If Len(stringArray(arrayIndex)) > 0 Then
                tempArray(tempArrayIndex) = stringArray(arrayIndex)
                tempArrayIndex = tempArrayIndex + 1
            End If
        
        Next arrayIndex
        
        ' assign it back to the main array variable while condencing the array
        ReDim stringArray((UBound(stringArray) - removedCount))
        stringArray = tempArray
       
    End If
   
End Sub

Sub Quicksort(vArray As Variant, arrLbound As Long, arrUbound As Long)

    ' Sorts a one-dimensional VBA array from smallest to largest
    ' using a very fast quicksort algorithm variant.
    ' from https://wellsr.com/vba/2018/excel/vba-quicksort-macro-to-sort-arrays-fast/
    
    Dim pivotVal As Variant
    Dim vSwap As Variant
    Dim tmpLow As Long
    Dim tmpHi As Long
    
    tmpLow = arrLbound
    tmpHi = arrUbound
    pivotVal = vArray((arrLbound + arrUbound) \ 2)
     
    While (tmpLow <= tmpHi)
       While (vArray(tmpLow) < pivotVal And tmpLow < arrUbound)
          tmpLow = tmpLow + 1
       Wend
      
       While (pivotVal < vArray(tmpHi) And tmpHi > arrLbound)
          tmpHi = tmpHi - 1
       Wend
     
       If (tmpLow <= tmpHi) Then
          vSwap = vArray(tmpLow)
          vArray(tmpLow) = vArray(tmpHi)
          vArray(tmpHi) = vSwap
          tmpLow = tmpLow + 1
          tmpHi = tmpHi - 1
       End If
    Wend
     
    If (arrLbound < tmpHi) Then Quicksort vArray, arrLbound, tmpHi
    If (tmpLow < arrUbound) Then Quicksort vArray, tmpLow, arrUbound
      
End Sub

Private Sub WriteTheStrings()

    Dim arrayIndex As Long
    
    ws.BeginTrans
    
    ' loop the sorted array and write it to a table
    For arrayIndex = 0 To UBound(stringArray) - 1
        
        rsWrite.AddNew
        rsWrite("SortedString") = stringArray(arrayIndex)
        rsWrite.Update
        
    Next arrayIndex

    ws.CommitTrans
    
    rsWrite.Close
    Set rsWrite = Nothing
    
    ws.Close
    Set ws = Nothing

End Sub

Public Sub GenerateRandomStrings()

    ' provided string generation code
    Dim db As DAO.Database
    Dim tdf As DAO.TableDef
    Dim rst As DAO.Recordset
    Dim varListSize As Variant
    Dim nListSize As Long
    Dim nRecord As Long
    Dim nChar As Integer
    Dim sChar As String
    Dim sText As String
    Dim nRand As Integer
    Set db = CurrentDb
    
    ' Delete tblRandom if it exists
    On Error Resume Next
    db.TableDefs.Delete "tblRandom"
    On Error GoTo 0
    DoEvents
    
    ' Re-create tblRandom and add the required fields
    Set tdf = db.CreateTableDef("tblRandom")
    DoEvents
    With tdf
        .Fields.Append .CreateField("ID", dbLong)
        .Fields("ID").Attributes = dbAutoIncrField
        .Fields.Append .CreateField("TextString", dbText, 255)
    End With
    db.TableDefs.Append tdf
    
    Set rst = db.OpenRecordset("tblRandom", dbOpenDynaset)
    
    ' get the desired list size
    varListSize = InputBox("Number of Elements", "Enter Number", 1000000) ' default 1,000,000 but can be changed
    If varListSize = "" Or varListSize <= 0 Or Not IsNumeric(varListSize) Then
        MsgBox "Exiting", vbOKOnly + vbInformation, "Invalid Selection"
        Exit Sub
    End If
    
    nListSize = Fix(varListSize)
    ' generate the random list
    Call Rnd(-1)     ' required along with Randomize
    Randomize 12345  ' set seed based on nRecord to ensure that the list of generated strings is always the same
    
    Me.lblCreatedStringsCount.Caption = "Creating " & nListSize & " strings": DoEvents
    
    ws.BeginTrans
    
    ' Outer loop
    For nRecord = 1 To nListSize
        sText = ""
        
        ' generate 8 random values between 0 and 26
        For nChar = 1 To 8
        
            nRand = Int(27 * Rnd())  ' Random number between 0 and 26
            
            If nChar = 1 And nRand = 0 Then
                nRand = (nRecord Mod 26) + 1  ' don't allow the first character to be a space
            End If
            
            ' Convert to character
            If nRand = 0 Then
                sChar = " "  ' if nRand = 0 then a space is generated
                Exit For     ' stop once a space is encountered
            Else
                sChar = Chr(96 + nRand)  ' Chr(97) = "a", Chr(98) = "b", etc.
            End If
            
            ' Build the string
            sText = sText & sChar
        Next nChar
        
        ' Write to the table
        rst.AddNew
        rst!TextString = sText
        rst.Update
        
    Next nRecord
    
    ws.CommitTrans
    
    rst.Close
    Set rst = Nothing
    Set tdf = Nothing
    Set db = Nothing
    
    Me.lblCreatedStringsCount.Caption = "Table of " & Format(nListSize, "#,##0") & " random strings created"
    
    randomStringCount = nListSize ' update the array size required
End Sub


