Skip to content

Instantly share code, notes, and snippets.

@KubaO

KubaO/Arrays.md Secret

Last active March 22, 2026 14:57
Show Gist options
  • Select an option

  • Save KubaO/f01f62b008f937d4ce7a9f3079cb5987 to your computer and use it in GitHub Desktop.

Select an option

Save KubaO/f01f62b008f937d4ce7a9f3079cb5987 to your computer and use it in GitHub Desktop.
A description of array types in the AlgDataStruct package.

Array Types

2026-03-22 rev 7

Three generic array classes cover the full range of twinBASIC value and reference types. All three implement IArray and share an identical public API; the differences are limited to assignment semantics, equality testing, and a handful of restrictions in UDTArray.

Class Element type Assignment Equality For Each
ValueArray(Of T) Scalar value types (Long, String, Boolean, …) Property Let / = = operator Yes
ObjectArray(Of T) Reference / COM types Property Set / Set Is (pointer identity) Yes
UDTArray(Of T) UDT structs exposing Compare(other As T) As Long Property Let / = T.Compare(other) = 0 No, raises error 5

Note

Some UDT structs may not have meaningful comparison operation. Compare should then return -2, or any value other than -1, 0 or +1.

Construction

Dim a As New ValueArray(Of Long)
Dim b As New ObjectArray(Of Collection)
Dim c As New UDTArray(Of MyStruct)   ' MyStruct must expose Compare(other As MyStruct) As Long

No arguments are required. Each array starts empty with no memory allocated. LBound reports 1 until set otherwise.

Bounds

The lower bound is configurable: it defaults to 1 (matching Option Base 1) but can be set to any integer — negative, zero, or positive. UBound is always derived as LBound + Count - 1. All index-taking members use the current LBound-relative coordinate space.

Member Description
LBound& (Get/Let) Lower bound of the array. Defaults to 1. Can be set to any integer. If the array is already initialized or lb ≠ 1, calls Reserve 1 to ensure the backing array exists first. Setting LBound = 1 on an uninitialized array is a no-op.
UBound& (Get) Upper bound: always LBound + Count - 1. Returns LBound - 1 for an empty array.
SetDim(from&, to&) Set LBound and Count in one call. Equivalent to Resize(to - from + 1) followed by LBound = from. New slots are initialised to default values. Expects to ≥ from - 1.

Indexing

Assignment uses Property Let in ValueArray and UDTArray, and Property Set in ObjectArray.

Member Description
Item(index&) As T (default) Get or set the element at index. Expects LBound ≤ index ≤ UBound.
First As T Get or set the first element (Item(LBound)). Expects non-empty.
Last As T Get or set the last element (Item(UBound)). Expects non-empty.

State

Member Description
Count As Long Number of elements currently stored.
Capacity As Long Number of slots currently allocated. Returns 0 if uninitialized.
IsEmpty As Boolean True when Count = 0.

Adding elements

Member Description
Append(item As T) Append a single item.
Append(items() As T) Append all elements of a native array. No-op if null or empty.
AppendIA(items As IArray) Append all elements of an IArray. No-op if Nothing or empty. In UDTArray, items must be Nothing or a UDTArray(Of T); any other type raises error 5.
Prepend(item As T) Insert a single item at LBound.
Prepend(items() As T) Insert all elements of a native array at the front. No-op if null or empty.
PrependIA(items As IArray) Insert all elements of an IArray at the front. No-op if Nothing or empty. In UDTArray, items must be Nothing or a UDTArray(Of T); any other type raises error 5.
InsertAt(index As Long, value As T) Insert value before index (valid range LBound to UBound + 1).
InsertArrayAt(index As Long, items() As T) Insert all elements of a native array at index. No-op if null or empty. Valid range for index: LBound to UBound + 1.
InsertIAAt(index As Long, items As IArray) Insert all elements of an IArray at index. No-op if Nothing or empty. In UDTArray, items must be Nothing or a UDTArray(Of T); any other type raises error 5. Valid range for index: LBound to UBound + 1.

Removing elements

