home *** CD-ROM | disk | FTP | other *** search
/ Visual Basic Controls / Visual Basic Controls.iso / vbcontrol / axgrid / collecti.cls < prev    next >
Encoding:
Visual Basic class definition  |  1998-08-19  |  18.2 KB  |  557 lines

  1. VERSION 1.0 CLASS
  2. BEGIN
  3.   MultiUse = -1  'True
  4. END
  5. Attribute VB_Name = "CollectionEx"
  6. Attribute VB_GlobalNameSpace = False
  7. Attribute VB_Creatable = False
  8. Attribute VB_PredeclaredId = False
  9. Attribute VB_Exposed = False
  10. Option Explicit
  11.  
  12. #Const ValueType = 2    '0=any, 1=non object, 2=objects
  13.                         ' use (2) when possible to save memory
  14.  
  15. Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (dest As Any, source As Any, ByVal bytes As Long)
  16. Private Declare Sub ZeroMemory Lib "kernel32" Alias "RtlZeroMemory" (dest As Any, ByVal numbytes As Long)
  17.  
  18. ' default initial size of the internal array
  19. Const INITSIZE_DEF = 1000
  20. ' default value of the number of items that are allocated when necessary
  21. Const ALLOCATIONCHUNK_DEF = INITSIZE_DEF
  22. ' default fill ratio
  23. Const FILLRATIO_DEF = 2
  24.  
  25. Private Type TValue
  26.     HashCode As Long
  27.     HashIndex As Long
  28.     Key As String
  29. #If ValueType = 2 Then
  30.     Item As Object
  31. #Else
  32.     Item As Variant
  33. #End If
  34. End Type
  35.  
  36. ' initial number of items in the Value() array
  37. Dim m_InitSize As Long
  38. ' number of items that are allocated when necessary
  39. Dim m_AllocationChunk As Long
  40. ' fill ratio - when the ratio between the total number of items
  41. ' in value() and the number of items actually used is greater than
  42. ' this value, additional DATA_CHUNK items are allocated
  43. Dim m_FillRatio As Single
  44.  
  45. ' this array of records holds the values
  46. Private value() As TValue
  47. ' this holds the size of Values()
  48. Private valueSize As Long
  49. ' this array holds the backpointers into values() (or 0 if unused)
  50. Private hashTable() As Long
  51. ' this holds the size of the hash table
  52. Private hashTableSize As Long
  53. ' the number of elements actually used in Value()
  54. Private m_Count As Long
  55. ' true if the collection should be sorted
  56. Private m_Sorted As Boolean
  57. ' this collection holds the values during enumeration loops (see NewEnum)
  58. Private m_values As Collection
  59. ' size of an item in Value()
  60. Dim itemLen As Long
  61.  
  62. Private Sub Class_Initialize()
  63.     m_InitSize = INITSIZE_DEF
  64.     m_AllocationChunk = ALLOCATIONCHUNK_DEF
  65.     m_FillRatio = FILLRATIO_DEF
  66.     Clear
  67. End Sub
  68.  
  69. ' set allocation values
  70. ' NUMITEMS  is the expected number of items in the collection
  71. '           (the collection can grow above this value)
  72. ' ALLOCATIONCHUCKS is the number of items that must be allocated when necessary
  73. ' FILLRATIO is a number >1 that states how bigger the internal hash table
  74. '           is relative to the number of items (suggested value is 2 or 3)
  75.  
  76. Sub SetMemory(ByVal NumItems As Long, Optional ByVal AllocationChunk As Long, Optional ByVal FillRatio As Single)
  77. Attribute SetMemory.VB_Description = "Allocate initial memory for the items of the collection, sets the allocation unit and the fill ratio for the internal hash table structure"
  78.     ' minimal range checking
  79.     If NumItems < 20 Then NumItems = 20
  80.     If AllocationChunk < 20 Then AllocationChunk = NumItems
  81.     If FillRatio < 1.5 Then FillRatio = 1.5
  82.     ' store into class variables
  83.     m_InitSize = NumItems
  84.     m_AllocationChunk = AllocationChunk
  85.     m_FillRatio = FillRatio
  86.     ' rebuild all internal tables
  87.     If m_Count = 0 Then
  88.         Clear
  89.     Else
  90.         RehashTables NumItems
  91.     End If
  92. End Sub
  93.  
  94. ' destroy all items in the collection
  95.  
  96. Sub Clear()
  97. Attribute Clear.VB_Description = "Remove all items from the collection"
  98.     m_Count = 0
  99.     valueSize = m_InitSize
  100.     ReDim value(valueSize) As TValue
  101.     itemLen = Len(value(1))
  102.     ' odd values minimize collisions in the hash table
  103.     hashTableSize = (valueSize * m_FillRatio) Or 1
  104.     ReDim hashTable(hashTableSize) As Long
  105.     ' clear the private collection
  106.     Set m_values = Nothing
  107. End Sub
  108.  
  109. ' return the number of items in the collection
  110.  
  111. Property Get Count() As Long
  112. Attribute Count.VB_Description = "Return the number of items in the collection"
  113.     Count = m_Count
  114. End Property
  115.  
  116. ' add a new item to the collection
  117. ' KEY is not optional (differently from standard collections)
  118. ' if IGNOREIFPRESENT = True, doesn't raise any error if the item is
  119. ' already in the collection
  120. ' BEFORE and AFTER are ignored if the collection is sorted
  121.  
  122. Sub Add(Item As Variant, Key As String, Optional Before As Variant, Optional After As Variant, Optional IgnoreIfPresent As Boolean)
  123. Attribute Add.VB_Description = "Add a new item to the collection; Before and After arguments are ignored if the collection is sorted"
  124.     Dim ndx As Long, hCode As Long, strKey As String
  125.     Dim NewIndex As Long, i As Long
  126.     
  127.     ' check if there is an item with that key
  128.     strKey = Key
  129.     ndx = GetIndex(strKey, hCode)
  130.     ' signal error if the item was already in the collection
  131.     If ndx > 0 Then
  132.         ' raise error, unless the flag is True
  133.         If Not IgnoreIfPresent Then Err.Raise 457
  134.         ' otherwise, just jump to where the item is assigned
  135.         NewIndex = hashTable(ndx)
  136.         GoTo Add_SetItem
  137.     End If
  138.     
  139.     ' see if we need to allocate more memory
  140.     If m_Count = valueSize Then
  141.         RehashTables valueSize + m_AllocationChunk
  142.         ndx = GetIndex(strKey, hCode)
  143.     End If
  144.     ' now NDX points to the right location in the hashtable
  145.     ndx = -ndx
  146.     
  147.     ' evaluate the newIndex of this item
  148.     If m_Sorted Then
  149.         ' the collection is sorted, so we can use binary search
  150.         NewIndex = -BinarySearch(strKey)
  151.     ElseIf Not IsMissing(Before) Then
  152.         If Not IsMissing(After) Then Err.Raise 5
  153.         If VarType(Before) = vbString Then
  154.             NewIndex = Index(CStr(Before))
  155.         Else
  156.             NewIndex = Before
  157.         End If
  158.         CheckRange NewIndex
  159.     ElseIf Not IsMissing(After) Then
  160.         If VarType(After) = vbString Then
  161.             NewIndex = Index(CStr(After))
  162.         Else
  163.             NewIndex = After
  164.         End If
  165.         ' first check for the range, then increase it
  166.         CheckRange NewIndex
  167.         NewIndex = NewIndex + 1
  168.     Else
  169.         ' both Before and After are omitted, and the collection is not sorted
  170.         NewIndex = m_Count + 1
  171.     End If
  172.  
  173. '    ' evaluate the newIndex of this item
  174. '    If m_Sorted Then
  175. '        ' the collection is sorted, so we can use binary search
  176. '        NewIndex = -BinarySearch(strKey)
  177. '    ElseIf Not IsMissing(Before) Then
  178. '        If Not IsMissing(After) Then Err.Raise 5
  179. '        If VarType(Before) = vbString Then
  180. '            NewIndex = GetIndex((Before), , True) ' pass by value
  181. '        Else
  182. '            NewIndex = Before
  183. '            CheckRange NewIndex
  184. '        End If
  185. '    ElseIf Not IsMissing(After) Then
  186. '        If VarType(After) = vbString Then
  187. '            NewIndex = GetIndex((After), , True) ' pass by value
  188. '        Else
  189. '            NewIndex = After
  190. '            CheckRange NewIndex
  191. '        End If
  192. '    Else
  193. '        ' both Before and After are omitted, and the collection is not sorted
  194. '        NewIndex = m_Count + 1
  195. '    End If
  196.     
  197.     ' we have a new value
  198.     m_Count = m_Count + 1
  199.     ' store the backpointer into the hashtable
  200.     hashTable(ndx) = NewIndex
  201.     
  202.     ' see if we need to make room in the value() array
  203.     If NewIndex <> m_Count Then
  204.         ' make a hole at value(newIndex)
  205.         CopyMemory ByVal VarPtr(value(NewIndex + 1)), ByVal VarPtr(value(NewIndex)), (m_Count - NewIndex) * itemLen
  206.         ZeroMemory ByVal VarPtr(value(NewIndex)), itemLen
  207.         ' adjust backpointers of all subsequent items
  208.         For i = NewIndex + 1 To m_Count
  209.             hashTable(value(i).HashIndex) = i
  210.         Next
  211.     End If
  212.     
  213.     ' store the item into the value() array
  214.     value(NewIndex).HashCode = hCode
  215.     value(NewIndex).HashIndex = ndx
  216.     value(NewIndex).Key = strKey
  217.     
  218. Add_SetItem:
  219. #If ValueType = 0 Then
  220.     ' objects and non-object values needs a different action
  221.     If IsObject(Item) Then
  222.         Set value(NewIndex).Item = Item
  223.     Else
  224.         value(NewIndex).Item = Item
  225.     End If
  226. #ElseIf ValueType = 1 Then
  227.     value(NewIndex).Item = Item
  228. #Else
  229.     Set value(NewIndex).Item = Item
  230. #End If
  231.  
  232.     ' clear the private collection
  233.     Set m_values = Nothing
  234.  
  235. End Sub
  236.  
  237. ' return an item
  238.  
  239. Function Item(Index As Variant) As Variant
  240. Attribute Item.VB_Description = "Return the item associated with this key or numeric index"
  241. Attribute Item.VB_UserMemId = 0
  242.     Dim ndx As Long
  243.     
  244.     If VarType(Index) = vbString Then
  245.         ' find the item given its key
  246.         ndx = hashTable(GetIndex((Index), , True)) ' pass by value
  247.     Else
  248.         ' find an item given its numeric Index
  249.         ndx = Index
  250.         CheckRange ndx
  251.     End If
  252.     
  253. #If ValueType = 0 Then
  254.     ' objects and non-object values needs a different action
  255.     If IsObject(value(ndx).Item) Then
  256.         Set Item = value(ndx).Item
  257.     Else
  258.         Item = value(ndx).Item
  259.     End If
  260. #ElseIf ValueType = 1 Then
  261.     Item = value(ndx).Item
  262. #Else
  263.     Set Item = value(ndx).Item
  264. #End If
  265. End Function
  266.  
  267. ' remove an item, given its alphabetical or numerical key
  268. ' if IgnoreIfNotFound=True, no error is raised if the item is not found
  269.  
  270. Sub Remove(Index As Variant, Optional IgnoreIfNotFound As Boolean)
  271. Attribute Remove.VB_Description = "Remove an item from the collection"
  272.     Dim ndx As Long, i As Long
  273.     Dim valueNdx As Long
  274.     
  275.     If VarType(Index) = vbString Then
  276.         ' remove an item given its key
  277.         ndx = GetIndex((Index)) ' pass by value
  278.         If ndx < 0 Then
  279.             If IgnoreIfNotFound Then Exit Sub
  280.             Err.Raise 5
  281.         End If
  282.         valueNdx = hashTable(ndx)
  283.     Else
  284.         ' remove an item given its numeric key
  285.         valueNdx = Index
  286.         If valueNdx < 1 Or valueNdx > m_Count Then
  287.             If IgnoreIfNotFound Then Exit Sub
  288.             Err.Raise 9
  289.         End If
  290.         ndx = value(valueNdx).HashIndex
  291.     End If
  292.     
  293.     ' clear this item
  294.     value(valueNdx) = value(0)
  295.     ' remove it from the value() array, shifting following items
  296.     If valueNdx < m_Count Then
  297.         CopyMemory ByVal VarPtr(value(valueNdx)), ByVal VarPtr(value(valueNdx + 1)), (m_Count - valueNdx) * itemLen
  298.         ZeroMemory ByVal VarPtr(value(m_Count)), itemLen
  299.     End If
  300.     ' decrease the counter
  301.     m_Count = m_Count - 1
  302.     ' adjust the Index backpointer for all subsequent items
  303.     For i = valueNdx To m_Count
  304.         hashTable(value(i).HashIndex) = i
  305.     Next
  306.     
  307.     ' remove this item from the hash table
  308.     hashTable(ndx) = 0
  309.     ' move all subsequent items in the correct position
  310.     i = ndx
  311.     Do
  312.         i = i + 1
  313.         If i > hashTableSize Then i = 1
  314.         ' get the corresponding index in the value() array
  315.         valueNdx = hashTable(i)
  316.         ' exit if element is blank
  317.         If valueNdx = 0 Then Exit Do
  318.         ' zero the HashCode value for this item
  319.         hashTable(i) = 0
  320.         ' search for the right index for this item
  321.         ndx = -GetIndex(value(valueNdx).Key, value(valueNdx).HashCode)
  322.         Debug.Assert ndx > 0
  323.         ' move the backpointer where it should go
  324.         hashTable(ndx) = valueNdx
  325.         value(valueNdx).HashIndex = ndx
  326.     Loop
  327.         
  328.     ' clear the private collection
  329.     Set m_values = Nothing
  330.  
  331. End Sub
  332.  
  333. ' add support for enumeration (For Each ... Next)
  334. ' NOTE: this is a time consuming operation, and should be avoided if possible
  335. ' iterating using a regular For...Next loop is always *much* faster
  336.  
  337. Function NewEnum() As IUnknown
  338. Attribute NewEnum.VB_UserMemId = -4
  339. Attribute NewEnum.VB_MemberFlags = "40"
  340.     If (m_values Is Nothing) Then
  341.         ' build the collection on the fly
  342.         Dim i As Long
  343.         Set m_values = New Collection
  344.         For i = 1 To m_Count
  345.             m_values.Add value(i).Item, value(i).Key
  346.         Next
  347.     End If
  348.     Set NewEnum = m_values.[_NewEnum]
  349. End Function
  350.  
  351. ' return True if an item actually exists
  352.  
  353. Function Exists(Key As Variant) As Boolean
  354. Attribute Exists.VB_Description = "Return True if an item with this key exists in the collection"
  355.     If VarType(Key) = vbString Then
  356.         ' check the index corresponding to a given key
  357.         Exists = (GetIndex((Key)) > 0)    ' pass by value
  358.     Else
  359.         ' simply check that index is in range
  360.         Exists = (Key >= 1 And Key <= m_Count)
  361.     End If
  362. End Function
  363.  
  364. ' return the numerical index given the alphabetical key
  365. '   -1 if the item does not exist
  366.  
  367. Function Index(Key As String) As Long
  368. Attribute Index.VB_Description = "Return the numeric index of the item with this key, or -1 if the key is not found"
  369.     Dim ndx As Long
  370.     ndx = GetIndex((Key))  ' pass by value
  371.     If ndx > 0 Then
  372.         Index = hashTable(ndx)
  373.     Else
  374.         Index = -1
  375.     End If
  376. End Function
  377.  
  378. ' return the Key corresponding to a numerical index
  379. ' raises an error if index is out-of-range
  380. ' may be a null string, in which case no key was specified in the Add method
  381.  
  382. Function Key(Index As Long) As String
  383. Attribute Key.VB_Description = "Return the key of the item found in this position"
  384.     CheckRange Index
  385.     Key = value(Index).Key
  386. End Function
  387.  
  388. ' the sorted state of the collection
  389. ' (set to True to sort items)
  390.  
  391. Property Get Sorted() As Boolean
  392. Attribute Sorted.VB_Description = "Return or sets the sort status of the collection"
  393.     Sorted = m_Sorted
  394. End Property
  395.  
  396. Property Let Sorted(ByVal newValue As Boolean)
  397.     If newValue <> m_Sorted Then
  398.         m_Sorted = newValue
  399.         If m_Sorted Then ShellSort
  400.     End If
  401. End Property
  402.  
  403. ' raise an error if an index is out of range
  404.  
  405. Private Sub CheckRange(ByVal numKey As Long)
  406.     'If numKey < 1 Or numKey > m_Count Then Err.Raise 9
  407. End Sub
  408.  
  409. ' binary search of a key
  410. ' assumes that key is lowercase, and that value() is sorted
  411. ' if found, returns the index of the item
  412. ' otherwise, returns the negated index of where it should be stored
  413.  
  414. Private Function BinarySearch(Key As String) As Long
  415.     Dim first As Long, last As Long, middle As Long
  416.     first = 1
  417.     last = m_Count
  418.     Do Until first > last
  419.         middle = (first + last) \ 2
  420.         Select Case StrComp(value(middle).Key, Key)
  421.             Case -1
  422.                 first = middle + 1
  423.             Case 1
  424.                 last = middle - 1
  425.             Case 0
  426.                 BinarySearch = middle
  427.                 Exit Function
  428.         End Select
  429.     Loop
  430.     BinarySearch = -first
  431. End Function
  432.  
  433. ' return the hash code of a string - TEXT should be lowercase
  434. ' (can't be a null value)
  435.  
  436. Private Function HashCode(text As String) As Long
  437.     Dim strLen As Long, result As Long, i As Long
  438.     ' allocate a static buffer (dramatically reduces overhead)
  439.     Static buffer(1 To 256) As Integer
  440.     
  441.     ' copy the string into an array of Longs (max 256 chars)
  442.     strLen = Len(text)
  443.     If strLen > 256 Then strLen = 256
  444.     CopyMemory ByVal VarPtr(buffer(1)), ByVal StrPtr(text), strLen * 2
  445.     
  446.     ' create the Hash code by adding all values
  447.     ' add a fixed value to account for null strings
  448.     result = buffer(1) + 17
  449.     For i = 2 To strLen
  450.         result = (result * 2 + buffer(i)) And &H3FFFFFFF
  451.     Next
  452.     HashCode = result
  453. End Function
  454.  
  455. ' return the position in value() of a given key and its Hash code
  456. ' if the item is not found, return the negated index of where it should go
  457. ' KEY is modified to its lowercase version
  458. ' If RaiseError = True, it raises an error if the item is not found
  459.  
  460. Private Function GetIndex(Key As String, Optional hCode As Long, Optional raiseError As Boolean) As Long
  461.     Dim ndx As Long, valueNdx As Long
  462.     
  463.     If hCode = 0 Then
  464.         ' if hash code is null, evaluate it
  465.         Key = LCase$(Key)
  466.         hCode = HashCode(Key)
  467.     End If
  468.     
  469.     ndx = (hCode Mod hashTableSize) + 1
  470.     Do
  471.         ' first, compare hash codes
  472.         valueNdx = hashTable(ndx)
  473.         If valueNdx = 0 Then
  474.             ' this item doesn't exist - raise error if requrested
  475.             If raiseError Then Err.Raise 5
  476.             ' else exit, but return its would-be position
  477.             GetIndex = -ndx
  478.             Exit Function
  479.         ElseIf value(valueNdx).HashCode = hCode Then
  480.             ' actually compare strings only if hash codes match
  481.             If value(valueNdx).Key = Key Then
  482.                 GetIndex = ndx
  483.                 Exit Function
  484.             End If
  485.         End If
  486.         ndx = ndx + 1
  487.         If ndx > hashTableSize Then ndx = 1
  488.     Loop
  489. End Function
  490.  
  491. ' rehash all internal tables
  492.  
  493. Private Sub RehashTables(newSize As Long)
  494.     Dim i As Long, ndx As Long
  495.     
  496.     ' enlarge the value() array, preserving current values
  497.     valueSize = newSize
  498.     ReDim Preserve value(valueSize) As TValue
  499.     
  500.     ' create a larger hashtable
  501.     ' always use an odd value to increase performance of HashTable
  502.     hashTableSize = (valueSize * m_FillRatio) Or 1
  503.     ReDim hashTable(hashTableSize) As Long
  504.     
  505.     ' rebuild the hash table
  506.     For i = 1 To m_Count
  507.         ndx = -GetIndex(value(i).Key, value(i).HashCode)
  508.         value(i).HashIndex = ndx
  509.         hashTable(ndx) = i
  510.     Next
  511.     
  512. End Sub
  513.  
  514. ' sort the value() array
  515.  
  516. Private Sub ShellSort()
  517.     Dim i As Long, j As Long
  518.     Dim firstItem As Long
  519.     Dim distance As Long
  520.     Dim tmpValue As TValue
  521.  
  522.     ' account for optional arguments
  523.     firstItem = 1
  524.     ' find the best value for distance
  525.     Do
  526.         distance = distance * 3 + 1
  527.     Loop Until distance > m_Count
  528.     
  529.     Do
  530.         distance = distance \ 3
  531.         For i = distance + 1 To m_Count
  532.             If value(i - distance).Key > value(i).Key Then
  533.                 ' save a copy of the data
  534.                 j = i
  535.                 CopyMemory ByVal VarPtr(tmpValue), ByVal VarPtr(value(j)), itemLen
  536.                 Do
  537.                     CopyMemory ByVal VarPtr(value(j)), ByVal VarPtr(value(j - distance)), itemLen
  538.                     j = j - distance
  539.                     If j <= distance Then Exit Do
  540.                 Loop While (value(j - distance).Key > tmpValue.Key)
  541.                 ' move the data back in the array
  542.                 CopyMemory ByVal VarPtr(value(j)), ByVal VarPtr(tmpValue), itemLen
  543.             End If
  544.         Next
  545.     Loop Until distance <= 1
  546.     
  547.     ' zero local variable before VB has a chance to deallocate it
  548.     ZeroMemory ByVal VarPtr(tmpValue), itemLen
  549.     
  550.     ' fix the backpointers for all items in the hash table
  551.     For i = 1 To m_Count
  552.         hashTable(value(i).HashIndex) = i
  553.     Next
  554.     
  555. End Sub
  556.  
  557.