Introduction
Linked Lists are used in many different ways to dynamically store and retrieve data in a logical order as opposed to storing the data based upon the physical order. Linked Lists can be very useful in certain situations and yet very frustrating to build if the base language does not directly support certain data types. Here is a quick version of a node-based Linked List written in VBA and Excel 2016 (for those of us confined to a scripting language).
Background
This is the "how" as opposed to the "why" of programming. "Why" should be reserved for chat-rooms and such. VBA does not directly support pointers while also passing all data from and to functions, subs, properties, etc. with pointers unless specified otherwise with the keyword ByVal
. Using ByRef
or omitting the keyword in the passing of variables is passing by pointers. Yet, VBA does not give easy access to said pointers. There are ways (LongPTR
and memcopy
) to see the reference pointer as a long and change the variable or pointer, but that is a more advanced answer to the problem of Linked Lists and VBA that I did not want to spend too much time on. This answer is basic and uses a node-type class with simple dialog to follow.
Using the Code
There are three classes and one testing module included with this code. Remember to name each class exactly as it appears or it will not compile. Please download the example to see how the testing module fully uses all elements of the LinkedList_CLS
.
How to declare the classes in the Test_Module
:
Option Explicit
Sub Test()
Dim oHelper As Helper: Set oHelper = New Helper
On Error GoTo Err
Dim oLinkedList As LinkedList_CLS: Set oLinkedList = New LinkedList_CLS
Dim running As Boolean: running = True
Dim wb As Workbook: Set wb = ThisWorkbook
Dim ws As Worksheet: Set ws = wb.Sheets("OutPut")
Dim intIn As Integer: Dim answerIn As Variant
This is a simple Helpe
r class that speeds things up:
Option Explicit
Private Sub Class_Initialize()
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
End Sub
Private Sub Class_Terminate()
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
End Sub
Here is the Node_CLS
class which contains the data and the pointer to the next node:
Option Explicit
Public data As Integer
Public nextNode As Node_CLS
Here is the LinkedList_CLS
broken down by each sub, function, or property:
Option Explicit
Private head As Node_CLS
Public Property Let push(pushData As Integer)
Dim pushNode As Node_CLS: Set pushNode = New Node_CLS
pushNode.data = pushData
Set pushNode.nextNode = head
Set head = pushNode
End Property
Public Property Let append(appendData As Integer)
Dim appendNode As Node_CLS: Set appendNode = New Node_CLS
appendNode.data = appendData
If head Is Nothing Then
Set head = appendNode
Exit Property
Else
Dim last As Node_CLS: Set last = head
Do Until last.nextNode Is Nothing
Set last = last.nextNode
Loop
Set last.nextNode = appendNode
End If
End Property
Public Property Get getLength() As Integer
getLength = get_Length(head)
End Property
Public Property Let remove(removeData As Integer)
Dim removeNode As Node_CLS: Set removeNode = head
Dim prevNode As Node_CLS
Do Until removeNode Is Nothing
If removeNode.data = removeData Then
If removeNode Is head Then
Set head = removeNode.nextNode
Else
Set prevNode.nextNode = removeNode.nextNode
End If
Exit Property
End If
Set prevNode = removeNode
Set removeNode = removeNode.nextNode
Loop
End Property
Public Property Get exists(dataExists As Integer) As Boolean
Dim existsNode As Node_CLS: Set existsNode = head
Do Until existsNode Is Nothing
If existsNode.data = dataExists Then
exists = True
Exit Property
End If
Set existsNode = existsNode.nextNode
Loop
End Property
Public Property Get pos(dataPos As Integer) As Integer
If Not exists(dataPos) Then: pos = -1: Exit Property
Dim posNode As Node_CLS: Set posNode = head
Do Until posNode Is Nothing
If posNode.data = dataPos Then
Exit Property
Else
Set posNode = posNode.nextNode
pos = pos + 1
End If
Loop
End Property
Public Property Get isEmpty() As Boolean
If head Is Nothing Then
isEmpty = True
End If
End Property
Public Property Get getNth(nth As Integer) As Integer
Dim nthNode As Node_CLS: Set nthNode = head
Dim nthCount As Integer
Do Until nthNode Is Nothing
If nthCount + 1 = nth Then
getNth = nthNode.data: Exit Property
Else
nthCount = nthCount + 1: Set nthNode = nthNode.nextNode
End If
Loop
getNth = -1
End Property
Public Property Get getNthFromLast(nthFromLast As Integer) As Integer
Dim nthCount As Integer: nthCount = get_Length(head)
Dim nthNode As Node_CLS: Set nthNode = head
Dim i As Integer
If nthCount >= nthFromLast Then
For i = 0 To nthCount - nthFromLast - 1
Set nthNode = nthNode.nextNode
Next i
getNthFromLast = nthNode.data: Exit Property
End If
getNthFromLast = -1
End Property
Public Property Get middle() As Integer
If Not isEmpty() And Not head.nextNode Is Nothing Then
Dim mid As Integer: mid = (get_Length(head) / 2)
Dim midNode As Node_CLS: Set midNode = head
Do Until mid - 1 = 0
Set midNode = midNode.nextNode
mid = mid - 1
Loop
middle = midNode.data: Exit Property
End If
middle = -1
End Property
Public Property Get countTotal(dataCount As Integer) As Integer
Dim countNode As Node_CLS: Set countNode = head
Do Until countNode Is Nothing
If dataCount = countNode.data Then
countTotal = countTotal + 1
End If
Set countNode = countNode.nextNode
Loop
End Property
Public Property Let printNodes(MyWS As Worksheet)
MyWS.Range("F:F").ClearContents
Dim rowCounter As Integer: rowCounter = 1
Dim nodeToPrint As Node_CLS: Set nodeToPrint = head
Do Until nodeToPrint Is Nothing
MyWS.Cells(rowCounter, 6).Value = nodeToPrint.data
rowCounter = rowCounter + 1
Set nodeToPrint = nodeToPrint.nextNode
Loop
End Property
Public Sub mergeSort()
If isEmpty Then: Exit Sub
If head.nextNode Is Nothing Then: Exit Sub
Set head = merge(head)
End Sub
Public Sub deleteList()
Do Until head Is Nothing
Set head = head.nextNode
Loop
Set head = Nothing
End Sub
Private Property Get merge(mergeNode As Node_CLS) As Node_CLS
Dim oldHead As Node_CLS: Set oldHead = mergeNode
Dim mid As Integer: mid = (get_Length(mergeNode) / 2) - 1
If mergeNode.nextNode Is Nothing Then: Set merge = mergeNode: Exit Property
Do Until mid = 0
Set oldHead = oldHead.nextNode
mid = mid - 1
Loop
Dim newHead As Node_CLS: Set newHead = oldHead.nextNode
Set oldHead.nextNode = Nothing
Set oldHead = mergeNode
Dim front As Node_CLS: Set front = merge(oldHead)
Dim back As Node_CLS: Set back = merge(newHead)
Set merge = mergeList(front, back)
End Property
Private Property Get mergeList(a As Node_CLS, b As Node_CLS) As Node_CLS
Dim resultNode As Node_CLS
If a Is Nothing Then: Set mergeList = b: Exit Property
If b Is Nothing Then: Set mergeList = a: Exit Property
If a.data > b.data Then
Set resultNode = b
Set resultNode.nextNode = mergeList(a, b.nextNode)
Else
Set resultNode = a
Set resultNode.nextNode = mergeList(a.nextNode, b)
End If
Set mergeList = resultNode
End Property
Private Property Get get_Length(getLengthNode As Node_CLS) As Integer
Dim lengthNode As Node_CLS: Set lengthNode = getLengthNode
Do Until lengthNode Is Nothing
Set lengthNode = lengthNode.nextNode
get_Length = get_Length + 1
Loop
End Property
Private Sub Class_Initialize()
End Sub
Private Sub Class_Terminate()
Set head = Nothing
End Sub
Points of Interest
Destroying large lists (say 10,000 elements on up) has proven time consuming and sometimes problematic. VBA is just not that easy to manage the stack with. However, smaller lists seems to work fine with no errors that I have found.
History
- 3/10/2019: 1st draft - Version 1.0.0 stable on Excel 2016