Member Description
RemoveAt(index&) Remove the element at index. Shifts subsequent elements left, then clears the freed slot via Wipe. Expects LBound ≤ index ≤ UBound.
RemoveFirst() Remove the first element. Expects non-empty.
RemoveLast() Remove the last element. Expects non-empty.
SwapRemove(index&) O(1) unordered removal: overwrites mData(index) with mData(UBound), then removes the last element by decrementing Count and wiping the freed slot. Does not preserve element order. Expects LBound ≤ index ≤ UBound.
RemoveRange(from&, count&) Remove count contiguous elements starting at from. Shifts the tail left, then wipes the freed tail slots via Wipe. Expects count ≥ 1 and from..from+count-1 within LBound..UBound.
RemoveOne(value As T) As Boolean Remove the first occurrence of value. Returns True if removed. Clears the vacated tail slots via Wipe.
RemoveAll(value As T) As Long Remove all occurrences of value by compacting in place. Returns the count removed. Clears the vacated tail slots via Wipe.
Deduplicate() As Long Remove consecutive duplicate elements in a single left-to-right pass. Each run of equal adjacent values is collapsed to its first element. Returns the number of elements removed. Clears the vacated tail slots via Wipe. Non-adjacent duplicates are unaffected; call Sort first if all duplicates should be removed.

Taking elements

These methods remove and return an element in a single call.

Member Description
TakeAt(index&) As T Remove and return the element at index. Expects LBound ≤ index ≤ UBound.
TakeFirst() As T Remove and return the first element. Expects non-empty.
TakeLast() As T Remove and return the last element. Expects non-empty.

Searching

Equality is type-specific: ValueArray uses =, ObjectArray uses Is (pointer identity), UDTArray uses T.Compare(other) = 0.

Member Description
IndexOf(value As T, from& = InvalidIndex) As Long First index of value at or after from. When from = InvalidIndex (the default), searches from LBound. Returns InvalidIndex if not found. Expects from ≥ LBound.
LastIndexOf(value As T, from& = InvalidIndex) As Long Last index of value at or before from. When from = InvalidIndex (the default), searches from UBound. Returns InvalidIndex if not found. Expects from ≤ UBound.
Contains(value As T) As Boolean True if value appears at least once.
CountOf(value As T) As Long Number of occurrences of value.
IsDefaultAt(index&) As Boolean True if the slot at index holds the default value for T: = default in ValueArray, Is Nothing in ObjectArray, T.Compare(default) = 0 in UDTArray. index is in backing-storage space and may address slots beyond UBound (up to Capacity - 1). Expects LBound ≤ index ≤ LBound+Capacity-1.
StartsWith(value As T) As Boolean True if Count > 0 and the first element equals value.
EndsWith(value As T) As Boolean True if Count > 0 and the last element equals value.

Bulk operations

Member Description
Fill(value As T) Set every element to value. No-op on an empty array.
MoveItem(from&, to&) Relocate the element at from to position to, shifting elements between them. No-op when from = to. Expects both indices to be LBound ≤ index ≤ UBound.
Swap(a&, b&) Exchange elements at positions a and b. No-op when a = b. Expects both indices to be LBound ≤ index ≤ UBound.
Rotate(n&) Right-rotate all elements by n positions: element at UBound - n + 1 moves to LBound, etc. Negative n rotates left. n is normalized modulo Count before use. No-op on an empty array or when n mod Count = 0. Implemented via three-reversal algorithm.
Reverse(from&, to&) Reverse the sub-range [from, to] in-place by swapping elements from both ends toward the middle. No-op when from ≥ to. Expects indices to be within LBound..UBound.
Move(first&, last&, by&) Shift the contiguous block [first..last] by by positions using RtlMoveMemory. Positive by shifts right; negative by shifts left. The vacated source slots are zeroed via RtlZeroMemory. Expects first ≥ LBound, last ≤ LBound + Capacity - 1, and that the destination range fits within allocated capacity.

Type Information

Member Description
Compare(index1&, index2&) As Long Compare the elements at index1 and index2. Returns a negative value if index1 < index2, 0 if equal, positive if index1 > index2. Comparison semantics are type-specific: =/</> in ValueArray; ObjPtr in ObjectArray; T.Compare(other) in UDTArray. Expects both indices to be within LBound..UBound.
Comparator() As Comparator Returns a type-specific function delegate (Delegate Function Comparator(first As Any, second As Any, Optional context As Any) As Long) used internally by pointer-based sort routines.
ElementSize() As Long Returns the byte size of one element (LenB(Of T) in ValueArray and UDTArray; pointer size in ObjectArray).

