001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 */
021
022package org.biojavax.ga.util;
023
024import java.util.AbstractSet;
025import java.util.ArrayList;
026import java.util.Collection;
027import java.util.Collections;
028import java.util.HashMap;
029import java.util.Iterator;
030import java.util.Map;
031import java.util.NoSuchElementException;
032
033
034/**
035 * <p>Inspred by the BioJava Distribution objects the WeightedSet is a map from
036 * a Key to a Weight. Unlike Distributions the Keys do not have to be Symbols.
037 * In the GA package the WeightedMap is useful for sampling Organisms according
038 * to their fitness.</p>
039 *
040 * <p>When Symbols are added or their weights are set then the weights are internally
041 * normalized to 1</p>
042 *
043 * @author Mark Schreiber
044 * @version 1.0
045 * @since 1.5
046 */
047
048public class WeightedSet extends AbstractSet implements java.io.Serializable{
049  private HashMap key2Weight;
050  double totalWeight;
051
052  public WeightedSet() {
053    key2Weight = new HashMap();
054  }
055
056  /**
057   * Converts the Set to a map from key <code>Objects</code> to <code>Double</code>
058   * weights.
059   * @return a Map with all the key-weight mappings. Weights are not normalized in this map.
060   */
061  public Map asMap(){
062    return key2Weight;
063  }
064
065  /**
066   * Randomly samples an <code>Object</code> from the <code>Set</code> according
067   * to its weight.
068   * @return the Object sampled.
069   */
070  public Object sample(){
071    double p = Math.random();
072    for (Iterator i = this.iterator(); i.hasNext(); ) {
073      Object o = i.next();
074      double weight = getWeight(o);
075
076      p -= weight;
077      if(p <= 0.0){
078        return o;
079      }
080    }
081    throw new org.biojava.bio.BioError("Cannot sample an object, does this set contain any objects?");
082  }
083
084  /**
085   * Determines the normalized weight for <code>o</code>
086   * @param o the <code>Object</code> you want to know the weight of
087   * @return the normalized weight
088   * @throws NoSuchElementException if <code>o</code> is not found in this set
089   */
090  public double getWeight(Object o) throws NoSuchElementException{
091    if(!( key2Weight.containsKey(o)))
092      throw new NoSuchElementException(o+" not found in this WeightedSet");
093
094    Double d = (Double)key2Weight.get(o);
095    if(totalWeight == 0.0)
096      return 0.0;
097
098
099    return d.doubleValue() / totalWeight;
100  }
101
102  /**
103   * The total weight that has been added to this Set.
104   * @return the total weight (the value that can be used for normalizing)
105   */
106  protected double getTotalWeight(){
107    return totalWeight;
108  }
109
110  /**
111   * Sets the weight of an <code>Object</code>. If the <code>Object</code> is
112   * not in this <code>Set</code> then it is added.
113   * @param o the <code>Object</code>
114   * @param w the weight.
115   * @throws IllegalArgumentException if <code>w</code> is < 0.0
116   */
117  public void setWeight(Object o, double w){
118    if(w < 0.0){
119      throw new IllegalArgumentException("Weight must be >= 0.0");
120    }
121    if(key2Weight.containsKey(o)){
122      remove(o);
123    }
124    totalWeight += w;
125    key2Weight.put(o, Double.valueOf(w));
126  }
127
128  public boolean contains(Object o) {
129    return key2Weight.containsKey(o);
130  }
131
132  public boolean remove(Object o) {
133    if(key2Weight.containsKey(o)){
134      totalWeight -= ((Double)key2Weight.get(o)).doubleValue();
135      key2Weight.remove(o);
136      return true;
137    }
138    return false;
139  }
140
141  public boolean isEmpty() {
142    return key2Weight.isEmpty();
143  }
144  public boolean retainAll(Collection c) {
145    boolean b = false;
146    Collection toRemove = new ArrayList();
147
148    for (Iterator i = iterator(); i.hasNext(); ) {
149      Object item = i.next();
150      if(c.contains(item) == false){
151        b = true;
152        toRemove.add(item);
153      }
154    }
155
156    removeAll(toRemove);
157
158    return b;
159  }
160
161  /**
162   * Adds a new <code>Object</code> with a weight of zero. Equivalent to
163   * setWeight(o, 0.0);
164   * @param o the object to add.
165   * @return true if this Object has not been added before.
166   */
167  public boolean add(Object o) {
168    boolean b = !(key2Weight.containsKey(o));
169    setWeight(o, 0.0);
170    return b;
171  }
172  public int size() {
173    return key2Weight.size();
174  }
175
176  public boolean containsAll(Collection c) {
177    if(size() == 0)
178      return false;
179
180    for (Iterator i = iterator(); i.hasNext(); ) {
181      Object item = i.next();
182      if(!(key2Weight.containsKey(item))){
183        return false;
184      }
185    }
186    return true;
187  }
188  public Object[] toArray() {
189    Object[] o = new Object[size()];
190    int j = 0;
191    for (Iterator i = iterator(); i.hasNext(); ) {
192      o[j++] = i.next();
193    }
194
195    return o;
196  }
197
198  public void clear() {
199    key2Weight = new HashMap();
200    totalWeight = 0.0;
201  }
202
203  /**
204   * Returns an unmodifiable iterator over the keys of the set.
205   * @return an Iterator
206   */
207  public Iterator iterator() {
208    return Collections.unmodifiableSet(key2Weight.keySet()).iterator();
209  }
210
211  public boolean addAll(Collection c) {
212    boolean b = false;
213
214    for (Iterator i = c.iterator(); i.hasNext(); ) {
215
216      Object item = i.next();
217      if(!(key2Weight.containsKey(item)))
218         b = true;
219
220      add(item);
221    }
222    return b;
223  }
224}