| /************************************************************** |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| *************************************************************/ |
| |
| |
| |
| // MARKER(update_precomp.py): autogen include statement, do not remove |
| #include "precompiled_tools.hxx" |
| |
| #include <tlgen.hxx> |
| #include "hashtbl.hxx" |
| |
| #include <algorithm> |
| |
| // ------------------------------------------------------------- |
| // class HashItem |
| // |
| class HashItem |
| { |
| enum ETag { TAG_EMPTY, TAG_USED, TAG_DELETED }; |
| |
| void* m_pObject; |
| ETag m_Tag; |
| String m_Key; |
| |
| public: |
| HashItem() { m_Tag = TAG_EMPTY; m_pObject = NULL; } |
| |
| BOOL IsDeleted() const |
| { return m_Tag == TAG_DELETED; } |
| |
| BOOL IsEmpty() const |
| { return m_Tag == TAG_DELETED || m_Tag == TAG_EMPTY; } |
| |
| BOOL IsFree() const |
| { return m_Tag == TAG_EMPTY; } |
| |
| BOOL IsUsed() const |
| { return m_Tag == TAG_USED; } |
| |
| void Delete() |
| { m_Tag = TAG_DELETED; m_Key = ""; m_pObject = NULL; } |
| |
| String const& GetKey() const |
| { return m_Key; } |
| |
| void* GetObject() const |
| { return m_pObject; } |
| |
| void SetObject(String const Key, void *pObject) |
| { m_Tag = TAG_USED; m_Key = Key; m_pObject = pObject; } |
| }; |
| |
| // #define MIN(a,b) (a)<(b)?(a):(b) |
| // #define MAX(a,b) (a)>(b)?(a):(b) |
| |
| // ------------------------------------------------------------- |
| // class HashTable |
| // |
| |
| /*static*/ double HashTable::m_defMaxLoadFactor = 0.8; |
| /*static*/ double HashTable::m_defDefGrowFactor = 2.0; |
| |
| HashTable::HashTable(ULONG lSize, BOOL bOwner, double dMaxLoadFactor, double dGrowFactor) |
| { |
| m_lSize = lSize; |
| m_bOwner = bOwner; |
| m_lElem = 0; |
| m_dMaxLoadFactor = std::max(0.5,std::min(1.0,dMaxLoadFactor)); // 0.5 ... 1.0 |
| m_dGrowFactor = std::max(1.3,(5.0,dGrowFactor)); // 1.3 ... 5.0 |
| m_pData = new HashItem [lSize]; |
| |
| // Statistik |
| #ifdef DBG_UTIL |
| m_aStatistic.m_lSingleHash = 0; |
| m_aStatistic.m_lDoubleHash = 0; |
| m_aStatistic.m_lProbe = 0; |
| #endif |
| } |
| |
| HashTable::~HashTable() |
| { |
| // Wenn die HashTable der Owner der Objecte ist, |
| // müssen die Destruktoren separat gerufen werden. |
| // Dies geschieht über die virtuelle Methode OnDeleteObject() |
| // |
| // Problem: Virtuelle Funktionen sind im Destructor nicht virtuell!! |
| // Der Code muß deshalb ins Macro |
| |
| /* |
| if (m_bOwner) |
| { |
| for (ULONG i=0; i<GetSize(); i++) |
| { |
| void *pObject = GetObjectAt(i); |
| |
| if (pObject != NULL) |
| OnDeleteObject(pObject()); |
| } |
| } |
| */ |
| |
| // Speicher für HashItems freigeben |
| delete [] m_pData; |
| } |
| |
| void* HashTable::GetObjectAt(ULONG lPos) const |
| // Gibt Objekt zurück, wenn es eines gibt, sonst NULL; |
| { |
| DBG_ASSERT(lPos<m_lSize, "HashTable::GetObjectAt()"); |
| |
| HashItem *pItem = &m_pData[lPos]; |
| |
| return pItem->IsUsed() ? pItem->GetObject() : NULL; |
| } |
| |
| void HashTable::OnDeleteObject(void*) |
| { |
| DBG_ERROR("HashTable::OnDeleteObject(void*) nicht überladen"); |
| } |
| |
| ULONG HashTable::Hash(String const& Key) const |
| { |
| /* |
| ULONG lHash = 0; |
| ULONG i,n; |
| |
| for (i=0,n=Key.Len(); i<n; i++) |
| { |
| lHash *= 256L; |
| lHash += (ULONG)(USHORT)Key.GetStr()[i]; |
| lHash %= m_lSize; |
| } |
| return lHash; |
| */ |
| |
| // Hashfunktion von P.J. Weinberger |
| // aus dem "Drachenbuch" von Aho/Sethi/Ullman |
| ULONG i,n; |
| ULONG h = 0; |
| ULONG g = 0; |
| |
| for (i=0,n=Key.Len(); i<n; i++) |
| { |
| h = (h<<4) + (ULONG)(USHORT)Key.GetStr()[i]; |
| g = h & 0xf0000000; |
| |
| if (g != 0) |
| { |
| h = h ^ (g >> 24); |
| h = h ^ g; |
| } |
| } |
| |
| return h % m_lSize; |
| } |
| |
| ULONG HashTable::DHash(String const& Key, ULONG lOldHash) const |
| { |
| ULONG lHash = lOldHash; |
| ULONG i,n; |
| |
| for (i=0,n=Key.Len(); i<n; i++) |
| { |
| lHash *= 256L; |
| lHash += (ULONG)(USHORT)Key.GetStr()[i]; |
| lHash %= m_lSize; |
| } |
| return lHash; |
| |
| /* return |
| ( |
| lHash |
| + (char)Key.GetStr()[0] * 256 |
| + (char)Key.GetStr()[Key.Len()-1] |
| + 1 |
| ) |
| % m_lSize; |
| */ |
| } |
| |
| ULONG HashTable::Probe(ULONG lPos) const |
| // gibt den Folgewert von lPos zurück |
| { |
| lPos++; if (lPos==m_lSize) lPos=0; |
| return lPos; |
| } |
| |
| BOOL HashTable::IsFull() const |
| { |
| return m_lElem>=m_lSize; |
| } |
| |
| BOOL HashTable::Insert(String const& Key, void* pObject) |
| // pre: Key ist nicht im Dictionary enthalten, sonst return FALSE |
| // Dictionary ist nicht voll, sonst return FALSE |
| // post: pObject ist unter Key im Dictionary; m_nElem wurde erhöht |
| { |
| SmartGrow(); |
| |
| if (IsFull()) |
| { |
| DBG_ERROR("HashTable::Insert() is full"); |
| return FALSE; |
| } |
| |
| if (FindPos(Key) != NULL ) |
| return FALSE; |
| |
| ULONG lPos = Hash(Key); |
| HashItem *pItem = &m_pData[lPos]; |
| |
| // first hashing |
| // |
| if (pItem->IsEmpty()) |
| { |
| pItem->SetObject(Key, pObject); |
| m_lElem++; |
| |
| #ifdef DBG_UTIL |
| m_aStatistic.m_lSingleHash++; |
| #endif |
| |
| return TRUE; |
| } |
| |
| // double hashing |
| // |
| lPos = DHash(Key,lPos); |
| pItem = &m_pData[lPos]; |
| |
| if (pItem->IsEmpty()) |
| { |
| pItem->SetObject(Key, pObject); |
| m_lElem++; |
| |
| #ifdef DBG_UTIL |
| m_aStatistic.m_lDoubleHash++; |
| #endif |
| |
| return TRUE; |
| } |
| |
| // linear probing |
| // |
| do |
| { |
| #ifdef DBG_UTIL |
| m_aStatistic.m_lProbe++; |
| #endif |
| |
| lPos = Probe(lPos); |
| pItem = &m_pData[lPos]; |
| } |
| while(!pItem->IsEmpty()); |
| |
| pItem->SetObject(Key, pObject); |
| m_lElem++; |
| return TRUE; |
| } |
| |
| HashItem* HashTable::FindPos(String const& Key) const |
| // sucht den Key; gibt Refrenz auf den Eintrag (gefunden) |
| // oder NULL (nicht gefunden) zurück |
| // |
| // pre: - |
| // post: - |
| { |
| // first hashing |
| // |
| ULONG lPos = Hash(Key); |
| HashItem *pItem = &m_pData[lPos]; |
| |
| if (pItem->IsUsed() |
| && pItem->GetKey() == Key) |
| { |
| return pItem; |
| } |
| |
| // double hashing |
| // |
| if (pItem->IsDeleted() || pItem->IsUsed()) |
| { |
| lPos = DHash(Key,lPos); |
| pItem = &m_pData[lPos]; |
| |
| if (pItem->IsUsed() |
| && pItem->GetKey() == Key) |
| { |
| return pItem; |
| } |
| |
| // linear probing |
| // |
| if (pItem->IsDeleted() || pItem->IsUsed()) |
| { |
| ULONG n = 0; |
| BOOL bFound = FALSE; |
| BOOL bEnd = FALSE; |
| |
| do |
| { |
| n++; |
| lPos = Probe(lPos); |
| pItem = &m_pData[lPos]; |
| |
| bFound = pItem->IsUsed() |
| && pItem->GetKey() == Key; |
| |
| bEnd = !(n<m_lSize || pItem->IsFree()); |
| } |
| while(!bFound && !bEnd); |
| |
| return bFound ? pItem : NULL; |
| } |
| } |
| |
| // nicht gefunden |
| // |
| return NULL; |
| } |
| |
| void* HashTable::Find(String const& Key) const |
| // Gibt Verweis des Objektes zurück, das unter Key abgespeichert ist, |
| // oder NULL wenn nicht vorhanden. |
| // |
| // pre: - |
| // post: - |
| { |
| HashItem *pItem = FindPos(Key); |
| |
| if (pItem != NULL |
| && pItem->GetKey() == Key) |
| return pItem->GetObject(); |
| else |
| return NULL; |
| } |
| |
| void* HashTable::Delete(String const& Key) |
| // Löscht Objekt, das unter Key abgespeichert ist und gibt Verweis |
| // darauf zurück. |
| // Gibt NULL zurück, wenn Key nicht vorhanden ist. |
| // |
| // pre: - |
| // post: Objekt ist nicht mehr enthalten; m_lElem dekrementiert |
| // Wenn die HashTable der Owner ist, wurde das Object gelöscht |
| { |
| HashItem *pItem = FindPos(Key); |
| |
| if (pItem != NULL |
| && pItem->GetKey() == Key) |
| { |
| void* pObject = pItem->GetObject(); |
| |
| if (m_bOwner) |
| OnDeleteObject(pObject); |
| |
| pItem->Delete(); |
| m_lElem--; |
| return pObject; |
| } |
| else |
| { |
| return NULL; |
| } |
| } |
| |
| double HashTable::CalcLoadFactor() const |
| // prozentuale Belegung der Hashtabelle berechnen |
| { |
| return double(m_lElem) / double(m_lSize); |
| } |
| |
| void HashTable::SmartGrow() |
| // Achtung: da die Objekte umkopiert werden, darf die OnDeleteObject-Methode |
| // nicht gerufen werden |
| { |
| double dLoadFactor = CalcLoadFactor(); |
| |
| if (dLoadFactor <= m_dMaxLoadFactor) |
| return; // nothing to grow |
| |
| ULONG lOldSize = m_lSize; // alte Daten sichern |
| HashItem* pOldData = m_pData; |
| |
| m_lSize = ULONG (m_dGrowFactor * m_lSize); // neue Größe |
| m_pData = new HashItem[m_lSize]; // neue Daten holen |
| |
| // kein Speicher: |
| // Zustand "Tabelle voll" wird in Insert abgefangen |
| // |
| if (m_pData == NULL) |
| { |
| m_lSize = lOldSize; |
| m_pData = pOldData; |
| return; |
| } |
| |
| m_lElem = 0; // noch keine neuen Daten |
| |
| // Umkopieren der Daten |
| // |
| for (ULONG i=0; i<lOldSize; i++) |
| { |
| HashItem *pItem = &pOldData[i]; |
| |
| if (pItem->IsUsed()) |
| Insert(pItem->GetKey(),pItem->GetObject()); |
| } |
| |
| delete [] pOldData; |
| } |
| |
| // Iterator --------------------------------------------------------- |
| // |
| |
| HashTableIterator::HashTableIterator(HashTable const& aTable) |
| : m_aTable(aTable) |
| { |
| m_lAt = 0; |
| } |
| |
| void* HashTableIterator::GetFirst() |
| { |
| m_lAt = 0; |
| return FindValidObject(TRUE /* forward */); |
| } |
| |
| void* HashTableIterator::GetLast() |
| { |
| m_lAt = m_aTable.GetSize() -1; |
| return FindValidObject(FALSE /* backward */); |
| } |
| |
| void* HashTableIterator::GetNext() |
| { |
| if (m_lAt+1 >= m_aTable.GetSize()) |
| return NULL; |
| |
| m_lAt++; |
| return FindValidObject(TRUE /* forward */); |
| } |
| |
| void* HashTableIterator::GetPrev() |
| { |
| if (m_lAt <= 0) |
| return NULL; |
| |
| m_lAt--; |
| return FindValidObject(FALSE /* backward */); |
| } |
| |
| void* HashTableIterator::FindValidObject(BOOL bForward) |
| // Sucht nach einem vorhandenen Objekt ab der aktuellen |
| // Position. |
| // |
| // pre: ab inkl. m_lAt soll die Suche beginnen |
| // post: if not found then |
| // if bForward == TRUE then |
| // m_lAt == m_aTable.GetSize() -1 |
| // else |
| // m_lAt == 0 |
| // else |
| // m_lAt ist die gefundene Position |
| { |
| void *pObject = m_aTable.GetObjectAt(m_lAt); |
| |
| if (pObject != NULL) |
| return pObject; |
| |
| while (pObject == NULL |
| && (bForward ? ((m_lAt+1) < m_aTable.GetSize()) |
| : m_lAt > 0)) |
| { |
| if (bForward) |
| m_lAt++; |
| else |
| m_lAt--; |
| |
| pObject = m_aTable.GetObjectAt(m_lAt); |
| } |
| |
| #ifdef DBG_UTIL |
| |
| if (pObject == NULL) |
| { |
| DBG_ASSERT(bForward ? m_lAt == m_aTable.GetSize() -1 : m_lAt == 0, |
| "HashTableIterator::FindValidObject()"); |
| } |
| |
| #endif |
| |
| return pObject; |
| } |