Sorting

Member Description
Sort(lo& = InvalidIndex, hi& = InvalidIndex, depthLimit& = -1) Sort elements in ascending order (natural order for the array's type). When lo/hi are InvalidIndex (the default), sorts all elements. Uses introsort via the MemOps type and the array's Comparator delegate: median-of-three Lomuto quicksort with a heapsort fallback when recursion depth exceeds 2 * ⌊log₂(n)⌋ (computed automatically when depthLimit = -1), and insertion sort for subarrays of 16 elements or fewer. In-place, O(n log n) worst case.
HeapSort(lo& = InvalidIndex, hi& = InvalidIndex) Heap-sort elements in the range [lo, hi]. When lo/hi are InvalidIndex, sorts all elements. O(n log n) worst case.
InsertionSort(lo& = InvalidIndex, hi& = InvalidIndex) Insertion-sort elements in the range [lo, hi]. When lo/hi are InvalidIndex, sorts all elements. O(n²) worst case but efficient for small or nearly-sorted data.

Slicing and copying

Member Description
Mid(pos&, len& = -1, lBound& = 1) As IArray Return a new array of the same type containing len elements starting at pos. len = -1 means "to the end". The returned slice has its LBound set to lBound (defaults to 1). In UDTArray, the source must be a UDTArray(Of T) (always satisfied when called as Me.Mid(...)); passing a foreign IArray to the private constructor produces an empty result.
View(pos&, len& = -1, viewLBound& = 1) As T() Return a zero-copy T() array that points directly into internal storage, covering len elements starting at pos. len = -1 means "to the end". The view array's lower bound is viewLBound (defaults to 1). Returns an empty array when len = 0. The backing data must not be reallocated while the view is in use.
View(viewOut As T(), pos&, len& = -1, viewLBound& = 1) Fill an existing array variable viewOut with the same zero-copy view. Implements IArray.View.
Clone() As IArray Return a full copy of the array as a new instance of the same type, preserving LBound.

Memory management

Member Description
Reserve(capacity&) Ensure at least capacity slots are allocated without changing Count.
Resize(count&) Set Count to count. When shrinking, slots past the new UBound are cleared via Wipe; existing elements within the new range are preserved. LBound is unchanged.
ResizeToAtLeast(count&) Call Resize(count) only if count > Count.
Squeeze() Release excess capacity so Capacity = Count, using ReDim Preserve. Deallocates entirely when empty.
Clear() Remove all elements and deallocate memory (Erase mData). Resets LBound to 1 on next access.
Wipe(from&, to&) Overwrite slots from..to with the default value of T: zero / empty string in ValueArray and UDTArray, Nothing in ObjectArray. Expects the range to be within allocated capacity.

Iteration

ValueArray and ObjectArray implement IEnumVARIANT via ArrayIterator, enabling For Each:

Dim a As New ValueArray(Of Long)
a.Append 10 : a.Append 20 : a.Append 30
Dim v As Variant
For Each v In a
    Debug.Print v   ' 10, 20, 30
Next

UDTArray cannot be enumerated with For Each: _NewEnum raises error 5 because TwinBasic cannot box UDT values into Variant for the COM enumeration protocol.

Variant interop

Available in ValueArray and ObjectArray only. Both methods raise error 5 in UDTArray.

Member Description
GetVarItem(index&, result As Variant) Copy the element at index into result as a Variant. Expects LBound ≤ index ≤ UBound.
SetVarItem(index&, value&) Set the element at index from value. Expects LBound ≤ index ≤ UBound.

Pointer access

Member Description
Ptr() As LongPtr Pointer to the first element in the backing array. Returns 0 when no memory is allocated. An empty-but-allocated array may return a non-null pointer.

Memory growth policy

Grow(by) doubles the current capacity when more space is needed, targeting Max(MinSize, cap * 2, cap + by) slots. The allocation always preserves the current LBound via ReDim Preserve mData(lb To lb + newCap - 1). This keeps amortised Append cost O(1).

Preconditions

All public methods assert their preconditions with Debug.Assert. These checks fire only in debug builds and are stripped in release builds.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment