!/*****************************************************************************/1! *2! * Elmer, A Finite Element Software for Multiphysical Problems3! *4! * Copyright 1st April 1995 - , CSC - IT Center for Science Ltd., Finland5! *6! * This library is free software; you can redistribute it and/or7! * modify it under the terms of the GNU Lesser General Public8! * License as published by the Free Software Foundation; either9! * version 2.1 of the License, or (at your option) any later version.10! *11! * This library is distributed in the hope that it will be useful,12! * but WITHOUT ANY WARRANTY; without even the implied warranty of13! * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU14! * Lesser General Public License for more details.15! *16! * You should have received a copy of the GNU Lesser General Public17! * License along with this library (in file ../LGPL-2.1); if not, write18! * to the Free Software Foundation, Inc., 51 Franklin Street,19! * Fifth Floor, Boston, MA 02110-1301 USA20! *21! *****************************************************************************/22!23!/******************************************************************************24! *25! * Authors: Juha Ruokolainen26! * Email: [email protected]27! * Web: http://www.csc.fi/elmer28! * Address: CSC - IT Center for Science Ltd.29! * Keilaranta 1430! * 02101 Espoo, Finland31! *32! * Original Date: 09 Aug 1998 (Original C-version) Genesis33! * Original Date: 25 Oct 2000 Fortran 90/95 version34! *35! *****************************************************************************/3637!> \ingroup ElmerLib38!> \{3940!-------------------------------------------------------41!> Hash table build & lookup routines.42!-------------------------------------------------------4344MODULE HashTable4546USE Lists4748IMPLICIT NONE4950TYPE HashValue_t51CHARACTER(LEN=MAX_NAME_LEN) :: Block,Type52END TYPE HashValue_t535455TYPE HashEntry_t56TYPE(HashEntry_t), POINTER :: Next57CHARACTER(LEN=MAX_NAME_LEN) :: Key58TYPE(HashValue_t), POINTER :: Value59END TYPE HashEntry_t606162TYPE HashBucket_t63TYPE(HashEntry_t), POINTER :: Head64END TYPE HashBucket_t656667TYPE HashTable_t68INTEGER :: BucketSize, CurrentBucket, TotalEntries69INTEGER :: MaxAvgEntries70TYPE(HashBucket_t), POINTER :: Bucket(:)71TYPE(HashEntry_t), POINTER :: CurrentEntry72END TYPE HashTable_t737475CONTAINS7677!-----------------------------------------------------------------------78!79! Call: TYPE(HashTable_t), POINTER hash = HashCreate( &80! InitialBucketSize, MaxAvgEntries, EqualKeys )81!82!> Initialize a hash table given initial bucket size. The size of the83!> bucket is rounded up to next power of two. The bucket doubles in size84!> whenever the size of the hash table grows over "MaxAvgEntries"85!> entries / bucket on the average. Keep the "MaxAvgEntries" small enough86!> (ordinarily from 3 entries up ?) to keep the hash table build & lookup87!> reasonably quick.88!89!> The hash table entries contain a "key" and an associated "value".90!> Currently the value structures are not copied to the hash table,91!> but only pointers to them are being stored. So one should not alter the92!> memory where the components are actually stored after adding them to the93!> hash table. Note that, potential problems in this respect are,94!> among others, automatic variables in functions. (C-Version: Bright side95!> is, that the "value" entries may be whatsoever as you manage them96!> yourself...)97!98! (Again, the C-version:99! The third and fourth arguments may be used to give user routines to100! compare equality of two keys and provide an index to the bucket array101! given key and array size respectively (these are the only place where102! any reference to the format of the keys is made). If given as NULL103! pointers, the default string comparison and hash functions routines104! respectively are implied. )105!106!-----------------------------------------------------------------------107FUNCTION HashCreate( InitialBucketSize, MaxAvgEntries ) RESULT(hash)108TYPE(HashTable_t), POINTER :: Hash109INTEGER :: InitialBucketSize, MaxAvgEntries110111INTEGER :: i, RoundBits, Stat112113NULLIFY( Hash )114IF ( InitialBucketSize <= 0 ) THEN115WRITE( Message, * ) 'HashCreate: invalid initial size given: ',InitialBucketSize116CALL Error( 'HashCreate', Message )117RETURN118END IF119120! /*121! * Round bucket size up to next largest power of two...122! */123RoundBits = CEILING( LOG(1.0d0*InitialBucketSize) / LOG(2.0d0) )124125! /*126! * Allocate the table and initialize the table entries...127! */128ALLOCATE( Hash )129130Hash % BucketSize = 2**RoundBits;131132! /*133! * Allocate the bucket array134! */135ALLOCATE( Hash % Bucket( Hash % BucketSize ), STAT=stat )136137IF ( stat /= 0 ) THEN138CALL Error( 'HashCreate', &139'Hash table initialize error: unable to allocate bucket.' )140DEALLOCATE( Hash )141NULLIFY( Hash )142143RETURN144END IF145146DO i=1,Hash % BucketSize147NULLIFY( Hash % Bucket(i) % Head )148END DO149150Hash % TotalEntries = 0151Hash % MaxAvgEntries = MaxAvgEntries;152153! /*154! * the key comparison routine155! */156! if ( EqualKeys )157! hash->EqualKeys = EqualKeys;158! else159! hash->EqualKeys = (int (*)(void *,void *))HashEqualKeys;160!161! /*162! * the hash table index generation routine163! */164! if ( HashFunc )165! hash->HashFunc = HashFunc;166! else167! hash->HashFunc = (int (*)(void *,int))HashStringFunc;168END FUNCTION HashCreate169170!--------------------------------------------------------------------------171! Call:index = HashStringFunc( key, mask )172!173!> Generate index to a hash table from given string. Hash table size174!> is assumed to be a power of two.175!--------------------------------------------------------------------------176FUNCTION HashStringFunc( key, size ) RESULT(Ind)177CHARACTER(LEN=*) :: key178INTEGER :: Size, Ind179180INTEGER :: i,keylen181182DO keylen=LEN(key),1,-1183IF ( key(keylen:keylen) /= ' ' ) EXIT184END DO185186Ind = 0187DO i=1,keylen188Ind = Ind*8 + ICHAR(key(i:i))189END DO190191Ind = IAND( Ind, size-1 ) + 1192END FUNCTION HashStringFunc193194!--------------------------------------------------------------------------195!196! Call: equal = HashEqualKeys( key1, key2 )197!198!> Return equality of given two strings.199!> This is for internal use only.200!--------------------------------------------------------------------------201FUNCTION HashEqualKeys( key1,key2 ) RESULT(equal)202203CHARACTER(LEN=*) :: key1,key2204LOGICAL :: equal205INTEGER :: n1,n2206207equal = .FALSE.208209n1 = LEN_TRIM(key1)210n2 = LEN_TRIM(key2)211IF ( n1 /= n2 ) RETURN212213equal = key1(1:n1) == key2(1:n1)214END FUNCTION HashEqualKeys215216!--------------------------------------------------------------------------217! Call: entry = HashFind( hash, key, bucket )218!219!> Search for a key from a hash table, return value is pointer to220!> the entry or NULL if not found. Bucket number of the entry221!> (if found) is given in int *bucket.222!> This is for internal use only.223!224!--------------------------------------------------------------------------225FUNCTION HashFind( hash, key, n ) RESULT( entry )226227TYPE(HashTable_t), POINTER :: Hash228TYPE(HashEntry_t), POINTER :: Entry, ptr229INTEGER :: n230CHARACTER(LEN=*) :: key231232! /*233! * Get bucket number for this key234! */235n = HashStringFunc( key, Hash % BucketSize )236237! /*238! * Now go through bucket entries, and check if it is to be found239! */240ptr => Hash % Bucket(n) % Head241DO WHILE( ASSOCIATED(ptr) )242IF ( HashEqualKeys( key, ptr % key ) ) EXIT243ptr => ptr % Next244END DO245Entry => ptr246END FUNCTION HashFind247248!--------------------------------------------------------------------------249! Call: HashAdd( HashTable_t *hash, void *key,void *value )250!251!> Add an entry to a hash table. If the key is already in the table252!> just change the "value" pointer.253!254!> The hash table entries contain a "key" and an associated "value".255!> Currently the (key) and value entries are not copied to the hash table,256!> but only pointers to them are being stored. So one should not alter the257!> memory where the components are actually stored after adding them to the258!> hash table. Note that, potential problems in this respect are, for259!> example, automatic variables in functions.260!>261! Return value is success or not...262!263!--------------------------------------------------------------------------264RECURSIVE FUNCTION HashAdd( hash, key, value ) RESULT(Success)265TYPE(HashTable_t), POINTER :: hash266CHARACTER(LEN=*) :: key267LOGICAL :: Success268TYPE(HashValue_t), POINTER :: Value269270INTEGER :: stat271INTEGER :: n, keylen, count = 0272TYPE(HashEntry_t), POINTER :: Entry273274Success = .TRUE.275276entry => HashFind( hash,key, n )277278IF ( ASSOCIATED( entry ) ) THEN279! /*280! * Already in, change the value pointer281! */282Entry % Value => Value283ELSE284! /*285! * not found add new...286! */287ALLOCATE( Entry, STAT=stat )288289IF ( stat /= 0 ) THEN290CALL Error( 'HashAdd', 'add failed: unable to allocate ' // &291'(a few bytes of) memory ?' )292RETURN293END IF294295Entry % Next => Hash % Bucket(n) % Head296Entry % Value => Value297Entry % Key = ' '298DO keylen=LEN(key),1,-1299IF ( key(keylen:keylen) /= ' ' ) EXIT300END DO301Entry % Key(1:keylen) = key(1:keylen)302303Hash % Bucket(n) % Head => Entry304Hash % TotalEntries = Hash % TotalEntries + 1305306IF ( Hash % TotalEntries > Hash % MaxAvgEntries*Hash % BucketSize ) THEN307Success = HashRebuild( Hash )308END IF309END IF310END FUNCTION HashAdd311312!--------------------------------------------------------------------------313! Call: HashRemove( HashTable_t *hash, void *key )314!315!> Remove an entry from a hash table given key of the entry.316!--------------------------------------------------------------------------317SUBROUTINE HashRemove( Hash, key )318TYPE(HashTable_t), POINTER :: Hash319CHARACTER(LEN=*) :: Key320321TYPE(HashEntry_t), POINTER :: entry,prev322INTEGER :: k,n323324IF ( .NOT. ASSOCIATED(hash) ) RETURN325326! /*327! * get bucket number for this key328! */329n = HashStringFunc( key, hash % BucketSize );330331! /*332! * Now go through bucket entries, and check if it's there333! */334NULLIFY( Prev )335Entry => Hash % Bucket(n) % Head336DO WHILE( ASSOCIATED( Entry ) )337! /*338! * if key in, remove339! */340IF ( HashEqualKeys( key,entry % key ) ) THEN341IF ( ASSOCIATED(prev) ) THEN342prev % next => entry % next343ELSE344hash % Bucket(n) % Head => Entry % next345END IF346347DEALLOCATE(Entry)348349hash % TotalEntries = Hash % TotalEntries - 1350RETURN351END IF352Prev => Entry353Entry => Entry % Next354END DO355END SUBROUTINE HashRemove356357!--------------------------------------------------------------------------358! Call: HashClean( HashTable_t *hash )359!360!> Clean all entries from the hash table, the bucket array is kept.361!> One may start refilling the hash table directly after cleaning.362!--------------------------------------------------------------------------363SUBROUTINE HashClean( hash )364TYPE(HashTable_t), POINTER :: Hash365366TYPE(HashEntry_t), POINTER :: ptr,ptr1367INTEGER :: i368369IF ( .NOT.ASSOCIATED(hash) ) RETURN370371DO i=1,hash % BucketSize372ptr => hash % Bucket(i) % Head373DO WHILE( ASSOCIATED(ptr) )374ptr1 => ptr % next375DEALLOCATE( ptr )376ptr => ptr1377END DO378NULLIFY( hash % Bucket(i) % Head )379END DO380hash % TotalEntries = 0381hash % CurrentBucket = 0382NULLIFY( hash % CurrentEntry )383END SUBROUTINE HashClean384385!--------------------------------------------------------------------------386! Call: HashDelete( HashTable_t *hash )387!388!> Delete a hash table by removing all the entries and freeing the389!> bucket and hash structures.390!--------------------------------------------------------------------------391SUBROUTINE HashDelete( Hash )392TYPE(HashTable_t), POINTER :: Hash393394IF ( ASSOCIATED( Hash ) ) THEN395IF ( ASSOCIATED( Hash % Bucket ) ) THEN396CALL HashClean( Hash )397DEALLOCATE( Hash % Bucket )398END IF399DEALLOCATE( Hash )400END IF401END SUBROUTINE HashDelete402403!--------------------------------------------------------------------------404! Call: HashRebuild( HashTable_t *hash )405!406!> Rebuild a hash table using a larger bucket array.407!> This is for internal use only.408!--------------------------------------------------------------------------409RECURSIVE FUNCTION HashRebuild( hash ) RESULT(Success)410TYPE(HashTable_t), POINTER :: Hash, NewHash411LOGICAL :: Success412413TYPE(HashEntry_t), POINTER :: entry414INTEGER :: i415416Success = .FALSE.417IF ( .NOT.ASSOCIATED(Hash) ) RETURN418419NewHash => HashCreate( 4*Hash % BucketSize, Hash % MaxAvgEntries )420IF ( .NOT. ASSOCIATED( Newhash ) ) RETURN421422DO i=1,Hash % BucketSize423Entry => Hash % Bucket(i) % Head424DO WHILE( ASSOCIATED( Entry ) )425IF ( .NOT. HashAdd( Newhash, Entry % Key, Entry % Value ) ) RETURN426Entry => Entry % Next427END DO428END DO429430CALL HashClean( Hash )431DEALLOCATE( Hash % Bucket )432433DEALLOCATE( Hash )434Hash => NewHash435Success = .TRUE.436END FUNCTION HashRebuild437438!--------------------------------------------------------------------------439! Call: void *value = HashValue( HashTable_t *hash, void *key )440!441!> Given a "key" to hash table return pointer to the "value" memory or442!> NULL if not found in the table.443!--------------------------------------------------------------------------444FUNCTION HashValue( Hash, key ) RESULT(Value)445TYPE(HashTable_t), POINTER :: Hash446CHARACTER(LEN=*) :: Key447TYPE(HashValue_t), POINTER :: Value448449INTEGER :: n450TYPE(HashEntry_t), POINTER :: Entry451452NULLIFY( Value )453Entry => HashFind( Hash, key, n )454IF ( ASSOCIATED( Entry ) ) Value => Entry % Value455END FUNCTION HashValue456457!--------------------------------------------------------------------------458! Call: HashInitWalk( HashTable_t *hash )459!460!> Initialize hash table walk through.461!--------------------------------------------------------------------------462SUBROUTINE HashInitWalk( Hash )463TYPE(HashTable_t), POINTER :: Hash464Hash % CurrentBucket = 0465NULLIFY( Hash % CurrentEntry )466END SUBROUTINE HashInitWalk467468!--------------------------------------------------------------------------469!! Call: HashEntry_t *entry = HashNext( HashTable_t *hash )470!!471!> Return pointer to "next" entry in a hash table. The walk must be472!> initialized with a call to HashInitWalk. The "key" and "value" of473!> the table entry may be referenced as follows:474!--------------------------------------------------------------------------475FUNCTION HashNext( Hash ) RESULT(Entry)476TYPE(HashTable_t), POINTER :: Hash477TYPE(HashEntry_t), POINTER :: Entry478479LOGICAL :: Current480481NULLIFY( Entry )482IF ( .NOT.ASSOCIATED(Hash) ) RETURN483484Current = .FALSE.485IF ( ASSOCIATED( Hash % CurrentEntry ) ) THEN486Current = ASSOCIATED( Hash % CurrentEntry % Next )487END IF488489IF ( Current ) THEN490Hash % CurrentEntry => Hash % CurrentEntry % Next491ELSE492Hash % CurrentBucket = Hash % CurrentBucket + 1493494DO WHILE( Hash % CurrentBucket < Hash % BucketSize .AND. &495.NOT.ASSOCIATED( Hash % Bucket(Hash % CurrentBucket) % Head) )496Hash % CurrentBucket = Hash % CurrentBucket + 1497END DO498499IF ( Hash % CurrentBucket >= Hash % BucketSize ) RETURN500501Hash % CurrentEntry => Hash % Bucket(Hash % CurrentBucket) % Head502END IF503504Entry => Hash % CurrentEntry505END FUNCTION HashNext506507!--------------------------------------------------------------------------508!! Call: void HashStats( HashTable_t *hash )509!!510!! Print info about the hash table organization.511!--------------------------------------------------------------------------512!void HashStats( HashTable_t *hash )513!{514! HashEntry_t *entry;515! int *BucketEntries;516! int n,i,j,MaxEntries=0,MinEntries=1<<30;517!518! for( i=0; i<hash->BucketSize; i++ )519! {520! n = 0;521! for( entry = hash->Bucket[i]; entry; entry=entry->next ) n++;522! MaxEntries = MAX( MaxEntries,n );523! MinEntries = MIN( MinEntries,n );524! }525!526! BucketEntries = (int *)calloc( MaxEntries+1,sizeof(int) );527!528! for( i=0; i<hash->BucketSize; i++ )529! {530! n = 0;531! for( entry = hash->Bucket[i]; entry; entry=entry->next ) n++;532! BucketEntries[n]++;533! }534!535! fprintf( stdout, "\n\nHash table statistics:\n\n" );536! fprintf( stdout, "Buckets: % 4d\n", hash->BucketSize );537! fprintf( stdout, "Entries: % 4d\n", hash->TotalEntries );538! fprintf( stdout, "Min / Bucket: %d\n", MinEntries );539! fprintf( stdout, "Max / Bucket: %d\n\n", MaxEntries );540!541!542! for( n=MinEntries; n<=MaxEntries; n++ )543! if ( BucketEntries[n] )544! fprintf( stdout, "% 4d entries in % 4d buckets.\n",545! n,BucketEntries[n] );546!547! free( BucketEntries );548!}549END MODULE HashTable550551552!> \}553554555