Хеш-таблица со строковыми ключами.

Здесь можно найти готовые «кирпичики» — части кода, пригодные для построения более крупных проектов, а также решения различных типовых и не очень задач на VB.

Модератор: Brickgroup

The trick
Постоялец
Постоялец
 
Сообщения: 774
Зарегистрирован: 26.06.2010 (Сб) 23:08

Хеш-таблица со строковыми ключами.

Сообщение The trick » 11.02.2023 (Сб) 13:09

Код: Выделить всё
' //
' // CHashTable.cls - hash table with string keys
' // The trick, 2023
' //

Option Explicit
Option Base 0

Private Const HASH_SIZE As Long = 587

Private Type tHashPointer
    lHash   As Long
    lIndex  As Long
End Type

Private Type tHashItem
    vValue      As Variant
    sKey        As String
    lNext       As Long
    lPrevEnum   As Long
    lNextEnum   As Long
End Type

Private Declare Function CharLowerBuff Lib "user32" _
                         Alias "CharLowerBuffW" ( _
                         ByVal lpsz As Long, _
                         ByVal cchLength As Long) As Long
Private Declare Sub HashData Lib "shlwapi" ( _
                    ByRef pbData As Any, _
                    ByVal cbData As Long, _
                    ByRef pbHash As Any, _
                    ByVal cbHash As Long)

Private m_tItems()          As tHashItem
Private m_bCaseInsensitive  As Boolean
Private m_lCount            As Long
Private m_lFirstFree        As Long
Private m_lFirstItem        As Long
Private m_lLastItem         As Long

Public Property Get CaseInsensitive() As Boolean
    CaseInsensitive = m_bCaseInsensitive
End Property

Public Property Let CaseInsensitive( _
                    ByVal bValue As Boolean)
   
    If m_lCount Then
        Err.Raise 5
    End If
   
    m_bCaseInsensitive = bValue
   
End Property

Public Function Exists( _
                ByRef sKey As String) As Boolean
    Exists = FindItem(sKey).lIndex <> -1
End Function

Public Property Get Item( _
                    ByRef sKey As String) As Variant
    Dim tPointer    As tHashPointer
   
    tPointer = FindItem(sKey)
   
    If tPointer.lIndex = -1 Then
        Item = Empty
    Else
        If IsObject(m_tItems(tPointer.lIndex).vValue) Then
            Set Item = m_tItems(tPointer.lIndex).vValue
        Else
             Item = m_tItems(tPointer.lIndex).vValue
        End If
    End If

End Property

Public Property Let Item( _
                    ByRef sKey As String, _
                    ByVal vValue As Variant)
    Dim tPointer    As tHashPointer
   
    tPointer = FindItem(sKey)

    If IsEmpty(vValue) Then
        If tPointer.lIndex = -1 Then
            Exit Property
        Else
            DeleteItem sKey
        End If
    Else
   
        If tPointer.lIndex = -1 Then
            tPointer = AddItem(sKey)
        End If
       
        m_tItems(tPointer.lIndex).vValue = vValue
       
    End If
   
End Property

Public Property Set Item( _
                    ByRef sKey As String, _
                    ByVal vValue As Variant)
    Dim tPointer    As tHashPointer
   
    tPointer = FindItem(sKey)

    If IsEmpty(vValue) Then
        If tPointer.lIndex = -1 Then
            Exit Property
        Else
            DeleteItem sKey
        End If
    Else
   
        If tPointer.lIndex = -1 Then
            tPointer = AddItem(sKey)
        End If
       
        Set m_tItems(tPointer.lIndex).vValue = vValue
       
    End If
   
End Property

Public Property Get Count() As Long
    Count = m_lCount
End Property

Public Sub Clear()
    Dim lIndex  As Long
   
    ReDim m_tItems(HASH_SIZE - 1)
   
    For lIndex = 0 To HASH_SIZE - 1
   
        m_tItems(lIndex).lNext = -1
        m_tItems(lIndex).lNextEnum = -1
        m_tItems(lIndex).lPrevEnum = -1
       
    Next
   
    m_lFirstFree = HASH_SIZE
    m_lFirstItem = -1
    m_lLastItem = -1
    m_lCount = 0
   
End Sub

Public Property Get Items() As Variant
    Dim lIndex      As Long
    Dim lItemIndex  As Long
    Dim vRet()      As Variant
   
    If m_lCount Then
       
        ReDim vRet(m_lCount - 1)
       
        lItemIndex = m_lFirstItem
       
        For lIndex = 0 To m_lCount - 1
       
            If IsObject(m_tItems(lItemIndex).vValue) Then
                Set vRet(lIndex) = m_tItems(lItemIndex).vValue
            Else
                vRet(lIndex) = m_tItems(lItemIndex).vValue
            End If
           
            lItemIndex = m_tItems(lItemIndex).lNextEnum
           
        Next
       
        Items = vRet
       
    Else
        Items = Split("")
    End If
   
End Property

Public Property Get Keys() As String()
    Dim lIndex      As Long
    Dim lItemIndex  As Long
    Dim sRet()      As String
   
    If m_lCount Then
       
        ReDim sRet(m_lCount - 1)
       
        lItemIndex = m_lFirstItem
       
        For lIndex = 0 To m_lCount - 1
       
            sRet(lIndex) = m_tItems(lItemIndex).sKey
            lItemIndex = m_tItems(lItemIndex).lNextEnum
           
        Next
   
    End If
   
    Keys = sRet
   
End Property

Public Function CalculateHash( _
                ByVal sKey As String) As Long
    Dim lHash   As Long
   
    If m_bCaseInsensitive Then
        CharLowerBuff StrPtr(sKey), Len(sKey)
    End If
   
    HashData ByVal StrPtr(sKey), LenB(sKey), lHash, Len(lHash)
   
    CalculateHash = (lHash And &H7FFFFFFF) Mod HASH_SIZE
   
End Function

Private Sub DeleteItem( _
            ByRef sKey As String)
    Dim lHash       As Long
    Dim lIndex      As Long
    Dim lNextIndex  As Long
    Dim lPrevIndex  As Long
    Dim eComp       As VbCompareMethod
   
    lHash = CalculateHash(sKey)
   
    If m_bCaseInsensitive Then
        eComp = vbTextCompare
    Else
        eComp = vbBinaryCompare
    End If
   
    lIndex = lHash
    lPrevIndex = -1
   
    Do While StrComp(sKey, m_tItems(lIndex).sKey, eComp)
   
        lPrevIndex = lIndex
        lIndex = m_tItems(lIndex).lNext
        If lIndex = -1 Then Exit Sub    ' // Not found
       
    Loop

    If lPrevIndex = -1 Then
       
        ' // In main hash table
        lNextIndex = m_tItems(lIndex).lNext

        If lNextIndex <> -1 Then
           
            ' // Move next collision to main table
            m_tItems(lIndex).sKey = m_tItems(lNextIndex).sKey
           
            If IsObject(m_tItems(lNextIndex).vValue) Then
                Set m_tItems(lIndex).vValue = m_tItems(lNextIndex).vValue
            Else
                m_tItems(lIndex).vValue = m_tItems(lNextIndex).vValue
            End If
           
            If m_tItems(lIndex).lPrevEnum <> -1 Then
                m_tItems(m_tItems(lIndex).lPrevEnum).lNextEnum = m_tItems(lIndex).lNextEnum
            End If
           
            If m_tItems(lIndex).lNextEnum <> -1 Then
                m_tItems(m_tItems(lIndex).lNextEnum).lPrevEnum = m_tItems(lIndex).lPrevEnum
            End If
           
            If m_tItems(lNextIndex).lPrevEnum <> -1 Then
                m_tItems(m_tItems(lNextIndex).lPrevEnum).lNextEnum = lIndex
            End If
           
            If m_tItems(lNextIndex).lNextEnum <> -1 Then
                m_tItems(m_tItems(lNextIndex).lNextEnum).lPrevEnum = lIndex
            End If
           
            If m_lFirstItem = lNextIndex Then
                m_lFirstItem = lIndex
            ElseIf m_lFirstItem = lIndex Then
                If m_tItems(lIndex).lNextEnum <> lNextIndex Then
                    m_lFirstItem = m_tItems(lIndex).lNextEnum
                End If
            End If
           
            If m_lLastItem = lNextIndex Then
                m_lLastItem = lIndex
            ElseIf m_lLastItem = lIndex Then
                If m_tItems(lIndex).lPrevEnum <> lNextIndex Then
                    m_lLastItem = m_tItems(lIndex).lPrevEnum
                End If
            End If
           
            m_tItems(lIndex).lNextEnum = m_tItems(lNextIndex).lNextEnum
            m_tItems(lIndex).lPrevEnum = m_tItems(lNextIndex).lPrevEnum
            m_tItems(lIndex).lNext = m_tItems(lNextIndex).lNext
           
            DeallocItem lNextIndex
           
        Else
           
            If m_lFirstItem = lIndex Then
                m_lFirstItem = m_tItems(lIndex).lNextEnum
            End If
           
            If m_lLastItem = lIndex Then
                m_lLastItem = m_tItems(lIndex).lPrevEnum
            End If
           
            If m_tItems(lIndex).lNextEnum <> -1 Then
                m_tItems(m_tItems(lIndex).lNextEnum).lPrevEnum = m_tItems(lIndex).lPrevEnum
            End If
           
            If m_tItems(lIndex).lPrevEnum <> -1 Then
                m_tItems(m_tItems(lIndex).lPrevEnum).lNextEnum = m_tItems(lIndex).lNextEnum
            End If

            m_tItems(lIndex).sKey = vbNullString
            m_tItems(lIndex).vValue = Empty
            m_tItems(lIndex).lNext = -1
            m_tItems(lIndex).lNextEnum = -1
            m_tItems(lIndex).lPrevEnum = -1
           
        End If

    Else
       
        m_tItems(lPrevIndex).lNext = m_tItems(lIndex).lNext
       
        If m_lFirstItem = lIndex Then
            m_lFirstItem = m_tItems(lIndex).lNextEnum
        End If
       
        If m_lLastItem = lIndex Then
            m_lLastItem = m_tItems(lIndex).lPrevEnum
        End If
           
        If m_tItems(lIndex).lNextEnum <> -1 Then
            m_tItems(m_tItems(lIndex).lNextEnum).lPrevEnum = m_tItems(lIndex).lPrevEnum
        End If
       
        If m_tItems(lIndex).lPrevEnum <> -1 Then
            m_tItems(m_tItems(lIndex).lPrevEnum).lNextEnum = m_tItems(lIndex).lNextEnum
        End If
       
        DeallocItem lIndex
       
    End If
   
    m_lCount = m_lCount - 1
   
End Sub

Private Function AddItem( _
                 ByRef sKey As String) As tHashPointer
    Dim lIndex      As Long
    Dim lNewIndex   As Long
    Dim eComp       As VbCompareMethod
   
    If m_bCaseInsensitive Then
        eComp = vbTextCompare
    Else
        eComp = vbBinaryCompare
    End If
   
    lIndex = CalculateHash(sKey)
   
    AddItem.lHash = lIndex
   
    Do While StrComp(sKey, m_tItems(lIndex).sKey, eComp)
        If m_tItems(lIndex).lNext = -1 Then
           
            If lIndex = AddItem.lHash And Len(m_tItems(lIndex).sKey) = 0 Then
                lNewIndex = lIndex
            Else
                lNewIndex = AllocItem
                m_tItems(lIndex).lNext = lNewIndex
            End If
           
            m_tItems(lNewIndex).sKey = sKey
            m_tItems(lNewIndex).lNext = -1
            m_tItems(lNewIndex).lNextEnum = -1
           
            If m_lFirstItem = -1 Then
                m_lFirstItem = lNewIndex
            End If
           
            m_tItems(lNewIndex).lPrevEnum = m_lLastItem
           
            If m_lLastItem <> -1 Then
                m_tItems(m_lLastItem).lNextEnum = lNewIndex
            End If
           
            m_lLastItem = lNewIndex
           
            lIndex = lNewIndex
           
            m_lCount = m_lCount + 1
           
            Exit Do
           
        Else
            lIndex = m_tItems(lIndex).lNext
        End If
    Loop
   
    AddItem.lIndex = lIndex
   
End Function

Private Function FindItem( _
                 ByRef sKey As String) As tHashPointer
    Dim lIndex  As Long
    Dim eComp   As VbCompareMethod
   
    If m_bCaseInsensitive Then
        eComp = vbTextCompare
    Else
        eComp = vbBinaryCompare
    End If
   
    lIndex = CalculateHash(sKey)
   
    FindItem.lHash = lIndex
   
    Do While StrComp(sKey, m_tItems(lIndex).sKey, eComp)
        lIndex = m_tItems(lIndex).lNext
        If lIndex = -1 Then Exit Do
    Loop
   
    FindItem.lIndex = lIndex
   
End Function

Private Sub DeallocItem( _
            ByVal lIndex As Long)
               
    m_tItems(lIndex).sKey = vbNullString
    m_tItems(lIndex).vValue = Empty
    m_tItems(lIndex).lNext = m_lFirstFree
    m_tItems(lIndex).lNextEnum = -1
    m_tItems(lIndex).lPrevEnum = -1
   
    m_lFirstFree = lIndex
               
End Sub

Private Function AllocItem() As Long
    Dim lIndex      As Long
    Dim lCurSize    As Long
   
    If m_lFirstFree > UBound(m_tItems) Then
       
        lCurSize = (UBound(m_tItems) + 1)
        ReDim Preserve m_tItems(lCurSize * 2 - 1)
       
        For lIndex = lCurSize To UBound(m_tItems)
       
            m_tItems(lIndex).lNext = lIndex + 1
            m_tItems(lIndex).lNextEnum = -1
            m_tItems(lIndex).lPrevEnum = -1
           
        Next
       
    End If
   
    AllocItem = m_lFirstFree
    m_lFirstFree = m_tItems(m_lFirstFree).lNext
   
End Function

Private Sub Class_Initialize()

    Clear
    m_bCaseInsensitive = True
   
End Sub
UA6527P

Вернуться в Кирпичный завод

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 1

    TopList