Re: Hashtable question

From: Roedy Green (look-on_at_mindprod.com.invalid)
Date: 05/11/04


Date: Tue, 11 May 2004 21:03:56 GMT

On Tue, 11 May 2004 20:59:09 GMT, Roedy Green
<look-on@mindprod.com.invalid> wrote or quoted :

>Here is some code that does that sort of thing:
 here is some more
// com.mindprod.pim.PIMEngine.java

package com.mindprod.pim;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OptionalDataException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.TreeMap;

/**
  * Engine for a ram-based Personal Information Manager.
  * Internally it has a HashMap of Items and a TreeMap of Keys.
  * Each key can point directly to several Items, and each Item can
point to several keys.
  * @author copyright (c) 1999-2004 Roedy Green, Canadian Mind
Products
  * may be copied and used freely for any purpose but military.
  *
  * Roedy Green
  * Canadian Mind Products
  * #327 - 964 Heywood Avenue
  * Victoria, BC Canada V8V 2Y5
  * tel: (250) 361-9093
  * mailto:roedyg@mindprod.com
  * http://mindprod.com
  *
  * version 1.0 1999 October 12 8:30 PM -- 9:30 PM
  * version 1.1 1999 October 13 - 9:30 PM -- 12:20 PM
  * - clean compile of code, no testing.
  * - no pickle
  * version 1.2 1999 October 13 - 9:10 AM -- 10:10 AM
  * - add find all strings for item
  * - add sort to keep Keys per item in
order
  * - remove requirement for makeUnique.
  * - get rid of compiler errors
  * - deal with null initial case in
addKey and addItem
  * version 1.3 1999 October 13 - 10:40 AM -- 11:15 AM
  * - make allKeys use String -> key
consistently.
  * - crude test of add/display functions.
  * version 1.4 1999 October 13 - 2:20 PM -- 3:20 PM 7:00 PM -- 8:00
PM
  * - add pickle/reconstitute
  * - only sort keyList just prior to
display to save overhead
  * of repeated sorts during
reconstitution.
  * - change constructor to take String
instead of File.
  * - add showAll debug tool
  * - clone rather than give access to
internal array in find.
  *
  */
