5. Hashes

Introduction

// The top interface for "Hashes" functionality in Java is java.util.Map. Multiple
// implementations with some selected added features are available, java.util.HashMap,
// java.util.Hashtable, java.util.LinkedHashMap and java.util.TreeMap being the most
// notable ones.
//
// Maps cannot contain primitive types, they must contain objects (int -> Integer).
//
// Maps don't have "sugar" syntax for easy creation of arbitrary maps, we need to
// call several methods for this:
Map age = new HashMap();
age.put( "Nat", new Integer( 24 ) );
age.put( "Jules", new Integer( 25 ) );
age.put( "Josh", new Integer( 17 ) );

Map food_color = new HashMap();
food_color.put( "Apple", "red" );
food_color.put( "Banana", "yellow" );
food_color.put( "Lemon", "yellow" );
food_color.put( "Carrot", "orange" );

Adding an Element to a Hash

// Setting or replacing a map entry uses Map#put:
Map map = new HashMap(); Object key = null; Object value = null;
map.put( key, value );
// food_color defined per the introduction; we use an iterator on the keys of the map
food_color.put( "Raspberry", "pink" );
System.out.println( "Known foods:" );
for ( Iterator it = food_color.keySet().iterator(); it.hasNext(); ) {
    System.out.println( it.next() );
}

Testing for the Presence of a Key in a Hash

// does map have a value for key?
if ( map.containsKey( key ) ) {
    // it exists
} else {        
    // it doesn't
}

String[] food_or_drink = new String[] { "Banana", "Martini" };
for ( int i = 0; i < food_or_drink.length; i++ ) {
    String name = food_or_drink[i];
    System.out.println( name + " is a " + ( food_color.containsKey( name ) ? "food" : "drink" ) );
}

age.put( "Toddler", new Integer( 3 ) );
age.put( "Unborn", new Integer( 0 ) );
age.put( "Phantasm", null );
String[] age_tries = new String[] { "Toddler", "Unborn", "Phantasm", "Relic" };
for ( int i = 0; i < age_tries.length; i++ ) {
    String atry = age_tries[i];
    System.out.println( atry + ": "
                        + ( age.containsKey( atry ) ? "contains-key " : "" )
                        + ( age.containsKey( atry ) && age.get( atry ) != null ? "non-null " : "" )
                        + ( age.containsKey( atry ) && age.get( atry ) != null
                            && ! ( (Integer)age.get( atry ) ).equals( new Integer( 0 ) )
                                ? "non-zero" : "" ) );
}
// =>
// Toddler: contains-key non-null non-zero
// Unborn: contains-key non-null 
// Phantasm: contains-key 
// Relic: 
    
// You use Map#containsKey when you use Perl's exists -> it checks
// for existence of a key in a Map.

Deleting from a Hash

food_color.remove( "Banana" );

Traversing a Hash

for ( Iterator it = food_color.entrySet().iterator(); it.hasNext(); ) {
    Map.Entry e = (Map.Entry) it.next();
    // do something with e.getKey() and e.getValue()
    // also supports e.setValue() to update the value
}

for ( Iterator it = food_color.keySet().iterator(); it.hasNext(); ) {
    key = it.next();
    // do something with key
}

for ( Iterator it = food_color.entrySet().iterator(); it.hasNext(); ) {
    Map.Entry e = (Map.Entry) it.next();
    System.out.println( e.getKey() + " is " + e.getValue() );
}

// The easiest way to iterate over keys of a Map in ordered keys is to use
// java.util.TreeMap, which "guarantees that the map will be in ascending
// key order, sorted according to the natural order for the key's class".
TreeMap sorted_foods = new TreeMap( food_color );
System.out.println( "sorted foods:" );
for ( Iterator it = sorted_foods.entrySet().iterator(); it.hasNext(); ) {
    Map.Entry e = (Map.Entry) it.next();
    System.out.println( e.getKey() + " is " + e.getValue() );
}

// download the following standalone program
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/** countfrom - count number of messages from each sender. */
public class CountFrom {

    public static void main( String args[] ) throws IOException {
        TreeMap from = new TreeMap();
        BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) );
        String line;
        Pattern p = Pattern.compile( "From: (.*)" );
        while ( ( line = br.readLine() ) != null ) {
            Matcher m = p.matcher( line );
            if ( m.lookingAt() ) {
                Integer current_value = (Integer) from.get( m.group( 1 ) );
                if ( current_value == null ) {
                    current_value = new Integer( 1 );
                } else {
                    current_value = new Integer( current_value.intValue() + 1 );
                }
                from.put( m.group( 1 ), current_value );
            }
        }

        for ( Iterator it = from.entrySet().iterator(); it.hasNext(); ) {
            Map.Entry e = (Map.Entry) it.next();
            System.out.println( e.getKey() + ": " + e.getValue() );
        }
    }
}

Printing a Hash

// Map#toString already produce a pretty decent output:
System.out.println( sorted_foods );
// =>
// {Apple=red, Carrot=orange, Lemon=yellow, Raspberry=pink}

// Or do it the Cookbook way:
for ( Iterator it = sorted_foods.entrySet().iterator(); it.hasNext(); ) {
    Map.Entry e = (Map.Entry) it.next();
    System.out.println( e.getKey() + " => " + e.getValue() );
}
// =>
// Apple => red
// Carrot => orange
// Lemon => yellow
// Raspberry => pink

Retrieving from a Hash in Insertion Order

// java.util.LinkedHashMap "maintains a doubly-linked list running through all of its entries.
// This linked list defines the iteration ordering, which is normally the order in which keys
// were inserted into the map (insertion-order)".
food_color = new LinkedHashMap();
food_color.put( "Banana", "Yellow" );
food_color.put( "Apple", "Green" );
food_color.put( "Lemon", "Yellow" );

System.out.println( "In insertion order, the foods are:" );
for ( Iterator it = food_color.keySet().iterator(); it.hasNext(); ) {
    System.out.println( it.next() );
}
// =>
// Banana
// Apple
// Lemon

Hashes with Multiple Values Per Key

Inverting a Hash

Sorting a Hash

Merging Hashes

Finding Common or Different Keys in Two Hashes

Hashing References

Presizing a Hash

Finding the Most Common Anything

Representing Relationships Between Data

Program: dutree