Dictionaries and General Hash Tables

People and computers spend a large amount of time with searching. Dictionaries are an abstract data structure which facilitates searching for objects. Depending on the kind of objects the implementation will use a variety of possible internal storage methods that will aim to provide the fastest possible access to objects. These internal methods include

Hash Tables
for objects for which a hash function has been defined.
Direct Indexing
if the domain is small and cheaply enumerable
Sorted Lists
if a total order can be computed easily
Plain lists
for objects for which nothing but an equality test is available.
Using Dictionaries

The standard way to use dictionaries is to first create a dictionary (using , and then to store objects (and associated information) in it and look them up.

For the creation of objects the user has to make a few choices: Is the dictionary only to be used to check whether objects are known already, or whether associated information is to be stored with the objects. This second case is called a lookup dictionary and is selected by the second parameter of .

The second choice is to indicate which kind of objects are to be stored. This choice will decide the internal storage used. This kind of objects is specified by the first parameter to , which is a sample object.

In some cases however such a sample object is not specific enough. For example when storing vectors over a finite field, it would not be clear whether all vectors will be over a prime field or over a field extension. Such an issue can be resolved by indicating in an (optional) third parameter to a domain which has to be a collection that will contain all objects to be used with this dictionary. (Such a domain may also be used internally to decide that direct indexing can be used).

The reason for this choice of giving two parameters is that in some cases no suitable collection of objects has been defined in &GAP; - for example for permutations there is no object representing the symmetric group on infinitely many points.

Once a dictionary has been created, it is possible to use to check which representation is used by &GAP;.

In the following example, we create a dictionary to store permutations with associated information. gap> d:=NewDictionary((1,2,3),true);; gap> AddDictionary(d,(1,2),1); gap> AddDictionary(d,(5,6),9); gap> AddDictionary(d,(4,7),2); gap> LookupDictionary(d,(5,6)); 9 gap> LookupDictionary(d,(5,7)); fail A typical example of this use would be in an orbit algorithm. The dictionary would be used to store the elements known in the orbit together with their respective orbit positions.

We observe that this dictionary is stored internally by a sorted list. On the other hand, if we have an explicit, sorted element list, direct indexing is to be used. gap> RepresentationsOfObject(d); [ "IsComponentObjectRep", "IsDictionaryDefaultRep", "IsListDictionary", "IsListLookupDictionary", "IsSortDictionary", "IsSortLookupDictionary" ] gap> d:=NewDictionary((1,2,3),true,Elements(SymmetricGroup(5)));; gap> RepresentationsOfObject(d); [ "IsComponentObjectRep", "IsDictionaryDefaultRep", "IsPositionDictionary", "IsPositionDictionary" ] (Just indicating SymmetricGroup(5) as a third parameter would still keep the first storage method, as indexing would be too expensive if no explicit element list is known.)

The same effect happens in the following example, in which we work with vectors: Indicating only a vector only enables sorted index, as it cannot be known whether all vectors will be defined over the prime field. On the other hand, providing the vector space (and thus limiting the domain) enables the use of hashing (which will be faster). gap> v:=GF(2)^7;; gap> d:=NewDictionary(Zero(v),true);; gap> RepresentationsOfObject(d); [ "IsComponentObjectRep", "IsDictionaryDefaultRep", "IsListDictionary", "IsListLookupDictionary", "IsSortDictionary", "IsSortLookupDictionary" ] gap> d:=NewDictionary(Zero(v),true,v);; gap> RepresentationsOfObject(d); [ "IsComponentObjectRep", "IsDictionaryDefaultRep", "IsPositionDictionary", "IsPositionDictionary" ]

Dictionaries

This section contains the formal declarations for dictionaries. For information on how to use them, please refer to the previous section .

<#Include Label="[1]{dict}"> <#Include Label="[2]{dict}"> <#Include Label="NewDictionary">

Dictionaries via Binary Lists

As there are situations where the approach via binary lists is explicitly desired, such dictionaries can be created deliberately.

<#Include Label="DictionaryByPosition"> <#Include Label="IsDictionary"> <#Include Label="IsLookupDictionary"> <#Include Label="AddDictionary"> <#Include Label="KnowsDictionary"> <#Include Label="LookupDictionary">

General Hash Tables

These sections describe some particularities for hash tables. These are intended mainly for extending the implementation - programs requiring hash functionality ought to use the dictionary interface described above.

We hash by keys and also store a value. Keys cannot be removed from the table, but the corresponding value can be changed. Fast access to last hash index allows you to efficiently store more than one array of values –this facility should be used with care.

This code works for any kind of object, provided you have a method to convert the key into a positive integer. This method should ideally be implemented efficiently in the core.

Note that, for efficiency, it is currently impossible to create a hash table with non-positive integers.

Hash keys

The crucial step of hashing is to transform key objects into integers such that equal objects produce the same integer.

The actual function used will vary very much on the type of objects. However &GAP; provides already key functions for some commonly encountered objects.

<#Include Label="DenseIntKey"> <#Include Label="SparseIntKey">

Dense hash tables

Dense hash tables are used for hashing dense sets without collisions, in particular integers. Keys are stored as an unordered list and values as an array with holes. The position of a value is given by the function returned by , and so KeyIntDense must be one-to-one.

<#Include Label="DenseHashTable">

Sparse hash tables

Sparse hash tables are used for hashing sparse sets. Stores keys as an array with fail denoting an empty position, stores values as an array with holes. Uses the result of calling ) of the key. DefaultHashLength is the default starting hash table length; the table is doubled when it becomes half full.

In sparse hash tables, the integer obtained from the hash key is then transformed to an index position by taking it modulo the length of the hash array.

<#Include Label="SparseHashTable"> <#Include Label="DoubleHashArraySize">