public class PIMEngine implements java.io.Serializable
   {

   /**
    * public constructor.
    * @param modelName is a filename less the .pim extension where the
PIM data are stored,
    * e.g. "C:\\myDir\\plays"
    */
   public PIMEngine(String modelName) throws IOException,
FileNotFoundException {
      this.modelName = modelName;
      this.load();
   } // end constructor

   /**
    * load a previously saved pickled version of the entire data
structure.
    */
   public void load() throws IOException {
      File current = new File(modelName + ".pim");
      if ( current.exists() )
         {
         // O P E N
         try
            {
            FileInputStream fis = new FileInputStream(current);
            BufferedInputStream bis = new BufferedInputStream(fis,
4096 /* buffsize */);
            ObjectInputStream ois = new ObjectInputStream(bis);
            // R E A D
            // reconstitute fields individually rather than
            // "this" because of readObject/writeObject asymmetry
            allKeys = (TreeMap) ois.readObject();
            // restore transient fields, namely the allItems HashMap
and the
            // Item.keyList[] back pointers.
            allItems = new java.util.HashMap();
            // enumerate all the Key objects in the TreeMap
            Collection keys = allKeys.values();
            Iterator eachKey = keys.iterator();
            while ( eachKey.hasNext() )
               {
               Key key = (Key)eachKey.next();
               int numItems = key.itemList.length;
               for ( int i=0; i<numItems; i++ )
                  {
                  // itemList[i] is already unique, we use makeUnique
for the side effect
                  // of adding the item to the allItems HashMap
                  Item item = makeUnique(key.itemList[i]);
                  // build item.keyList[] back pointer
                  item.addKey(key);
                  } // end for
               } // end while
            // C L O S E
            ois.close();
            }
         catch ( ClassNotFoundException e )
            {
            throw new IOException("PIM file corrupt");
            }
         catch ( OptionalDataException e )
            {
            throw new IOException("PIM file corrupt");
            }
         catch ( IOException e )
            {
            throw new IOException("PIM file corrupt");
            }
         }
      else
         {
         // starting from scratch
         allKeys = new java.util.TreeMap();
         allItems = new java.util.HashMap();
         }
   } // end load

   /**
     * save the entire data structure in pickled form in a file.
     * @param File to store program state in pickled form.
     */
   public void save() throws IOException {
      // rename old, as .old save as .pim
      File prev = new File(modelName + ".old");
      File current = new File(modelName + ".pim");;
      prev.delete();
      current.renameTo(prev);
      // O P E N
      FileOutputStream fos = new FileOutputStream(current);
      BufferedOutputStream bos = new BufferedOutputStream(fos, 4096 /*
buffsize */);
      ObjectOutputStream oos = new ObjectOutputStream(bos);
      // W R I T E
      // pickle fields individually rather than
      // "this" because of readObject/writeObject asymmetry
      // Write out the allKeys TreeMap but leave behind the allItems
HashMap
      // Also leave out the Key[] Item.KeyList back pointers.
      oos.writeObject(allKeys);
      // C L O S E
      oos.close();
   } // end save

   /**
    * create an association between the given key and the given item.
    * Items may have many associated keys, and keys may associate to
many items.
    * @param keystring key for the association
    * @param item data object derived from Item class.
    */
   public void add(String keyString, Item item)
      {
      Item uniqueItem = makeUnique(item);
      Key key = lookup(keyString);
      if ( key == null )
         {
         key = new Key(keyString);
         allKeys.put(keyString, key);
         }
      key.addItem(uniqueItem);
      uniqueItem.addKey(key);
      } // end add

   /**
    * create an association between the given list of keys and the
given item.
    * Items may have many associated keys, and keys may associate to
many items.
    * @param keystring list of keys for the association
    * @param item data object derived from Item class.
    */
   public void add(String[] keyString, Item item)
      {
      Item uniqueItem = makeUnique(item);
      int numKeyStrings = keyString.length;
      for ( int i=0; i<numKeyStrings; i++ )
         {
         add(keyString[i], uniqueItem);
         } // end for
      } // end add

   /**
     * Break an existing association between a key and an item.
     * This will not destroy either the key or the item, though
     * it will remove references to them, which may eventually lead to
     * them being garbage collected.
     * It is not considered an error to remove a pair that has not
been previously added.
     * @param keystring key for the association.
     * @param item data object derived from Item class.
     */
   public void remove(String keyString, Item item)
      {
      Item uniqueItem = makeUnique(item);
      Key key = lookup(keyString);
      if ( key == null )
         {
         // was no such keyString
         return;
         }
      key.removeItem(uniqueItem);
      if ( key.itemList == null )
         {
         allKeys.remove(key.keyString);
         }
      uniqueItem.removeKey(key);
      if ( uniqueItem.keyList == null )
         {
         allItems.remove(uniqueItem);
         }
      } // end remove

   /**
     * Break an existing association between a list of keys and an
item.
     * This will not destroy either the key or the item, though
     * it will remove references to them, which may eventually lead to
     * them being garbage collected.
     * It is not considered an error to remove a pair that has not
been previously added.
     * @param keystring list of keys for the association.
     * @param item data object derived from Item class.
     */
   public void remove(String[] keyString, Item item)
      {
      Item uniqueItem = makeUnique(item);
      int numKeyStrings = keyString.length;
      for ( int i=0; i<numKeyStrings; i++ )
         {
         remove(keyString[i], uniqueItem);
         }
      } // end remove

   /**
   * Break all existing associations between the given key and any
items.
   * This will not destroy either the key or the item, though
   * it will remove references to them, which may eventually lead to
   * them being garbage collected.
   * It is not considered an error to remove a key that has not been
previously added.
   * @param keystring key to remove.
   */
   public void remove(String keyString)
      {
      Key key = lookup(keyString);
      if ( key == null )
         {
         // was no such keyString
         return;
         }
      int numItems = key.itemList.length;
      for ( int i=0; i<numItems; i++ )
         {
         Item item = key.itemList[i];
         item.removeKey(key);
         if ( item.keyList == null )
            {
            allItems.remove(item);
            }
         } // end for
      key.itemList = null;
      allKeys.remove(key.keyString);
      } // end remove

   /**
    * Break all existing associations between the given item and all
associated keys.
    * This will not destroy either the key or the item, though
    * it will remove references to them, which may eventually lead to
    * them being garbage collected.
    * It is not considered an error to remove a key that has not been
previously added.
    * @param item data object derived from Item class.
    */
   public void remove(Item item)
      {
      Item uniqueItem = makeUnique(item);
      int numKeys = uniqueItem.keyList.length;
      for ( int i=0; i<numKeys; i++ )
         {
         Key key = uniqueItem.keyList[i];
         key.removeItem(uniqueItem);
         if ( key.itemList == null )
            {
            allKeys.remove(key.keyString);
            }
         } // end for
      uniqueItem.keyList = null;
      allItems.remove(uniqueItem);
      } // end remove

   /**
    * Find all items associated with the given key.
    * @param keyString the string to lookup by.
    */
   public Item[] find(String keyString)
      {
      Key key = lookup(keyString);
      if ( key == null )
         {
         System.out.println("lookup fail");
         return null;
         }
      Item[] itemList = key.itemList;
      if ( itemList == null )
         {
         return null;
         }
      int numItems = itemList.length;
      // can't use Item[].clone because it is protected.
      Item[] clonedItemList = new Item[numItems];
      System.arraycopy(itemList, 0, clonedItemList, 0, numItems);
      return clonedItemList;
      } // end find

   /**
    * Find all KeyStrings associated with the given item
    * @param item data object derived from Item class.
    * @return array of Strings representing associated keys.
    */
   public String[] find(Item item)
      {
      Item uniqueItem = makeUnique(item);
      Key[] keyList = uniqueItem.keyList;
      if ( keyList == null )
         {
         return null;
         }
      // put in alphabetical order, preparatory to display
      Arrays.sort(keyList);
      int numKeyStrings = keyList.length;
      String[] keyStringList = new String[numKeyStrings];
      for ( int i=0; i<numKeyStrings; i++ )
         {
         keyStringList[i] = keyList[i].keyString;
         }
      return keyStringList;
      } // end find

   /**
    * Ensure the Item is unique by returning a reference to the master
version which
    * already lives in the allKeys HashTable. If the Item has never
been seen before,
    * it is added to the HashTable.
    * @param item data object derived from Item class.
    */
   public Item makeUnique(Item item)
      {
      Item uniqueItem = (Item) allItems.get(item);
      if ( uniqueItem != null )
         {
         return uniqueItem;
         }
      uniqueItem = item;
      allItems.put(uniqueItem, uniqueItem);
      return uniqueItem;
      }

   /**
    * lookup the given Keystring to find the associated Key object.
    * @param keyString the lookup string.
    * @return Key object, or null if not found.
    */
   private Key lookup(String keyString)
      {
      return(Key) allKeys.get(keyString);
      } // end lookup

   /**
     * Lookup structure keys -> Items.
     * Access either directy by key or by alphabetic order.
     * indexed by KeyString, gives Key, which then points to Items.
     */
   protected java.util.TreeMap allKeys;

   /**
    * Lookup structure data contents -> Item.
    * Only used internally. Helps prevent duplicate Items.
    * indexed by Item, gives Item, which then points to assocated Keys
which then give keyStrings.
    */
   transient protected java.util.HashMap allItems;

   /**
    * the filename, without .pim extension where the model is stored,
in pickled form.
    */
   transient protected String modelName;

   /**
    * debugging tool that displays all the Key and Item objects.
    */
   public void showAll()
      {
      if ( true )
         {
         if ( allKeys != null )
            {
            System.out.println("BY KEY");
            Collection keys = allKeys.values();
            Iterator eachKey = keys.iterator();
            while ( eachKey.hasNext() )
               {
               Key key = (Key)eachKey.next();
               System.out.println("key " + key.keyString);
               if ( key.itemList != null )
                  {
                  int numItems = key.itemList.length;
                  for ( int i=0; i<numItems; i++ )
                     {
                     TextItem item = (TextItem) key.itemList[i];
                     System.out.println(" item " + item.getText());
                     } // end for
                  } // if
               } // end while
            } // end if allKeys
         if ( allItems != null )
            {

            System.out.println("BY ITEM");
            Collection items = allItems.values();
            Iterator eachItem = items.iterator();
            while ( eachItem.hasNext() )
               {
               TextItem item = (TextItem)eachItem.next();
               System.out.println("item " + item.getText());
               String [] keyStrings = find(item);
               if ( keyStrings != null )
                  {
                  int numKeyStrings = keyStrings.length;
                  for ( int j=0; j<numKeyStrings; j++ )
                     {
                     System.out.println(" key " + keyStrings[j]);
                     } // end for
                  } // end if
               } // end while
            } // end if allItems
         } // end if

      } // end showAll

   public static void main (String[] args)
      {
      try
         {
         PIMEngine p = new PIMEngine("C:\\temp\\temp1");
         Item t1 = new TextItem("Love's Labour Lost");
         Item t2 = new TextItem("The Tempest");
         Item t3 = new TextItem("Macbeth");
         Item t4 = new TextItem("The Tempest");
         Item t5 = new TextItem("Duncan the Wonder Dog");
         p.add("Duncan",t3);
         p.add("Duncan",t5);
         p.add("Banquo",t3);
         p.add("Ariel",t4);
         System.out.println("corresponding keys to item Macbeth");
         String[] foundKeyStrings = p.find(t3);
         if ( foundKeyStrings != null )
            {
            for ( int i=0; i<foundKeyStrings.length; i++ )
               {
               System.out.println(foundKeyStrings[i]);
               }
            }

         System.out.println("corresponding items to key Duncan");
         Item[] foundItems = p.find("Duncan");
         if ( foundItems != null )
            {
            for ( int i=0; i<foundItems.length; i++ )
               {

System.out.println(((TextItem)foundItems[i]).getText());
               }
            }
         p.showAll();
         p.save();
         }
      catch ( IOException e )
         {
         System.out.println(e);
         }

      } // end main

   } // end class PIMEngine

