VBA's Collection
class is lacking so I created a basic List
class using Python's as a template. This could make future derived classes easier to implement.
The biggest features are better setting and accessing of values. But one neat-o feature is Python-style indexing.
Dim myList As ListmyList.Extend(Array(1,2,3,4,5,6))Debug.Print myList(-2) ' 5Debug.print myList.Slice(-1, 1).ToString '"[6,5,4,3,2,1]"
Note: I kept VBA's 1 offset for collections. This means that there is a hole at index 0 and will always return subscript out of range. I don't like it but this way List's will play nice with VBA's collection objects.
Private members
Option ExplicitPrivate collec As Collection ' Sole datamember
TransformIndex
: Enforces Zero Offset and Cylcing.
Private Sub TransformIndex(ByRef x As Variant) If x < 0 Then x = x + collec.Count + 1End Sub
Replace is private
; use Item
and Slice
to actually replace elements
Private Sub Replace(ByVal index As Long, ByVal element As Variant) collec.Remove index If index = collec.Count + 1 Then collec.Add element Else collec.Add element, before:=index End IfEnd Sub
Some boring stuff
Private Sub Class_Initialize() Set collec = New CollectionEnd SubPrivate Sub Class_Terminate() Set collec = NothingEnd SubPublic Property Get NewEnum() As IUnknownAttribute NewEnum.VB_UserMemId = -4 Set NewEnum = collec.[_NewEnum]End Property
Public Methods
The general pattern for these is and implementation of one action for a single element and another for a sequence of elements. e.g.Item
and then Slice
or Append
and Extend
. The only exception is removal that only implements single elements.
Accessers and Replacement
Item
and Slice
provide general access to members and allows replacement as they are not read only.
Public Property Let Item(ByVal index As Long, ByVal element As Variant)Attribute Item.VB_UserMemId = 0 TransformIndex index Replace index, elementEnd PropertyPublic Property Set Item(ByVal index As Long, ByVal element As Variant)Attribute Item.VB_UserMemId = 0 TransformIndex index Replace index, elementEnd Property
seq
is an auxiliary module for general sequence functions. For purposes of this review assume functions from it does exactly what it should do. seq.Assign(x,y)
assign's or sets x
to y
.
Public Property Get Item(ByVal index As Long) As VariantAttribute Item.VB_UserMemId = 0 TransformIndex index seq.Assign Item, collec.Item(index)End Property
Slice
: Allows for accessing sub sequences from a
to b
. Regardless of the order. You can specify a step s
to skip elements but note that s
must be a natural number. If you want to get a reversed sequence make b
less than a
not a negative s
.
Also Slice
is read-write, so it allows replacement of sub-sequences.
Public Property Get Slice(ByVal a As Long, ByVal b As Long, Optional ByVal s As Long = 1) As List TransformIndex a TransformIndex b Set Slice = New List If s < 1 Then Err.Raise "List.Slice", "Step "& s & " is not a natural number." s = IIF(a < b, s, -s) Dim i As Long For i = a To b Step s Slice.Append collec.Item(i) Next iEnd PropertyPublic Property Let Slice(ByVal a As Long, ByVal b As Long, Optional ByVal s As Long = 1, ByVal sequence As Variant) TransformIndex a TransformIndex b If s < 1 Then Err.Raise "List.Slice", "Step "& s & " is not a natural number." s = IIF(a < b, s, -s) If Abs(a - b) + 1 <> seq.Length(sequence) Then Err.Raise 9, "List.Slice", "Subscript out of Range." End If Dim i As Long: i = a Dim element As Variant For Each element In sequence Replace i, element i = i + s Next element Debug.Assert (i - s = b)End Property
Removal Methods
Public Sub Remove(ByVal index As Long) TransformIndex index collec.Remove indexEnd Sub''' List.Clear(x, y) \equiv List.Clear(y, x)Public Sub Clear(ByVal a As Long, ByVal b As Long) TransformIndex a TransformIndex b' Order of removal is irrelevant If a > b Then seq.Swap a, b Dim i As Long For i = 0 To b - a collec.Remove a Next iEnd Sub
I have been trying to work out a Added a RemoveRange
function. I will update with them later.Clear
function. Note List.Clear(x, y)
\$ \equiv \$ List.Clear(y, x)
.
Appending Methods
Public Sub Append(ByVal element As Variant) collec.Add elementEnd SubPublic Sub Extend(ByVal sequence As Variant) Dim element As Variant For Each element In sequence collec.Add element Next elementEnd Sub
Insertion Methods
Public Sub Emplace(ByVal index As Long, ByVal element As Variant) TransformIndex index collec.Add element, before:=indexEnd SubPublic Sub Insert(ByVal index As Long, ByVal sequence As Variant) TransformIndex index seq.Reverse sequence Dim element As Variant For Each element In sequence collec.Add element, before:=index Next elementEnd Sub
Auxiliary Methods
Public Property Get Count() As Long Count = collec.CountEnd PropertyPublic Function Exists(ByVal sought As Variant) As Boolean Exists = True Dim element As Variant For Each element In collec If element = sought Then Exit Function Next element Exists = FalseEnd FunctionPublic Property Get ToString() As String ToString = "["& Join(seq.ToArray(collec), ", ") & "]"End Property