8
\$\begingroup\$

This class encapsulates a 2D Array and a Scripting Dictionary. Values are add or returned from to the Array using a Key and a ColumnIndex, e.g. IndexedArray(Key, ColumnIndex) = Value . The Key is used to get the RowIndex of the Array. The Value is then added to the Array, e.g. Array(ColumnIndex, RowIndex).

Although, I am quite pleased with the performance, flexibility, and ease of use, I am open to any suggestions on how to improve its design.

IndexedArray:Class

Attribute VB_Description = "Uses a Scripting.Dictionary (m.Dictionary) to Index and Array of Values"
Option Explicit
Private Const DEFAULT_ROW_BUFFER As Long = 100
Attribute DEFAULT_ROW_BUFFER.VB_VarDescription = "The number of rows added to m.Values when resized"
Private Const DEFAULT_COLUMN_COUNT As Long = 10
Attribute DEFAULT_COLUMN_COUNT.VB_VarDescription = "The default number of Columns in m.Values"
Private Type Members
 ColumnCount As Long
 Dictionary As Object
 Initiated As Boolean
 Values As Variant 'Values(Columns, Rows)
End Type
Private m As Members
Attribute m.VB_VarDescription = "Encapsulates Class Member Fields For VBA Like m Reference (e.g. m.Value is simular to VBA m_Value)"
Private Sub Class_Initialize()
 Set m.Dictionary = CreateObject("Scripting.Dictionary")
 m.ColumnCount = DEFAULT_COLUMN_COUNT
 ReDim m.Values(1 To m.ColumnCount, 1 To DEFAULT_ROW_BUFFER)
End Sub
Public Property Get Value(ByVal Key As Variant, ByVal ColumnIndex As Long) As Variant
Attribute Value.VB_Description = "Gets or sets m.Values() element at Index returned by m.Dictionary(Key)"
Attribute Value.VB_UserMemId = 0
 If Not m.Dictionary.Exists(Key) Then Expand Key
 Value = m.Values(ColumnIndex, m.Dictionary(Key))
End Property
Public Property Let Value(ByVal Key As Variant, ByVal ColumnIndex As Long, ByVal vValue As Variant)
 If Not m.Dictionary.Exists(Key) Then Expand Key
 m.Values(ColumnIndex, m.Dictionary(Key)) = vValue
End Property
Public Function Exists(Key As Variant) As Boolean
Attribute Exists.VB_Description = "Tests if Key Exists in m.Dictionary"
 Exists = m.Dictionary.Exists(Key)
End Function
Private Sub Expand(ByVal Key As Variant)
Attribute Expand.VB_Description = "Adds new Key to m.Dictionary and Increase adds an additional number of rows to m.Values equal to DEFAULT_ROW_BUFFER."
 m.Dictionary.Add Key, m.Dictionary.Count + 1
 If m.Dictionary.Count > UBound(m.Values, 2) Then ReDim Preserve m.Values(1 To m.ColumnCount, 1 To UBound(m.Values, 2) + DEFAULT_ROW_BUFFER)
End Sub
Public Sub setColumnCount(ColumnCount As Long)
Attribute setColumnCount.VB_Description = "Changes the number of column in m.Values()"
 Dim Values As Variant
 Dim c As Long, r As Long
 If ColumnCount < 1 Then Err.Raise Number:=vbObjectError + 513, Description:="ColumnCount can not be less than 1"
 If m.ColumnCount <> ColumnCount Then
 ReDim Values(1 To ColumnCount, UBound(m.Values, 2))
 For r = 1 To UBound(m.Values)
 For c = 1 To IIf(m.ColumnCount < ColumnCount, m.ColumnCount, ColumnCount)
 Values(c, r) = m.Values(c, r)
 Next
 Next
 m.ColumnCount = ColumnCount
 m.Values = Values
 End If
End Sub
Public Sub EnsureCapacity(Capacity As Long)
 If UBound(m.Values, 2) < Capacity Then ReDim Preserve m.Values(1 To m.ColumnCount, 1 To Capacity)
End Sub
Public Function ToArray(Optional SearchString As String, Optional IncludeHeaderRows As Boolean) As Variant()
Attribute ToArray.VB_Description = "Return 2D Array of Values(Rows, Columns) either filtered or unfiltered. Array filtering is delagated to ToFilteredArray"
 Dim Values As Variant
 Dim c As Long, r As Long
 If Len(SearchString) = 0 Then
 ReDim Values(1 To m.Dictionary.Count, 1 To m.ColumnCount)
 For r = 1 To m.Dictionary.Count
 For c = 1 To m.ColumnCount
 Values(r, c) = m.Values(c, r)
 Next
 Next
 ToArray = Values
 Else
 ToArray = ToFilteredArray(SearchString, IncludeHeaderRows)
 End If