------------------

// com.mindprod.pim.Item.java
package com.mindprod.pim;

/**
 * Base class to represent an data item in the PIM. It might
represent a
 * URL, the name of file, a hunk of text, a GIF, etc. Different items
types
 * would hold different types of data. All Item types are derived
from this
 * base class.
 */
public abstract class Item implements java.io.Serializable
   {
   /**
    * List of associated Key objects, not key Strings.
    * Put in alphabetical order, just prior to display, but otherwise
we allow it to
    * be in any order.
    * Array is compact even if a bit clumsy to add and remove an
element.
    * We don't include this in the pickle, because the recursion could
overflow
    * the stack. PIMEngine.load reconstitutes these, not
Item.readObject.
    */
   transient protected Key[] keyList;

   /**
    * add given key to the keylist if it is not already there.
    */
   protected void addKey(Key key)
      {
      int len;
      if ( keyList == null )
         {
         len = 0;
         }
      else
         {
         len = keyList.length;
         }
      for ( int i=0; i<len; i++ )
         {
         // since unique, can use == instead of equals.
         if ( key == keyList[i] )
            {
            // already in list
            return;
            }
         } // end for
      // grow the array to make room to add item at the end
      Key[] newKeyList = new Key[len+1];
      if ( len > 0 )
         {
         System.arraycopy(keyList, 0, newKeyList, 0, len);
         }
      newKeyList[len] = key;
      // allow to get out of alphabetical order
      keyList = newKeyList;
      } // end addKey

   /**
    * remove given key from the keylist.
    * If it is already removed, that's ok.
    */
   protected void removeKey(Key key)
      {
      int len = keyList.length;
      for ( int i=0; i<len; i++ )
         {
         // since unique, can use == instead of equals.
         if ( key == keyList[i] )
            {
            // found in list
            if ( len == 1 )
               {
               keyList = null;
               return;
               }
            // shrink the array to remove ith element
            Key[] newKeyList = new Key[len-1];
            // copy elts below i, if any
            if ( i > 0 )
               {
               System.arraycopy(keyList, 0, newKeyList, 0, i);
               }
            // copy elts above i, if any
            int upper = len-i;
            if ( upper > 0 )
               {
               System.arraycopy(keyList, i+1, newKeyList, i, upper);
               }
            keyList = newKeyList;
            // won't disturb sort order
            return;
            } // end if
         } // end for
      // not in list, nothing to do
      } // end removeKey

   /**
    * Returns a hashcode based solely on the data contents of the
Item, not the keylist.
    *
    * @return a hash code value for this object.
    */
   public abstract int hashCode();

   /**
    * Compares two Items, basing equality on the data contents of the
Item, not the keylist.
    * @param anObject other Item to compare to.
    * @return true if the Items are equal. Return false if anObject is
not of the same type.
    */
   public abstract boolean equals(Object anObject);

   } // end class Item

----------------

// com.mindprod.pim.Key.java
package com.mindprod.pim;

/**
 * The Key our lookup engine works with in the TreeMap. It points to
several Items.
 */
public final class Key implements Comparable, java.io.Serializable
   {

   /**
    * public constructor
    */
   public Key (String keyString)
      {
      this.keyString = keyString;
      this.itemList = null;
      } // end constructor

   /**
    * the lookup key
    */
   protected String keyString;

   /**
    * List of associated Items
    * Array is compact even if a bit clumsy to add and remove an
element.
    */
   protected Item[] itemList;

   /**
     * Returns a hashcode based solely on the lookup key, not the
items.
     *
     * @return a hash code value for this object.
     */
   public int hashCode()
      {
      return keyString != null ? keyString.hashCode(): 0;
      }

   /**
    * Compares two Keys, basing equality on the key, not the Itemlist.
    * @param anObject other Item to compare to.
    * @return true if the Items are equal. Return false if anObject is
not of the same type.
    */
   public boolean equals(Object anObject)
      {
      if ( keyString == null ) return false;
      if ( anObject instanceof Key )
         {
         return this.keyString.equals(((Key)anObject).keyString);
         }
      if ( anObject instanceof String )
         {
         return this.keyString.equals((String)anObject);
         }
      return false;
      } // end equals

   /**
    * add an Item to the ItemList if it is not already there.
    * @param Item to add, must be unique.
    */
   protected void addItem(Item uniqueItem)
      {
      int len;
      if ( itemList == null )
         {
         len = 0;
         }
      else
         {
         len = itemList.length;
         }
      for ( int i=0; i<len; i++ )
         {
         // since unique, can use == instead of equals.
         if ( uniqueItem == itemList[i] )
            {
            // already in list
            return;
            }

         } // end for
      // grow the array to make room to add item at the end
      Item[] newItemList = new Item[len+1];
      int below = len;
      if ( below > 0 )
         {
         System.arraycopy(itemList, 0, newItemList, 0, below);
         }
      newItemList[len] = uniqueItem;
      itemList = newItemList;
      // order does not matter
      } // end addItem

   /**
    * remove given item from this key's itemList.
    * If it is already removed, that's ok.
    */
   protected void removeItem(Item uniqueItem)
      {
      int len = itemList.length;
      for ( int i=0; i<len; i++ )
         {
         // since unique, can use == instead of equals.
         if ( uniqueItem == itemList[i] )
            {
            // found in list
            if ( len == 1 )
               {
               itemList = null;
               return;
               }
            // shrink the array to remove ith element
            Item[] newItemList = new Item[len-1];
            // copy elts below i, if any
            int below = i;
            if ( below > 0 )
               {
               System.arraycopy(itemList, 0, newItemList, 0, below);
               }
            // copy elts above i, if any
            int above = len-i;
            if ( above > 0 )
               {
               System.arraycopy(itemList, i+1, newItemList, i, above);
               }
            itemList = newItemList;
            // existing order is just fine
            return;
            } // end if
         } // end for
      // not in list, nothing to do
      } // end removeItem

   /* compares this Key's Keystring to another's.
    * @param o Key to be compared with this one.
    * @return +ve if this one is bigger, 0 if equal, -ve if smaller.
    */
   public int compareTo (Object o) throws ClassCastException
   {
      return this.keyString.compareTo(((Key)o).keyString);
   }

   /**
    * @return a String that represents this Key
    */
   public String toString()
      {
      return keyString;
      }

   } // end class Key

