/**********************************************************************1*2* Name: tif_hash_set.c3* Purpose: Hash set functions.4* Author: Even Rouault, <even dot rouault at spatialys.com>5*6**********************************************************************7* Copyright (c) 2008-2009, Even Rouault <even dot rouault at spatialys.com>8*9* Permission is hereby granted, free of charge, to any person obtaining a10* copy of this software and associated documentation files (the "Software"),11* to deal in the Software without restriction, including without limitation12* the rights to use, copy, modify, merge, publish, distribute, sublicense,13* and/or sell copies of the Software, and to permit persons to whom the14* Software is furnished to do so, subject to the following conditions:15*16* The above copyright notice and this permission notice shall be included17* in all copies or substantial portions of the Software.18*19* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR20* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,21* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL22* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER23* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING24* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER25* DEALINGS IN THE SOFTWARE.26****************************************************************************/2728#include "tif_config.h"2930#include "tif_hash_set.h"3132#include <assert.h>33#include <stdbool.h>34#include <stdint.h>35#include <stdio.h>36#include <stdlib.h>3738/** List element structure. */39typedef struct _TIFFList TIFFList;4041/** List element structure. */42struct _TIFFList43{44/*! Pointer to the data object. Should be allocated and freed by the45* caller.46* */47void *pData;48/*! Pointer to the next element in list. NULL, if current element is the49* last one.50*/51struct _TIFFList *psNext;52};5354struct _TIFFHashSet55{56TIFFHashSetHashFunc fnHashFunc;57TIFFHashSetEqualFunc fnEqualFunc;58TIFFHashSetFreeEltFunc fnFreeEltFunc;59TIFFList **tabList;60int nSize;61int nIndiceAllocatedSize;62int nAllocatedSize;63TIFFList *psRecyclingList;64int nRecyclingListSize;65bool bRehash;66#ifdef HASH_DEBUG67int nCollisions;68#endif69};7071static const int anPrimes[] = {7253, 97, 193, 389, 769, 1543, 3079,736151, 12289, 24593, 49157, 98317, 196613, 393241,74786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653,75100663319, 201326611, 402653189, 805306457, 1610612741};7677/************************************************************************/78/* TIFFHashSetHashPointer() */79/************************************************************************/8081/**82* Hash function for an arbitrary pointer83*84* @param elt the arbitrary pointer to hash85*86* @return the hash value of the pointer87*/8889static unsigned long TIFFHashSetHashPointer(const void *elt)90{91return (unsigned long)(uintptr_t)((void *)(elt));92}9394/************************************************************************/95/* TIFFHashSetEqualPointer() */96/************************************************************************/9798/**99* Equality function for arbitrary pointers100*101* @param elt1 the first arbitrary pointer to compare102* @param elt2 the second arbitrary pointer to compare103*104* @return true if the pointers are equal105*/106107static bool TIFFHashSetEqualPointer(const void *elt1, const void *elt2)108{109return elt1 == elt2;110}111112/************************************************************************/113/* TIFFHashSetNew() */114/************************************************************************/115116/**117* Creates a new hash set118*119* The hash function must return a hash value for the elements to insert.120* If fnHashFunc is NULL, TIFFHashSetHashPointer will be used.121*122* The equal function must return if two elements are equal.123* If fnEqualFunc is NULL, TIFFHashSetEqualPointer will be used.124*125* The free function is used to free elements inserted in the hash set,126* when the hash set is destroyed, when elements are removed or replaced.127* If fnFreeEltFunc is NULL, elements inserted into the hash set will not be128* freed.129*130* @param fnHashFunc hash function. May be NULL.131* @param fnEqualFunc equal function. May be NULL.132* @param fnFreeEltFunc element free function. May be NULL.133*134* @return a new hash set135*/136137TIFFHashSet *TIFFHashSetNew(TIFFHashSetHashFunc fnHashFunc,138TIFFHashSetEqualFunc fnEqualFunc,139TIFFHashSetFreeEltFunc fnFreeEltFunc)140{141TIFFHashSet *set = (TIFFHashSet *)malloc(sizeof(TIFFHashSet));142if (set == NULL)143return NULL;144set->fnHashFunc = fnHashFunc ? fnHashFunc : TIFFHashSetHashPointer;145set->fnEqualFunc = fnEqualFunc ? fnEqualFunc : TIFFHashSetEqualPointer;146set->fnFreeEltFunc = fnFreeEltFunc;147set->nSize = 0;148set->tabList = (TIFFList **)(calloc(53, sizeof(TIFFList *)));149if (set->tabList == NULL)150{151free(set);152return NULL;153}154set->nIndiceAllocatedSize = 0;155set->nAllocatedSize = 53;156set->psRecyclingList = NULL;157set->nRecyclingListSize = 0;158set->bRehash = false;159#ifdef HASH_DEBUG160set->nCollisions = 0;161#endif162return set;163}164165/************************************************************************/166/* TIFFHashSetSize() */167/************************************************************************/168169/**170* Returns the number of elements inserted in the hash set171*172* Note: this is not the internal size of the hash set173*174* @param set the hash set175*176* @return the number of elements in the hash set177*/178179int TIFFHashSetSize(const TIFFHashSet *set)180{181assert(set != NULL);182return set->nSize;183}184185/************************************************************************/186/* TIFFHashSetGetNewListElt() */187/************************************************************************/188189static TIFFList *TIFFHashSetGetNewListElt(TIFFHashSet *set)190{191if (set->psRecyclingList)192{193TIFFList *psRet = set->psRecyclingList;194psRet->pData = NULL;195set->nRecyclingListSize--;196set->psRecyclingList = psRet->psNext;197return psRet;198}199200return (TIFFList *)malloc(sizeof(TIFFList));201}202203/************************************************************************/204/* TIFFHashSetReturnListElt() */205/************************************************************************/206207static void TIFFHashSetReturnListElt(TIFFHashSet *set, TIFFList *psList)208{209if (set->nRecyclingListSize < 128)210{211psList->psNext = set->psRecyclingList;212set->psRecyclingList = psList;213set->nRecyclingListSize++;214}215else216{217free(psList);218}219}220221/************************************************************************/222/* TIFFHashSetClearInternal() */223/************************************************************************/224225static void TIFFHashSetClearInternal(TIFFHashSet *set, bool bFinalize)226{227assert(set != NULL);228for (int i = 0; i < set->nAllocatedSize; i++)229{230TIFFList *cur = set->tabList[i];231while (cur)232{233if (set->fnFreeEltFunc)234set->fnFreeEltFunc(cur->pData);235TIFFList *psNext = cur->psNext;236if (bFinalize)237free(cur);238else239TIFFHashSetReturnListElt(set, cur);240cur = psNext;241}242set->tabList[i] = NULL;243}244set->bRehash = false;245}246247/************************************************************************/248/* TIFFListDestroy() */249/************************************************************************/250251/**252* Destroy a list. Caller responsible for freeing data objects contained in253* list elements.254*255* @param psList pointer to list head.256*257*/258259static void TIFFListDestroy(TIFFList *psList)260{261TIFFList *psCurrent = psList;262263while (psCurrent)264{265TIFFList *const psNext = psCurrent->psNext;266free(psCurrent);267psCurrent = psNext;268}269}270271/************************************************************************/272/* TIFFHashSetDestroy() */273/************************************************************************/274275/**276* Destroys an allocated hash set.277*278* This function also frees the elements if a free function was279* provided at the creation of the hash set.280*281* @param set the hash set282*/283284void TIFFHashSetDestroy(TIFFHashSet *set)285{286if (set)287{288TIFFHashSetClearInternal(set, true);289free(set->tabList);290TIFFListDestroy(set->psRecyclingList);291free(set);292}293}294295#ifdef notused296/************************************************************************/297/* TIFFHashSetClear() */298/************************************************************************/299300/**301* Clear all elements from a hash set.302*303* This function also frees the elements if a free function was304* provided at the creation of the hash set.305*306* @param set the hash set307*/308309void TIFFHashSetClear(TIFFHashSet *set)310{311TIFFHashSetClearInternal(set, false);312set->nIndiceAllocatedSize = 0;313set->nAllocatedSize = 53;314#ifdef HASH_DEBUG315set->nCollisions = 0;316#endif317set->nSize = 0;318}319320/************************************************************************/321/* TIFFHashSetForeach() */322/************************************************************************/323324/**325* Walk through the hash set and runs the provided function on all the326* elements327*328* This function is provided the user_data argument of TIFFHashSetForeach.329* It must return true to go on the walk through the hash set, or FALSE to330* make it stop.331*332* Note : the structure of the hash set must *NOT* be modified during the333* walk.334*335* @param set the hash set.336* @param fnIterFunc the function called on each element.337* @param user_data the user data provided to the function.338*/339340void TIFFHashSetForeach(TIFFHashSet *set, TIFFHashSetIterEltFunc fnIterFunc,341void *user_data)342{343assert(set != NULL);344if (!fnIterFunc)345return;346347for (int i = 0; i < set->nAllocatedSize; i++)348{349TIFFList *cur = set->tabList[i];350while (cur)351{352if (!fnIterFunc(cur->pData, user_data))353return;354355cur = cur->psNext;356}357}358}359#endif360361/************************************************************************/362/* TIFFHashSetRehash() */363/************************************************************************/364365static bool TIFFHashSetRehash(TIFFHashSet *set)366{367int nNewAllocatedSize = anPrimes[set->nIndiceAllocatedSize];368TIFFList **newTabList =369(TIFFList **)(calloc(nNewAllocatedSize, sizeof(TIFFList *)));370if (newTabList == NULL)371return false;372#ifdef HASH_DEBUG373TIFFDebug("TIFFHASH",374"hashSet=%p, nSize=%d, nCollisions=%d, "375"fCollisionRate=%.02f",376set, set->nSize, set->nCollisions,377set->nCollisions * 100.0 / set->nSize);378set->nCollisions = 0;379#endif380for (int i = 0; i < set->nAllocatedSize; i++)381{382TIFFList *cur = set->tabList[i];383while (cur)384{385const unsigned long nNewHashVal =386set->fnHashFunc(cur->pData) % nNewAllocatedSize;387#ifdef HASH_DEBUG388if (newTabList[nNewHashVal])389set->nCollisions++;390#endif391TIFFList *psNext = cur->psNext;392cur->psNext = newTabList[nNewHashVal];393newTabList[nNewHashVal] = cur;394cur = psNext;395}396}397free(set->tabList);398set->tabList = newTabList;399set->nAllocatedSize = nNewAllocatedSize;400set->bRehash = false;401return true;402}403404/************************************************************************/405/* TIFFHashSetFindPtr() */406/************************************************************************/407408static void **TIFFHashSetFindPtr(TIFFHashSet *set, const void *elt)409{410const unsigned long nHashVal = set->fnHashFunc(elt) % set->nAllocatedSize;411TIFFList *cur = set->tabList[nHashVal];412while (cur)413{414if (set->fnEqualFunc(cur->pData, elt))415return &cur->pData;416cur = cur->psNext;417}418return NULL;419}420421/************************************************************************/422/* TIFFHashSetInsert() */423/************************************************************************/424425/**426* Inserts an element into a hash set.427*428* If the element was already inserted in the hash set, the previous429* element is replaced by the new element. If a free function was provided,430* it is used to free the previously inserted element431*432* @param set the hash set433* @param elt the new element to insert in the hash set434*435* @return true if success. If false is returned, elt has not been inserted,436* but TIFFHashSetInsert() will have run the free function if provided.437*/438439bool TIFFHashSetInsert(TIFFHashSet *set, void *elt)440{441assert(set != NULL);442void **pElt = TIFFHashSetFindPtr(set, elt);443if (pElt)444{445if (set->fnFreeEltFunc)446set->fnFreeEltFunc(*pElt);447448*pElt = elt;449return true;450}451452if (set->nSize >= 2 * set->nAllocatedSize / 3 ||453(set->bRehash && set->nIndiceAllocatedSize > 0 &&454set->nSize <= set->nAllocatedSize / 2))455{456set->nIndiceAllocatedSize++;457if (!TIFFHashSetRehash(set))458{459set->nIndiceAllocatedSize--;460if (set->fnFreeEltFunc)461set->fnFreeEltFunc(elt);462return false;463}464}465466const unsigned long nHashVal = set->fnHashFunc(elt) % set->nAllocatedSize;467#ifdef HASH_DEBUG468if (set->tabList[nHashVal])469set->nCollisions++;470#endif471472TIFFList *new_elt = TIFFHashSetGetNewListElt(set);473if (new_elt == NULL)474{475if (set->fnFreeEltFunc)476set->fnFreeEltFunc(elt);477return false;478}479new_elt->pData = elt;480new_elt->psNext = set->tabList[nHashVal];481set->tabList[nHashVal] = new_elt;482set->nSize++;483484return true;485}486487/************************************************************************/488/* TIFFHashSetLookup() */489/************************************************************************/490491/**492* Returns the element found in the hash set corresponding to the element to493* look up The element must not be modified.494*495* @param set the hash set496* @param elt the element to look up in the hash set497*498* @return the element found in the hash set or NULL499*/500501void *TIFFHashSetLookup(TIFFHashSet *set, const void *elt)502{503assert(set != NULL);504void **pElt = TIFFHashSetFindPtr(set, elt);505if (pElt)506return *pElt;507508return NULL;509}510511/************************************************************************/512/* TIFFHashSetRemoveInternal() */513/************************************************************************/514515static bool TIFFHashSetRemoveInternal(TIFFHashSet *set, const void *elt,516bool bDeferRehash)517{518assert(set != NULL);519if (set->nIndiceAllocatedSize > 0 && set->nSize <= set->nAllocatedSize / 2)520{521set->nIndiceAllocatedSize--;522if (bDeferRehash)523set->bRehash = true;524else525{526if (!TIFFHashSetRehash(set))527{528set->nIndiceAllocatedSize++;529return false;530}531}532}533534int nHashVal = (int)(set->fnHashFunc(elt) % set->nAllocatedSize);535TIFFList *cur = set->tabList[nHashVal];536TIFFList *prev = NULL;537while (cur)538{539if (set->fnEqualFunc(cur->pData, elt))540{541if (prev)542prev->psNext = cur->psNext;543else544set->tabList[nHashVal] = cur->psNext;545546if (set->fnFreeEltFunc)547set->fnFreeEltFunc(cur->pData);548549TIFFHashSetReturnListElt(set, cur);550#ifdef HASH_DEBUG551if (set->tabList[nHashVal])552set->nCollisions--;553#endif554set->nSize--;555return true;556}557prev = cur;558cur = cur->psNext;559}560return false;561}562563/************************************************************************/564/* TIFFHashSetRemove() */565/************************************************************************/566567/**568* Removes an element from a hash set569*570* @param set the hash set571* @param elt the new element to remove from the hash set572*573* @return true if the element was in the hash set574*/575576bool TIFFHashSetRemove(TIFFHashSet *set, const void *elt)577{578return TIFFHashSetRemoveInternal(set, elt, false);579}580581#ifdef notused582/************************************************************************/583/* TIFFHashSetRemoveDeferRehash() */584/************************************************************************/585586/**587* Removes an element from a hash set.588*589* This will defer potential rehashing of the set to later calls to590* TIFFHashSetInsert() or TIFFHashSetRemove().591*592* @param set the hash set593* @param elt the new element to remove from the hash set594*595* @return true if the element was in the hash set596*/597598bool TIFFHashSetRemoveDeferRehash(TIFFHashSet *set, const void *elt)599{600return TIFFHashSetRemoveInternal(set, elt, true);601}602#endif603604605