End Function
Private Function ToFilteredArray(SearchString As String, IncludeHeaderRows As Boolean) As Variant()
Attribute ToFilteredArray.VB_Description = "Returns a 2D Array of filtered Values(Rows, Columns) to ToArray"
 Dim Key As Variant, header As Variant, Values As Variant
 Dim c As Long, r As Long, n As Long
 With CreateObject("System.Collections.ArrayList")
 If IncludeHeaderRows Then
 header = m.Dictionary.Keys()(0)
 .Add header
 End If
 For Each Key In m.Dictionary.Keys()
 If Key Like SearchString And Not .Contains(Key) Then .Add Key
 Next
 If .Count = 0 Then
 ReDim Values(1 To 1, 1 To 1)
 Values(1, 1) = vbNullString
 Else
 .Sort
 If Not IsEmpty(header) Then
 .Remove header
 .Insert 0, header
 End If
 ReDim Values(1 To .Count + 1, 1 To m.ColumnCount)
 For Each Key In .ToArray
 n = n + 1
 r = m.Dictionary(Key)
 For c = 1 To m.ColumnCount
 Values(n, c) = m.Values(c, r)
 Next
 Next
 End If
 End With
 ToFilteredArray = Values
End Function

Test Routine

Note: constants prefixed with order belong to Public Enum OrderColumns. OrderColumns enumerates all the columns on the Worksheet("Orders").

Public Sub UpdateSummary(KeyColumn As Long, Optional SearchString As String, Optional IncludeHeaderRows As Boolean)
 Dim t As Long
 Application.ScreenUpdating = False
 Dim idxArray As New IndexedArray, Key As Variant, row As Range
 t = Timer
 With ThisWorkbook.Worksheets("Orders")
 Key = "Header Row"
 idxArray(Key, 1) = .Cells(KeyColumn).Value
 idxArray(Key, 2) = "Count"
 idxArray(Key, 3) = "Average"
 idxArray(Key, 4) = .Cells(orderSales).Value
 idxArray(Key, 5) = .Cells(orderQuantity).Value
 idxArray(Key, 6) = .Cells(orderDiscount).Value
 idxArray(Key, 7) = .Cells(orderProfit).Value
 For Each row In .Range("A2", .Range("A" & .Rows.Count).End(xlUp)).EntireRow
 With row
 Key = .Cells(KeyColumn)
 idxArray(Key, 1) = .Cells(KeyColumn).Value
 idxArray(Key, 2) = idxArray(Key, 4) + 1
 idxArray(Key, 3) = "=AVERAGE(RC[-2]:RC[-1])"
 idxArray(Key, 4) = .Cells(orderSales).Value + idxArray(Key, 4)
 idxArray(Key, 5) = .Cells(orderQuantity).Value + idxArray(Key, 5)
 idxArray(Key, 6) = .Cells(orderDiscount).Value + idxArray(Key, 6)
 idxArray(Key, 7) = .Cells(orderProfit).Value + idxArray(Key, 7)
 End With
 Next
 End With
 Debug.Print Round(Timer - t, 2)
 CreateSummaryTable idxArray.ToArray(SearchString, IncludeHeaderRows)
 Application.ScreenUpdating = True
End Sub

Immediate Window Test

UpdateSummary orderCustomer_Name

UpdateSummary orderCustomer_Name

UpdateSummary orderCustomer_Name, "*Alan*", False

UpdateSummary orderCustomer_Name, "*Alan*", False

enter image description here

UpdateSummary orderCustomer_Name, "*Alan*", True

enter image description here

UpdateSummary orderOrder_ID, "*CA-2014-##3###*", True

Download: IndexedArray.xlsm

asked Jun 24, 2018 at 0:28
\$\endgroup\$
10
  • \$\begingroup\$ I'd KeyColumn = getKeyColumn If KeyColumn < 1 Or KeyColumn > 18 Then Exit Sub \$\endgroup\$ Commented Jun 24, 2018 at 3:11
  • 1
    \$\begingroup\$ Slicing with Index does have size limits though I guess you are aware of that? \$\endgroup\$ Commented Jun 25, 2018 at 5:25
  • 1
    \$\begingroup\$ I just meant catch the errors for non-implemented columns, if I put in 0 I get an error and if I put in 100 the table doesn't make any sense. Slice - hm, couldn't you slice off a part of an array into a variant in one shot? I'd imagine you could, but I don't think I know how. Plus I mean - it's not too off the wall to just implement your own index \$\endgroup\$ Commented Jun 25, 2018 at 6:32
  • 1
    \$\begingroup\$ @Raystafarian I think what I need is a better error message. Excel 16.0 has 16384 columns. Why should I limit the potential of the class? Implementing a Slice function shouldn't be too hard. The main thing that concerns me is getting the syntax right. I want to model it after other languages. So that users will have both an idea of how to use it and be able to look at other documentation and examples on the web, outside of this scope of this class. \$\endgroup\$ Commented Jun 25, 2018 at 8:19
  • 1
    \$\begingroup\$ You state correctly. And thanks for sharing the above link. I shall bookmark that. I look forward to any future posts you make using what you learn from it! Meanwhile, have a plus +1 \$\endgroup\$ Commented Jun 25, 2018 at 8:41

0

Know someone who can answer? Share a link to this question via email, Twitter, or Facebook.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.