diff --git a/qse/include/qse/cmn/HashList.hpp b/qse/include/qse/cmn/HashList.hpp new file mode 100644 index 00000000..c9a990cd --- /dev/null +++ b/qse/include/qse/cmn/HashList.hpp @@ -0,0 +1,507 @@ +/* + * $Id$ + * + Copyright (c) 2006-2014 Chung, Hyung-Hwan. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QSE_CMN_HASHLIST_CLASS_ +#define _QSE_CMN_HASHLIST_CLASS_ + +#include +#include + +///////////////////////////////// +QSE_BEGIN_NAMESPACE(QSE) +///////////////////////////////// + +template +struct HashListHasher +{ + qse_size_t operator() (const T& v) const + { + return v.hashCode(); + } +}; + +template +struct HashListComparator +{ + bool operator() (const T& v1, const T& v2) const + { + return v1 == v2; + } +}; + +// TODO: use MPOLL for nodes??? + +template , typename COMPARATOR = HashListComparator > +class HashList +{ +public: + typedef LinkedList DatumList; + typedef typename DatumList::Node Node; + typedef HashList SelfType; + + HashList ( + qse_size_t node_capacity = 10, + qse_size_t load_factor = 75, + qse_size_t mpb_size = 0)/*: datum_list (mpb_size)*/ + { + this->nodes = QSE_NULL; + this->node_capacity = 0; + this->datum_list = QSE_NULL; + + try + { + this->nodes = new Node*[node_capacity << 1]; + + this->node_capacity = node_capacity; + for (qse_size_t i = 0; i < (node_capacity << 1); i++) + { + this->nodes[i] = QSE_NULL; + } + + this->datum_list = new DatumList (mpb_size); + } + catch (...) + { + if (this->nodes) + { + delete[] this->nodes; + this->node_capacity = 0; + this->nodes = QSE_NULL; + } + + if (this->datum_list) + { + delete this->datum_list; + this->datum_list = QSE_NULL; + } + + throw; + } + + this->load_factor = load_factor; + this->threshold = node_capacity * load_factor / 100; + } + + HashList (const SelfType& list)/*: datum_list (list.datum_list.getMPBlockSize()) */ + { + this->nodes = QSE_NULL; + this->node_capacity = 0; + this->datum_list = QSE_NULL; + + try + { + this->nodes = new Node*[list.node_capacity << 1]; + this->node_capacity = list.node_capacity; + for (qse_size_t i = 0; i < list.node_capacity << 1; i++) + { + this->nodes[i] = QSE_NULL; + } + + this->datum_list = new DatumList (list.datum_list->getMPBlockSize()); + } + catch (...) + { + if (this->nodes) + { + delete[] this->nodes; + this->node_capacity = 0; + this->nodes = QSE_NULL; + } + if (this->datum_list) + { + delete this->datum_list; + this->datum_list = QSE_NULL; + } + + throw; + } + + this->load_factor = list.load_factor; + this->threshold = list.threshold; + + for (qse_size_t i = 0; i < list.node_capacity; i++) + { + qse_size_t head = i << 1; + qse_size_t tail = head + 1; + + Node* np = list.nodes[head]; + if (!np) continue; + + do + { + copy_datum (np, this->node_capacity, this->nodes, this->datum_list); + if (np == list.nodes[tail]) break; + np = np->getNextNode (); + } + while (1); + } + } + + ~HashList () + { + this->clear (); + if (this->nodes) delete[] this->nodes; + if (this->datum_list) delete this->datum_list; + } + + SelfType& operator= (const SelfType& list) + { + this->clear (); + + for (qse_size_t i = 0; i < list.node_capacity; i++) + { + qse_size_t head = i << 1; + qse_size_t tail = head + 1; + + Node* np = list.nodes[head]; + if (np == QSE_NULL) continue; + + do + { + copy_datum (np, this->node_capacity, this->nodes, this->datum_list); + if (np == list.nodes[tail]) break; + np = np->getNextNode (); + } + while (1); + } + + return *this; + } + +protected: + Node* insert_value (const T& datum, bool overwrite = true) + { + qse_size_t hc, head, tail; + Node* np; + + hc = this->hasher(datum) % this->node_capacity; + head = hc << 1; tail = head + 1; + + np = this->nodes[head]; + if (np) + { + do + { + T& t = np->value; + if (this->comparator(datum, t)) + { + if (!overwrite) return QSE_NULL; + t = datum; + return np; + } + + if (np == this->nodes[tail]) break; + np = np->getNextNode (); + } + while (1); + } + + if (datum_list->getSize() >= threshold) + { + this->rehash (); + hc = this->hasher(datum) % this->node_capacity; + head = hc << 1; tail = head + 1; + } + + if (nodes[head] == QSE_NULL) + { + this->nodes[head] = this->datum_list->insertValue (QSE_NULL, datum); + this->nodes[tail] = this->nodes[head]; + } + else + { + this->nodes[head] = datum_list->insertValue (this->nodes[head], datum); + } + + return this->nodes[head]; + } + +public: + Node* insert (const T& datum) + { + return this->insert_value (datum, false); + } + + Node* upsert (const T& datum) + { + return this->insert_value (datum, true); + } + +protected: + const Node* find_node (const T& datum) const + { + qse_size_t hc, head, tail; + Node* np; + + hc = this->hasher(datum) % this->node_capacity; + head = hc << 1; tail = head + 1; + + np = this->nodes[head]; + if (np) + { + do + { + T& t = np->value; + if (datum == t) return np; + if (np == this->nodes[tail]) break; + np = np->getNextNode (); + } + while (1); + } + + return QSE_NULL; + } + +public: + Node* findNode (const T& datum) + { + return (Node*)this->find_node (datum); + } + + const Node* findNode (const T& datum) const + { + return this->find_node (datum); + } + + T* findValue (const T& datum) + { + Node* b = this->findNode (datum); + if (!b) return QSE_NULL; + return &b->value; + } + + const T* findValue (const T& datum) const + { + const Node* b = this->findNode (datum); + if (!b) return QSE_NULL; + return &b->value; + } + + bool isEmpty () const + { + return this->datum_list->isEmpty(); + } + + bool contains (const T& datum) const + { + return this->findNode (datum) != QSE_NULL; + } + + int remove (const T& datum) + { + qse_size_t hc, head, tail; + Node* np; + + hc = this->hasher(datum) % this->node_capacity; + head = hc << 1; tail = head + 1; + + np = this->nodes[head]; + if (np) + { + do + { + T& t = np->value; + if (this->comparator(datum, t)) + { + if (this->nodes[head] == this->nodes[tail]) + { + QSE_ASSERT (np == this->nodes[head]); + this->nodes[head] = this->nodes[tail] = QSE_NULL; + } + else if (np == this->nodes[head]) + { + this->nodes[head] = np->getNextNode(); + } + else if (np == this->nodes[tail]) + { + this->nodes[tail] = np->getPrevNode(); + } + + this->datum_list->remove (np); + return 0; + } + + if (np == this->nodes[tail]) break; + np = np->getNextNode (); + } + while (1); + } + + return -1; + } + + void clear () + { + for (qse_size_t i = 0; i < (this->node_capacity << 1); i++) + { + this->nodes[i] = QSE_NULL; + } + if (this->datum_list) this->datum_list->clear (); + } + + Node* getHeadNode () const + { + return datum_list->getHeadNode(); + } + + Node* getTaileNode () const + { + return datum_list->getTailNode(); + } + + typedef int (SelfType::*TraverseCallback) (Node* start, Node* cur); + + void traverse (TraverseCallback callback, Node* start) + { + Node* cur, *prev, * next; + + cur = start; + while (cur) + { + prev = cur->getPrevNode (); + next = cur->getNextNode (); + + int n = (this->*callback) (start, cur); + + if (n > 0) cur = next; + else if (n < 0) cur = prev; + else break; + } + } + + qse_size_t getCapacity() const + { + return this->node_capacity; + } + + qse_size_t getSize () const + { + return this->datum_list->getSize(); + } + +protected: + mutable qse_size_t node_capacity; + mutable Node** nodes; + mutable DatumList* datum_list; + mutable qse_size_t threshold; + qse_size_t load_factor; + HASHER hasher; + COMPARATOR comparator; + + void rehash () + { + // Move nodes around instead of values to prevent + // existing values from being copied over and destroyed. + // this incurs less number of memory allocations also. + SelfType temp (this->node_capacity << 1, this->load_factor, this->datum_list->getMPBlockSize()); + Node* p = this->datum_list->getHeadNode(); + while (p) + { + Node* next = p->getNextNode(); + + // p->value must be a unique value in the existing hashed list. + // i can safely skip checking existing values. + + // detach the existing node. + Node* pp = this->datum_list->yield (p, true); + + // get the hash code using the new capacity + qse_size_t hc, head, tail; + hc = this->hasher(pp->value) % temp.node_capacity; + head = hc << 1; tail = head + 1; + + // insert the detached node to the new temporary list + if (temp.nodes[head]) + { + temp.nodes[head] = temp.datum_list->insertNode (temp.nodes[head], pp); + } + else + { + temp.nodes[head] = temp.datum_list->insertNode (QSE_NULL, pp); + temp.nodes[tail] = temp.nodes[head]; + } + + p = next; + } + + // all nodes must have been popped out. + QSE_ASSERT (this->datum_list->getSize() <= 0); + + // clear the node pointers by force as no nodes exist in the old list. + for (qse_size_t i = 0; i < (this->node_capacity << 1); i++) + { + this->nodes[i] = QSE_NULL; + } + + // swap the contents + qse_size_t temp_capa = temp.node_capacity; + Node** temp_nodes = temp.nodes; + DatumList* temp_datum_list = temp.datum_list; + qse_size_t temp_threshold = temp.threshold; + qse_size_t temp_load_factor = temp.load_factor; + + temp.node_capacity = this->node_capacity; + temp.nodes = this->nodes; + temp.datum_list = this->datum_list; + temp.threshold = this->threshold; + temp.load_factor = this->load_factor; + + this->node_capacity = temp_capa; + this->nodes = temp_nodes; + this->datum_list = temp_datum_list; + this->threshold = temp_threshold; + this->load_factor = temp_load_factor; + } + + void copy_datum ( + Node* np, qse_size_t new_node_capa, + Node** new_nodes, DatumList* new_datum_list) const + { + T& t = np->value; + + qse_size_t hc = this->hasher(t) % new_node_capa; + qse_size_t head = hc << 1; + qse_size_t tail = head + 1; + + if (new_nodes[head] == QSE_NULL) + { + new_nodes[head] = new_datum_list->insertValue (QSE_NULL, t); + new_nodes[tail] = new_nodes[head]; + } + else + { + new_nodes[head] = new_datum_list->insertValue (new_nodes[head], t); + } + } + +}; + + +///////////////////////////////// +QSE_END_NAMESPACE(QSE) +///////////////////////////////// + +#endif diff --git a/qse/include/qse/cmn/LinkedList.hpp b/qse/include/qse/cmn/LinkedList.hpp index efdb1e86..038ab321 100644 --- a/qse/include/qse/cmn/LinkedList.hpp +++ b/qse/include/qse/cmn/LinkedList.hpp @@ -141,7 +141,7 @@ public: qse_size_t getMPBlockSize() const { - return this->mp.blockSize(); + return this->mp.getBlockSize(); } bool isMPEnabled () const @@ -280,7 +280,7 @@ public: QSE_ASSERT (this->node_count > 0); if (node->next) - node->next->prev = node->next; + node->next->prev = node->prev; else this->tail_node = node->prev; diff --git a/qse/include/qse/cmn/Makefile.am b/qse/include/qse/cmn/Makefile.am index 66578dac..dd81f6aa 100644 --- a/qse/include/qse/cmn/Makefile.am +++ b/qse/include/qse/cmn/Makefile.am @@ -52,6 +52,6 @@ pkginclude_HEADERS = \ if ENABLE_CXX pkginclude_HEADERS += \ Mmgr.hpp StdMmgr.hpp ExcMmgr.hpp Mmged.hpp Mpool.hpp Mpoolable.hpp \ - LinkedList.hpp + LinkedList.hpp HashList.hpp endif diff --git a/qse/include/qse/cmn/Makefile.in b/qse/include/qse/cmn/Makefile.in index 1050dc8f..6bb62229 100644 --- a/qse/include/qse/cmn/Makefile.in +++ b/qse/include/qse/cmn/Makefile.in @@ -52,7 +52,7 @@ build_triplet = @build@ host_triplet = @host@ @ENABLE_CXX_TRUE@am__append_1 = \ @ENABLE_CXX_TRUE@ Mmgr.hpp StdMmgr.hpp ExcMmgr.hpp Mmged.hpp Mpool.hpp Mpoolable.hpp \ -@ENABLE_CXX_TRUE@ LinkedList.hpp +@ENABLE_CXX_TRUE@ LinkedList.hpp HashList.hpp subdir = include/qse/cmn DIST_COMMON = $(am__pkginclude_HEADERS_DIST) $(srcdir)/Makefile.am \ @@ -91,7 +91,7 @@ am__pkginclude_HEADERS_DIST = alg.h chr.h cp949.h cp950.h dir.h dll.h \ nwio.h oht.h opt.h path.h pio.h pma.h rbt.h rex.h sck.h sio.h \ sll.h slmb.h str.h task.h time.h tio.h tmr.h tre.h uni.h uri.h \ utf8.h xma.h Mmgr.hpp StdMmgr.hpp ExcMmgr.hpp Mmged.hpp \ - Mpool.hpp Mpoolable.hpp LinkedList.hpp + Mpool.hpp Mpoolable.hpp LinkedList.hpp HashList.hpp am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; am__vpath_adj = case $$p in \ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \