// Microsoft Windows // Copyright (C) Microsoft Corporation, 1992 - 1994. // File: strhash.cxx // Contents: A hash table for strings // History: 7-Nov-94 BruceFo Created #include "headers.hxx" #pragma hdrstop #include "strhash.hxx" const DWORD g_cMinElemsRehash = 3; const DWORD g_cMinBuckets = 13; // Methods for the CStrHashBucketElem class CStrHashBucketElem::CStrHashBucketElem( IN const WCHAR* pszKey ) : m_pszKey(pszKey), m_next(NULL) { INIT_SIG(CStrHashBucketElem); } CStrHashBucketElem::~CStrHashBucketElem() { CHECK_SIG(CStrHashBucketElem); } BOOL CStrHashBucketElem::IsEqual( IN const WCHAR* pszKey ) { CHECK_SIG(CStrHashBucketElem); appAssert(NULL != pszKey); appAssert(NULL != m_pszKey); // NOTE: case-insensitive compare. This affects the hash function! return (0 == _wcsicmp(pszKey, m_pszKey)); } // Methods for the CStrHashBucket class CStrHashBucket::CStrHashBucket( VOID ) : m_head(NULL) { INIT_SIG(CStrHashBucket); } CStrHashBucket::~CStrHashBucket( VOID ) { CHECK_SIG(CStrHashBucket); for (CStrHashBucketElem* x = m_head; NULL != x; ) { CStrHashBucketElem* tmp = x->m_next; delete x; x = tmp; } } HRESULT CStrHashBucket::Insert( IN const WCHAR* pszKey ) { CHECK_SIG(CStrHashBucket); appAssert(NULL != pszKey); CStrHashBucketElem* x = new CStrHashBucketElem(pszKey); if (NULL == x) { return E_OUTOFMEMORY; } x->m_next = m_head; m_head = x; return S_OK; } // return TRUE if it was found and removed, FALSE if it wasn't even found BOOL CStrHashBucket::Remove( IN const WCHAR* pszKey ) { CHECK_SIG(CStrHashBucket); appAssert(NULL != pszKey); CStrHashBucketElem** pPrev = &m_head; for (CStrHashBucketElem* x = m_head; NULL != x; x = x->m_next) { if (x->IsEqual(pszKey)) // found it { *pPrev = x->m_next; delete x; return TRUE; } pPrev = &x->m_next; } return FALSE; // didn't find it } BOOL CStrHashBucket::IsMember( IN const WCHAR* pszKey ) { CHECK_SIG(CStrHashBucket); appAssert(NULL != pszKey); for (CStrHashBucketElem* x = m_head; NULL != x; x = x->m_next) { if (x->IsEqual(pszKey)) { return TRUE; // found it } } return FALSE; // didn't find it } // Methods for the CStrHashTable class CStrHashTable::CStrHashTable( IN DWORD cNumBuckets ) : m_cElems(0), m_cMinNumBuckets(cNumBuckets) { INIT_SIG(CStrHashTable); m_cNumBuckets = max(cNumBuckets, g_cMinBuckets); m_ht = new CStrHashBucket[m_cNumBuckets]; if (NULL == m_ht) { appDebugOut((DEB_ERROR, "Failed to allocate hash table with %d buckets\n", m_cNumBuckets)); m_cMinNumBuckets = 0; m_cNumBuckets = 0; } } HRESULT CStrHashTable::QueryError( VOID ) { if (NULL == m_ht) { return E_OUTOFMEMORY; } return S_OK; } CStrHashTable::~CStrHashTable( VOID ) { CHECK_SIG(CStrHashTable); delete[] m_ht; } HRESULT CStrHashTable::Insert( IN const WCHAR* pszKey ) { CHECK_SIG(CStrHashTable); if (NULL == pszKey) { return E_INVALIDARG; } appAssert(NULL != m_ht); DWORD key = HashFunction(pszKey); if (!(m_ht[key].IsMember(pszKey))) { // only insert if the key isn't already in the table. HRESULT hr = m_ht[key].Insert(pszKey); CHECK_HRESULT(hr); if (FAILED(hr)) { return hr; } ++m_cElems; return CheckRehash(); } return S_OK; } HRESULT CStrHashTable::Remove( IN const WCHAR* pszKey ) { CHECK_SIG(CStrHashTable); if (NULL == pszKey) { return E_INVALIDARG; } appAssert(NULL != m_ht); if (m_ht[HashFunction(pszKey)].Remove(pszKey)) { --m_cElems; return CheckRehash(); } return S_OK; // key was not found and hence not deleted } BOOL CStrHashTable::IsMember( IN const WCHAR* pszKey ) { CHECK_SIG(CStrHashTable); if (NULL == pszKey) { return FALSE; // invalid argument, really } appAssert(NULL != m_ht); return m_ht[HashFunction(pszKey)].IsMember(pszKey); } DWORD CStrHashTable::Count( VOID ) { CHECK_SIG(CStrHashTable); return m_cElems; } // This returns the iteration data, or NULL on failure CIterateData* CStrHashTable::IterateStart( VOID ) { CHECK_SIG(CStrHashTable); CIterateData* pData = new CIterateData; if (NULL != pData) { IterateGetNext(pData); } return pData; } const WCHAR* CStrHashTable::IterateGetData( IN OUT CIterateData* pCurrent ) { CHECK_SIG(CStrHashTable); appAssert(NULL != pCurrent); appAssert(ITERATE_END != pCurrent->m_CurrentBucket); return pCurrent->m_pCurrentElem->m_pszKey; } BOOL CStrHashTable::IterateDone( IN CIterateData* pCurrent ) { CHECK_SIG(CStrHashTable); appAssert(NULL != pCurrent); return (ITERATE_END == pCurrent->m_CurrentBucket); } VOID CStrHashTable::IterateEnd( IN CIterateData* pCurrent ) { CHECK_SIG(CStrHashTable); delete pCurrent; } VOID CStrHashTable::IterateGetNext( IN OUT CIterateData* pCurrent ) { CHECK_SIG(CStrHashTable); appAssert(NULL != pCurrent); appAssert(ITERATE_END != pCurrent->m_CurrentBucket); if (NULL != pCurrent->m_pCurrentElem) { if (NULL != pCurrent->m_pCurrentElem->m_next) { // just get next element in bucket pCurrent->m_pCurrentElem = pCurrent->m_pCurrentElem->m_next; } else { // need to search to new bucket ++pCurrent->m_CurrentBucket; IterateGetBucket(pCurrent); } } else { IterateGetBucket(pCurrent); } } VOID CStrHashTable::IterateGetBucket( IN OUT CIterateData* pCurrent ) { CHECK_SIG(CStrHashTable); appAssert(NULL != m_ht); for (DWORD i = pCurrent->m_CurrentBucket; i < m_cNumBuckets; i++) { if (NULL != m_ht[i].m_head) { pCurrent->m_pCurrentElem = m_ht[i].m_head; pCurrent->m_CurrentBucket = i; return; } } pCurrent->m_CurrentBucket = ITERATE_END; // we've iterated through all! } DWORD CStrHashTable::HashFunction( IN const WCHAR* pszKey ) { CHECK_SIG(CStrHashTable); appAssert(NULL != pszKey); appAssert(m_cNumBuckets > 0); DWORD total = 0; for (const WCHAR* p = pszKey; L'\0' != *p; p++) { // lower case it, so case-insensitive IsEqual works total += towlower(*p); } return total % m_cNumBuckets; } HRESULT CStrHashTable::CheckRehash( VOID ) { CHECK_SIG(CStrHashTable); if (m_cElems > g_cMinElemsRehash && m_cElems > m_cMinNumBuckets) { if ( (m_cElems > m_cNumBuckets) || (m_cElems < m_cNumBuckets / 4) ) { // add one to at least make it odd (for better hashing behavior) return Rehash(m_cElems * 2 + 1); } } return S_OK; } HRESULT CStrHashTable::Rehash( IN DWORD cNumBuckets ) { CHECK_SIG(CStrHashTable); cNumBuckets = max(cNumBuckets, g_cMinBuckets); CStrHashBucket* ht = new CStrHashBucket[cNumBuckets]; if (NULL == ht) { // return error, but don't delete the existing table return E_OUTOFMEMORY; } appAssert(NULL != m_ht); // now transfer all data from old to new ULONG cOldNumBuckets = m_cNumBuckets; m_cNumBuckets = cNumBuckets; // set this so HashFunction() uses it for (ULONG i=0; i < cOldNumBuckets; i++) { for (CStrHashBucketElem* x = m_ht[i].m_head; NULL != x; ) { CStrHashBucketElem* tmp = x->m_next; // now, just put this bucket in the right place in the new // hash table. Avoid performing new's and copying the keys. DWORD bucket = HashFunction(x->m_pszKey); x->m_next = ht[bucket].m_head; ht[bucket].m_head = x; x = tmp; } m_ht[i].m_head = NULL; // there isn't anything left in the list! } // the data is transfered; complete the switchover delete[] m_ht; m_ht = ht; return S_OK; }