--------------------

// com.mindprod.pim.TextItem.java
package com.mindprod.pim;

/**
 * Item to associate, holds simple string, without embedded \n chars.
 */

public class TextItem extends Item
   {

   /**
    * public constructor
    */
   public TextItem (String text)
      {
      super();
      this.text = text;
      } // end constructor

   /**
    * text data to be associated with keys
    */
   private String text;

   /**
    * get text data string for this TextItem
    * Note there is no corresponding setText, since the data must be
immutable.
    * @return text string.
    */
   public String getText()
      {
      return text;
      } // end getText

   /**
    * @return a String that represents this TextItem
    */
   public String toString()
      {
      return text;
      }

   /**
    * Returns a hashcode based solely on the data contents of the
Item, not the keylist.
    *
    * @return a hash code value for this object.
    */
   public int hashCode()
      {
      return text.hashCode();
      }

   /**
    * Compares two Items, basing equality on the data contents of the
Item, not the keylist.
    * @param anObject other Item to compare to.
    * @return true if the Items are equal. Return false if anObject is
not of the same type.
    */
   public boolean equals(Object anObject)
      {
      if ( text == null ) return false;
      if ( anObject instanceof TextItem )
         {
         return text.equals(((TextItem)anObject).text);
         }
      if ( anObject instanceof String )
         {
         return text.equals((String)anObject);
         }
      return false;
      } // end equals

   } // end class TextItem

