\(\) \(%\newcommand{\CLRS}{\href{https://mitpress.mit.edu/books/introduction-algorithms}{Cormen et al}}\) \(\newcommand{\CLRS}{\href{https://mitpress.mit.edu/books/introduction-algorithms}{Introduction to Algorithms, 3rd ed.} (\href{http://libproxy.aalto.fi/login?url=http://site.ebrary.com/lib/aalto/Doc?id=10397652}{online via Aalto lib})}\) \(\newcommand{\SW}{\href{http://algs4.cs.princeton.edu/home/}{Algorithms, 4th ed.}}\) \(%\newcommand{\SW}{\href{http://algs4.cs.princeton.edu/home/}{Sedgewick and Wayne}}\) \(\) \(\newcommand{\Java}{\href{http://java.com/en/}{Java}}\) \(\newcommand{\Python}{\href{https://www.python.org/}{Python}}\) \(\newcommand{\CPP}{\href{http://www.cplusplus.com/}{C++}}\) \(\newcommand{\ST}[1]{{\Blue{\textsf{#1}}}}\) \(\newcommand{\PseudoCode}[1]{{\color{blue}\textsf{#1}}}\) \(\) \(%\newcommand{\subheading}[1]{\textbf{\large\color{aaltodgreen}#1}}\) \(\newcommand{\subheading}[1]{\large{\usebeamercolor[fg]{frametitle} #1}}\) \(\) \(\newcommand{\Blue}[1]{{\color{flagblue}#1}}\) \(\newcommand{\Red}[1]{{\color{aaltored}#1}}\) \(\newcommand{\Emph}[1]{\emph{\color{flagblue}#1}}\) \(\) \(\newcommand{\Engl}[1]{({\em engl.}\ #1)}\) \(\) \(\newcommand{\Pointer}{\raisebox{-1ex}{\huge\ding{43}}\ }\) \(\) \(\newcommand{\Set}[1]{\{#1\}}\) \(\newcommand{\Setdef}[2]{\{{#1}\mid{#2}\}}\) \(\newcommand{\PSet}[1]{\mathcal{P}(#1)}\) \(\newcommand{\Card}[1]{{\vert{#1}\vert}}\) \(\newcommand{\Tuple}[1]{(#1)}\) \(\newcommand{\Implies}{\Rightarrow}\) \(\newcommand{\Reals}{\mathbb{R}}\) \(\newcommand{\Seq}[1]{(#1)}\) \(\newcommand{\Arr}[1]{[#1]}\) \(\newcommand{\Floor}[1]{{\lfloor{#1}\rfloor}}\) \(\newcommand{\Ceil}[1]{{\lceil{#1}\rceil}}\) \(\newcommand{\Path}[1]{(#1)}\) \(\) \(%\newcommand{\Lg}{\lg}\) \(\newcommand{\Lg}{\log_2}\) \(\) \(\newcommand{\BigOh}{O}\) \(%\newcommand{\BigOh}{\mathcal{O}}\) \(\newcommand{\Oh}[1]{\BigOh(#1)}\) \(\) \(\newcommand{\todo}[1]{\Red{\textbf{TO DO: #1}}}\) \(\) \(\newcommand{\NULL}{\textsf{null}}\) \(\) \(\newcommand{\Insert}{\ensuremath{\textsc{insert}}}\) \(\newcommand{\Search}{\ensuremath{\textsc{search}}}\) \(\newcommand{\Delete}{\ensuremath{\textsc{delete}}}\) \(\newcommand{\Remove}{\ensuremath{\textsc{remove}}}\) \(\) \(\newcommand{\Parent}[1]{\mathop{parent}(#1)}\) \(\) \(\newcommand{\ALengthOf}[1]{{#1}.\textit{length}}\) \(\) \(\newcommand{\TRootOf}[1]{{#1}.\textit{root}}\) \(\newcommand{\TLChildOf}[1]{{#1}.\textit{leftChild}}\) \(\newcommand{\TRChildOf}[1]{{#1}.\textit{rightChild}}\) \(\newcommand{\TNode}{x}\) \(\newcommand{\TNodeI}{y}\) \(\newcommand{\TKeyOf}[1]{{#1}.\textit{key}}\) \(\) \(\newcommand{\PEnqueue}[2]{{#1}.\textsf{enqueue}(#2)}\) \(\newcommand{\PDequeue}[1]{{#1}.\textsf{dequeue}()}\) \(\) \(\newcommand{\Def}{\mathrel{:=}}\) \(\) \(\newcommand{\Eq}{\mathrel{=}}\) \(\newcommand{\Asgn}{\mathrel{\leftarrow}}\) \(%\newcommand{\Asgn}{\mathrel{:=}}\) \(\) \(%\) \(% Heaps\) \(%\) \(\newcommand{\Downheap}{\textsc{downheap}}\) \(\newcommand{\Upheap}{\textsc{upheap}}\) \(\newcommand{\Makeheap}{\textsc{makeheap}}\) \(\) \(%\) \(% Dynamic sets\) \(%\) \(\newcommand{\SInsert}[1]{\textsc{insert}(#1)}\) \(\newcommand{\SSearch}[1]{\textsc{search}(#1)}\) \(\newcommand{\SDelete}[1]{\textsc{delete}(#1)}\) \(\newcommand{\SMin}{\textsc{min}()}\) \(\newcommand{\SMax}{\textsc{max}()}\) \(\newcommand{\SPredecessor}[1]{\textsc{predecessor}(#1)}\) \(\newcommand{\SSuccessor}[1]{\textsc{successor}(#1)}\) \(\) \(%\) \(% Union-find\) \(%\) \(\newcommand{\UFMS}[1]{\textsc{make-set}(#1)}\) \(\newcommand{\UFFS}[1]{\textsc{find-set}(#1)}\) \(\newcommand{\UFCompress}[1]{\textsc{find-and-compress}(#1)}\) \(\newcommand{\UFUnion}[2]{\textsc{union}(#1,#2)}\) \(\) \(\) \(%\) \(% Graphs\) \(%\) \(\newcommand{\Verts}{V}\) \(\newcommand{\Vtx}{v}\) \(\newcommand{\VtxA}{v_1}\) \(\newcommand{\VtxB}{v_2}\) \(\newcommand{\VertsA}{V_\textup{A}}\) \(\newcommand{\VertsB}{V_\textup{B}}\) \(\newcommand{\Edges}{E}\) \(\newcommand{\Edge}{e}\) \(\newcommand{\NofV}{\Card{V}}\) \(\newcommand{\NofE}{\Card{E}}\) \(\newcommand{\Graph}{G}\) \(\) \(\newcommand{\SCC}{C}\) \(\newcommand{\GraphSCC}{G^\text{SCC}}\) \(\newcommand{\VertsSCC}{V^\text{SCC}}\) \(\newcommand{\EdgesSCC}{E^\text{SCC}}\) \(\) \(\newcommand{\GraphT}{G^\text{T}}\) \(%\newcommand{\VertsT}{V^\textup{T}}\) \(\newcommand{\EdgesT}{E^\text{T}}\) \(\) \(%\) \(% NP-completeness etc\) \(%\) \(\newcommand{\Poly}{\textbf{P}}\) \(\newcommand{\NP}{\textbf{NP}}\) \(\newcommand{\PSPACE}{\textbf{PSPACE}}\) \(\newcommand{\EXPTIME}{\textbf{EXPTIME}}\)
\(\newcommand{\HMap}[2]{\text{#1}\mapsto\text{#2}}\)

Intro: small key universe, bit sets and direct-access tables

Before introducing hashing and hash tables, let us first assume that

  • ​ the number \( m \) of all possible keys is small, and

  • ​ the keys are, or can be easily mapped to, the integers \( [0..m-1] \).

In this case, it is easy to implement sets and maps so that the operations insert, search, and remove take constant time.

Bit sets

When the keys are, or have been mapped to, the integers \( [0..m-1] \), a simple way to implement a mutable set data structure for them is to use a bit set (also called bit array or bit vector):

  • ​ Allocate an array \( a = a_0 a_1 … a_{M-1} \) of \( M = \Ceil{\frac{m}{8}} \) 8-bit bytes.

  • ​ A key \( k \in [0..m-1] \) is in the current set if and only if the bit \( k \bmod 8 \) in the byte \( a_{\Floor{k/8}} \) is \( 1 \).

Now it should be obvious how to implement the operations insert, search, and remove with bit manipulation operations so that they operate in constant time (recall this section in the Programming 2 notes).

Example

The bit set below stores a subset of the keys \( \Set{0,1,…,999} \). The underlying array data structure has of 125 bytes. Now the keys \( 0 \times 8+1=1 \), \( 0 \times 8+4=4 \), \( 100 \times 8+6=806 \), \( 124 \times 8+7=999 \) are included in the current set, while the key \( 100 \times 8+5=805 \) is not.

_images/bitset-1.png

Bit sets are a very memory efficient way of representing dense sets (meaning sets in which a large portion of the keys are included). For sparse sets space is “wasted” and, for instance, listing all the keys in the set becomes costly.

Some implementations of bit sets in standard libraries:

  • BitSet in Scala

  • bitset in the C++ standard library

Direct-access tables

Again, suppose that we have a small set of \( n \) possible keys and that the set can be easily mapped to the integers \( [0..m-1] \). In that case, building a map/dictionary from the keys to some value type is easy; just allocate an array of \( m \) elements and store the value of the key \( i \) in the index \( i \) of the array.

For instance, in the two-letter country codes in ISO 3166-1 alpha-2 drawn from the alphabet \( \Set{\textrm{A},\textrm{B},…,\textrm{Z}} \) of 26 letters, there are \( 26 \times 26=676 \) possible codes. Of these 249, such as FI and UK, are actually assigned to some meaning. We can implement a dictionary mapping country codes to objects, such as capital city names, by having a direct-access table with 676 entries. The value \( v \) corresponding to a code \( c_1 c_2 \) is simply stored in the entry $$\mathit{index}(c_1 c_2) = 26 f(c_1) + f(c_2)$$ of the array, where \( f(\textrm{A})=0,f(\textrm{B})=1,…,f(\textrm{Z})=25 \).

Example

Mapping country codes to the entries in a direct-access table arr:

_images/direct-access-table-1.png

It is now easy to implement the insert, search, and remove operations to run in constant time. As an example, one can implement a country code map in Scala as follows:

import scala.reflect.ClassTag
class CountryMap[B >: Null]()(implicit tag: ClassTag[B]) {
  private val arr = new Array[B](676)
  private def f(c: Char) = c.toInt-'A'.toInt
  private def isValidCode(code: String) = code.length == 2 &&
    0 <= f(code(0)) && f(code(0)) < 26 &&
    0 <= f(code(1)) && f(code(1)) < 26
  def index(code: String): Int = {
    require(isValidCode(code))
    f(code(0))*26 + f(code(1))
  }
  def apply(code: String): Option[B] = {
    val v = arr(index(code))
    if(v == null) None else Some(v)
  }
  def update(code: String, value: B) = {
    require(value != null)
    arr(index(code)) = value
  }
  def remove(code: String) = {
    arr(index(code)) = null
  }
}

Note: use of null pointers is generally discouraged in Scala (use Option instead) but excusable inside data structure implementations. Extending the class with a constant-time size operation, returning the number of the keys in the map, is easy as well: how would you implement it?

Example

Building a direct-access table mapping country codes to some capital city names.

val capital = new CountryMap[String]()
capital("DE") = "Berlin"
capital("FI") = "Helsinki"
capital("UK") = "London"
capital("US") = "Washington"
println(capital("FI"))

produces

Some(Helsinki)

Graphically the data structure after the insertions could be illustrated as below.

_images/direct-access-table-2.png