-------------------

I have two other variants, one uses a TreeMap and one supports Many to
One mappings, not many to many.

--
Canadian Mind Products, Roedy Green.
Coaching, problem solving, economical contract programming. 
See http://mindprod.com/jgloss/jgloss.html for The Java Glossary.


Relevant Pages

  • Re: trouble figuring out HttpURLConnection
    ... private ArrayList cookies = new ArrayList; ... private String queryString; ... * @param url the full URL of the form. ... public void setConn ...
    (comp.lang.java.programmer)
  • Help need to send email using java applet in html page
    ... the java applet show as below ... private String strRecipients = ""; ... public void destroy() { ... * @param exception java.lang.Throwable ...
    (comp.lang.java)
  • Re: Graph traversieren wie ein Petri-Netz
    ... @param strValue zu prüfender String ... public static final boolean empty{ ... @param strBuffValue zu prüfender StringBuffer ...
    (de.comp.lang.java)
  • Re: which JAR to use ?
    ... scan for a string that leads into the piece you want with indexOf. ... public static String getWantedCurrencies throws IOException ... * @param result Results to save, ... public static String get(String websiteURL, String relativeURL, ...
    (comp.lang.java.programmer)
  • Re: Time - BuffererdReader takes read in data - store in hash Map TAKING FOREVER
    ... then convert to string, then parse it yourself. ... @param prefix ... @exception IOException ... public static byterawReadEntireFile (File fromFile) throws ...
    (comp.lang.java.programmer)