source: src/main/java/weka/attributeSelection/BestFirst.java @ 10

Last change on this file since 10 was 4, checked in by gnappo, 14 years ago

Import di weka.

File size: 24.1 KB
Line 
1/*
2 *    This program is free software; you can redistribute it and/or modify
3 *    it under the terms of the GNU General Public License as published by
4 *    the Free Software Foundation; either version 2 of the License, or
5 *    (at your option) any later version.
6 *
7 *    This program is distributed in the hope that it will be useful,
8 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
9 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 *    GNU General Public License for more details.
11 *
12 *    You should have received a copy of the GNU General Public License
13 *    along with this program; if not, write to the Free Software
14 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
15 */
16
17/*
18 *    BestFirst.java
19 *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
20 *
21 */
22
23package weka.attributeSelection;
24
25import weka.core.FastVector;
26import weka.core.Instances;
27import weka.core.Option;
28import weka.core.OptionHandler;
29import weka.core.Range;
30import weka.core.RevisionHandler;
31import weka.core.RevisionUtils;
32import weka.core.SelectedTag;
33import weka.core.Tag;
34import weka.core.Utils;
35
36import java.io.Serializable;
37import java.util.BitSet;
38import java.util.Enumeration;
39import java.util.Hashtable;
40import java.util.Vector;
41
42/**
43 <!-- globalinfo-start -->
44 * BestFirst:<br/>
45 * <br/>
46 * Searches the space of attribute subsets by greedy hillclimbing augmented with a backtracking facility. Setting the number of consecutive non-improving nodes allowed controls the level of backtracking done. Best first may start with the empty set of attributes and search forward, or start with the full set of attributes and search backward, or start at any point and search in both directions (by considering all possible single attribute additions and deletions at a given point).<br/>
47 * <p/>
48 <!-- globalinfo-end -->
49 *
50 <!-- options-start -->
51 * Valid options are: <p/>
52 *
53 * <pre> -P &lt;start set&gt;
54 *  Specify a starting set of attributes.
55 *  Eg. 1,3,5-7.</pre>
56 *
57 * <pre> -D &lt;0 = backward | 1 = forward | 2 = bi-directional&gt;
58 *  Direction of search. (default = 1).</pre>
59 *
60 * <pre> -N &lt;num&gt;
61 *  Number of non-improving nodes to
62 *  consider before terminating search.</pre>
63 *
64 * <pre> -S &lt;num&gt;
65 *  Size of lookup cache for evaluated subsets.
66 *  Expressed as a multiple of the number of
67 *  attributes in the data set. (default = 1)</pre>
68 *
69 <!-- options-end -->
70 *
71 * @author Mark Hall (mhall@cs.waikato.ac.nz)
72 *         Martin Guetlein (cashing merit of expanded nodes)
73 * @version $Revision: 1.29 $
74 */
75public class BestFirst 
76  extends ASSearch
77  implements OptionHandler, StartSetHandler {
78 
79  /** for serialization */
80  static final long serialVersionUID = 7841338689536821867L;
81
82  // Inner classes
83  /**
84   * Class for a node in a linked list. Used in best first search.
85   * @author Mark Hall (mhall@cs.waikato.ac.nz)
86   **/
87  public class Link2 
88    implements Serializable, RevisionHandler {
89
90    /** for serialization */
91    static final long serialVersionUID = -8236598311516351420L;
92   
93    /*    BitSet group; */
94    Object [] m_data;
95    double m_merit;
96
97
98    /**
99     * Constructor
100     */
101    public Link2 (Object [] data, double mer) {
102      //      group = (BitSet)gr.clone();
103      m_data = data;
104      m_merit = mer;
105    }
106
107
108    /** Get a group */
109    public Object [] getData () {
110      return  m_data;
111    }
112
113
114    public String toString () {
115      return  ("Node: " + m_data.toString() + "  " + m_merit);
116    }
117   
118    /**
119     * Returns the revision string.
120     *
121     * @return          the revision
122     */
123    public String getRevision() {
124      return RevisionUtils.extract("$Revision: 1.29 $");
125    }
126  }
127
128
129  /**
130   * Class for handling a linked list. Used in best first search.
131   * Extends the Vector class.
132   * @author Mark Hall (mhall@cs.waikato.ac.nz)
133   **/
134  public class LinkedList2
135    extends FastVector {
136   
137    /** for serialization */
138    static final long serialVersionUID = 3250538292330398929L;
139   
140    /** Max number of elements in the list */
141    int m_MaxSize;
142
143    // ================
144    // Public methods
145    // ================
146    public LinkedList2 (int sz) {
147      super();
148      m_MaxSize = sz;
149    }
150
151
152    /**
153     * removes an element (Link) at a specific index from the list.
154     * @param index the index of the element to be removed.
155     **/
156    public void removeLinkAt (int index)
157      throws Exception {
158     
159      if ((index >= 0) && (index < size())) {
160        removeElementAt(index);
161      }
162      else {
163        throw  new Exception("index out of range (removeLinkAt)");
164      }
165    }
166
167
168    /**
169     * returns the element (Link) at a specific index from the list.
170     * @param index the index of the element to be returned.
171     **/
172    public Link2 getLinkAt (int index)
173      throws Exception {
174     
175      if (size() == 0) {
176        throw  new Exception("List is empty (getLinkAt)");
177      }
178      else {if ((index >= 0) && (index < size())) {
179        return  ((Link2)(elementAt(index)));
180      }
181      else {
182        throw  new Exception("index out of range (getLinkAt)");
183      }
184      }
185    }
186
187
188    /**
189     * adds an element (Link) to the list.
190     * @param data the attribute set specification
191     * @param mer the "merit" of this attribute set
192     **/
193    public void addToList (Object [] data, double mer)
194      throws Exception {
195      Link2 newL = new Link2(data, mer);
196
197      if (size() == 0) {
198        addElement(newL);
199      }
200      else {if (mer > ((Link2)(firstElement())).m_merit) {
201        if (size() == m_MaxSize) {
202          removeLinkAt(m_MaxSize - 1);
203        }
204
205        //----------
206        insertElementAt(newL, 0);
207      }
208      else {
209        int i = 0;
210        int size = size();
211        boolean done = false;
212
213        //------------
214        // don't insert if list contains max elements an this
215        // is worst than the last
216        if ((size == m_MaxSize) && (mer <= ((Link2)(lastElement())).m_merit)) {
217
218        }
219        //---------------
220        else {
221          while ((!done) && (i < size)) {
222            if (mer > ((Link2)(elementAt(i))).m_merit) {
223              if (size == m_MaxSize) {
224                removeLinkAt(m_MaxSize - 1);
225              }
226
227              // ---------------------
228              insertElementAt(newL, i);
229              done = true;
230            }
231            else {if (i == size - 1) {
232              addElement(newL);
233              done = true;
234            }
235            else {
236              i++;
237            }
238            }
239          }
240        }
241      }
242      }
243    }
244   
245    /**
246     * Returns the revision string.
247     *
248     * @return          the revision
249     */
250    public String getRevision() {
251      return RevisionUtils.extract("$Revision: 1.29 $");
252    }
253  }
254
255  // member variables
256  /** maximum number of stale nodes before terminating search */
257  protected int m_maxStale;
258
259  /** 0 == backward search, 1 == forward search, 2 == bidirectional */
260  protected int m_searchDirection;
261
262  /** search direction: backward */
263  protected static final int SELECTION_BACKWARD = 0;
264  /** search direction: forward */
265  protected static final int SELECTION_FORWARD = 1;
266  /** search direction: bidirectional */
267  protected static final int SELECTION_BIDIRECTIONAL = 2;
268  /** search directions */
269  public static final Tag [] TAGS_SELECTION = {
270    new Tag(SELECTION_BACKWARD, "Backward"),
271    new Tag(SELECTION_FORWARD, "Forward"),
272    new Tag(SELECTION_BIDIRECTIONAL, "Bi-directional"),
273  };
274
275  /** holds an array of starting attributes */
276  protected int[] m_starting;
277
278  /** holds the start set for the search as a Range */
279  protected Range m_startRange;
280
281  /** does the data have a class */
282  protected boolean m_hasClass;
283
284  /** holds the class index */
285  protected int m_classIndex;
286
287  /** number of attributes in the data */
288  protected int m_numAttribs;
289
290  /** total number of subsets evaluated during a search */
291  protected int m_totalEvals;
292
293  /** for debugging */
294  protected boolean m_debug;
295
296  /** holds the merit of the best subset found */
297  protected double m_bestMerit;
298
299  /** holds the maximum size of the lookup cache for evaluated subsets */
300  protected int m_cacheSize;
301 
302  /**
303   * Returns a string describing this search method
304   * @return a description of the search method suitable for
305   * displaying in the explorer/experimenter gui
306   */
307  public String globalInfo() {
308    return "BestFirst:\n\n"
309      +"Searches the space of attribute subsets by greedy hillclimbing "
310      +"augmented with a backtracking facility. Setting the number of "
311      +"consecutive non-improving nodes allowed controls the level of "
312      +"backtracking done. Best first may start with the empty set of "
313      +"attributes and search forward, or start with the full set of "
314      +"attributes and search backward, or start at any point and search "
315      +"in both directions (by considering all possible single attribute "
316      +"additions and deletions at a given point).\n";
317  }
318
319  /**
320   *Constructor
321   */
322  public BestFirst () {
323    resetOptions();
324  }
325
326  /**
327   * Returns an enumeration describing the available options.
328   * @return an enumeration of all the available options.
329   *
330   **/
331  public Enumeration listOptions () {
332    Vector newVector = new Vector(4);
333   
334    newVector.addElement(new Option("\tSpecify a starting set of attributes." 
335                                    + "\n\tEg. 1,3,5-7."
336                                    ,"P",1
337                                    , "-P <start set>"));
338    newVector.addElement(new Option("\tDirection of search. (default = 1)."
339                                    , "D", 1
340                                    , "-D <0 = backward | 1 = forward " 
341                                    + "| 2 = bi-directional>"));
342    newVector.addElement(new Option("\tNumber of non-improving nodes to" 
343                                    + "\n\tconsider before terminating search."
344                                    , "N", 1, "-N <num>"));
345    newVector.addElement(new Option("\tSize of lookup cache for evaluated subsets."
346                                    +"\n\tExpressed as a multiple of the number of"
347                                    +"\n\tattributes in the data set. (default = 1)",
348                                    "S", 1, "-S <num>"));
349                                   
350    return  newVector.elements();
351  }
352
353
354  /**
355   * Parses a given list of options. <p/>
356   *
357   <!-- options-start -->
358   * Valid options are: <p/>
359   *
360   * <pre> -P &lt;start set&gt;
361   *  Specify a starting set of attributes.
362   *  Eg. 1,3,5-7.</pre>
363   *
364   * <pre> -D &lt;0 = backward | 1 = forward | 2 = bi-directional&gt;
365   *  Direction of search. (default = 1).</pre>
366   *
367   * <pre> -N &lt;num&gt;
368   *  Number of non-improving nodes to
369   *  consider before terminating search.</pre>
370   *
371   * <pre> -S &lt;num&gt;
372   *  Size of lookup cache for evaluated subsets.
373   *  Expressed as a multiple of the number of
374   *  attributes in the data set. (default = 1)</pre>
375   *
376   <!-- options-end -->
377   *
378   * @param options the list of options as an array of strings
379   * @throws Exception if an option is not supported
380   *
381   **/
382  public void setOptions (String[] options)
383    throws Exception {
384    String optionString;
385    resetOptions();
386
387    optionString = Utils.getOption('P', options);
388    if (optionString.length() != 0) {
389      setStartSet(optionString);
390    }
391
392    optionString = Utils.getOption('D', options);
393
394    if (optionString.length() != 0) {
395      setDirection(new SelectedTag(Integer.parseInt(optionString),
396                                   TAGS_SELECTION));
397    } else {
398      setDirection(new SelectedTag(SELECTION_FORWARD, TAGS_SELECTION));
399    }
400
401    optionString = Utils.getOption('N', options);
402
403    if (optionString.length() != 0) {
404      setSearchTermination(Integer.parseInt(optionString));
405    }
406
407    optionString = Utils.getOption('S', options);
408    if (optionString.length() != 0) {
409      setLookupCacheSize(Integer.parseInt(optionString));
410    }
411
412    m_debug = Utils.getFlag('Z', options);
413  }
414
415  /**
416   * Set the maximum size of the evaluated subset cache (hashtable). This is
417   * expressed as a multiplier for the number of attributes in the data set.
418   * (default = 1).
419   *
420   * @param size the maximum size of the hashtable
421   */
422  public void setLookupCacheSize(int size) {
423    if (size >= 0) {
424      m_cacheSize = size;
425    }
426  }
427
428  /**
429   * Return the maximum size of the evaluated subset cache (expressed as a multiplier
430   * for the number of attributes in a data set.
431   *
432   * @return the maximum size of the hashtable.
433   */
434  public int getLookupCacheSize() {
435    return m_cacheSize;
436  }
437
438  /**
439   * Returns the tip text for this property
440   * @return tip text for this property suitable for
441   * displaying in the explorer/experimenter gui
442   */
443  public String lookupCacheSizeTipText() {
444    return "Set the maximum size of the lookup cache of evaluated subsets. This is "
445      +"expressed as a multiplier of the number of attributes in the data set. "
446      +"(default = 1).";
447  }
448
449  /**
450   * Returns the tip text for this property
451   * @return tip text for this property suitable for
452   * displaying in the explorer/experimenter gui
453   */
454  public String startSetTipText() {
455    return "Set the start point for the search. This is specified as a comma "
456      +"seperated list off attribute indexes starting at 1. It can include "
457      +"ranges. Eg. 1,2,5-9,17.";
458  }
459
460  /**
461   * Sets a starting set of attributes for the search. It is the
462   * search method's responsibility to report this start set (if any)
463   * in its toString() method.
464   * @param startSet a string containing a list of attributes (and or ranges),
465   * eg. 1,2,6,10-15.
466   * @throws Exception if start set can't be set.
467   */
468  public void setStartSet (String startSet) throws Exception {
469    m_startRange.setRanges(startSet);
470  }
471
472  /**
473   * Returns a list of attributes (and or attribute ranges) as a String
474   * @return a list of attributes (and or attribute ranges)
475   */
476  public String getStartSet () {
477    return m_startRange.getRanges();
478  }
479
480  /**
481   * Returns the tip text for this property
482   * @return tip text for this property suitable for
483   * displaying in the explorer/experimenter gui
484   */
485  public String searchTerminationTipText() {
486    return "Set the amount of backtracking. Specify the number of ";
487  }
488
489  /**
490   * Set the numnber of non-improving nodes to consider before terminating
491   * search.
492   *
493   * @param t the number of non-improving nodes
494   * @throws Exception if t is less than 1
495   */
496  public void setSearchTermination (int t)
497    throws Exception {
498    if (t < 1) {
499      throw  new Exception("Value of -N must be > 0.");
500    }
501
502    m_maxStale = t;
503  }
504
505
506  /**
507   * Get the termination criterion (number of non-improving nodes).
508   *
509   * @return the number of non-improving nodes
510   */
511  public int getSearchTermination () {
512    return  m_maxStale;
513  }
514
515  /**
516   * Returns the tip text for this property
517   * @return tip text for this property suitable for
518   * displaying in the explorer/experimenter gui
519   */
520  public String directionTipText() {
521    return "Set the direction of the search.";
522  }
523
524  /**
525   * Set the search direction
526   *
527   * @param d the direction of the search
528   */
529  public void setDirection (SelectedTag d) {
530   
531    if (d.getTags() == TAGS_SELECTION) {
532      m_searchDirection = d.getSelectedTag().getID();
533    }
534  }
535
536
537  /**
538   * Get the search direction
539   *
540   * @return the direction of the search
541   */
542  public SelectedTag getDirection () {
543
544    return new SelectedTag(m_searchDirection, TAGS_SELECTION);
545  }
546
547
548  /**
549   * Gets the current settings of BestFirst.
550   * @return an array of strings suitable for passing to setOptions()
551   */
552  public String[] getOptions () {
553    String[] options = new String[6];
554    int current = 0;
555
556    if (!(getStartSet().equals(""))) {
557      options[current++] = "-P";
558      options[current++] = ""+startSetToString();
559    }
560    options[current++] = "-D";
561    options[current++] = "" + m_searchDirection;
562    options[current++] = "-N";
563    options[current++] = "" + m_maxStale;
564
565    while (current < options.length) {
566      options[current++] = "";
567    }
568
569    return  options;
570  }
571
572  /**
573   * converts the array of starting attributes to a string. This is
574   * used by getOptions to return the actual attributes specified
575   * as the starting set. This is better than using m_startRanges.getRanges()
576   * as the same start set can be specified in different ways from the
577   * command line---eg 1,2,3 == 1-3. This is to ensure that stuff that
578   * is stored in a database is comparable.
579   * @return a comma seperated list of individual attribute numbers as a String
580   */
581  private String startSetToString() {
582    StringBuffer FString = new StringBuffer();
583    boolean didPrint;
584
585    if (m_starting == null) {
586      return getStartSet();
587    }
588    for (int i = 0; i < m_starting.length; i++) {
589      didPrint = false;
590     
591      if ((m_hasClass == false) || 
592          (m_hasClass == true && i != m_classIndex)) {
593        FString.append((m_starting[i] + 1));
594        didPrint = true;
595      }
596     
597      if (i == (m_starting.length - 1)) {
598        FString.append("");
599      }
600      else {
601        if (didPrint) {
602          FString.append(",");
603          }
604      }
605    }
606
607    return FString.toString();
608  }
609
610  /**
611   * returns a description of the search as a String
612   * @return a description of the search
613   */
614  public String toString () {
615    StringBuffer BfString = new StringBuffer();
616    BfString.append("\tBest first.\n\tStart set: ");
617
618    if (m_starting == null) {
619      BfString.append("no attributes\n");
620    }
621    else {
622      BfString.append(startSetToString()+"\n");
623    }
624
625    BfString.append("\tSearch direction: ");
626
627    if (m_searchDirection == SELECTION_BACKWARD) {
628      BfString.append("backward\n");
629    }
630    else {if (m_searchDirection == SELECTION_FORWARD) {
631      BfString.append("forward\n");
632    }
633    else {
634      BfString.append("bi-directional\n");
635    }
636    }
637
638    BfString.append("\tStale search after " 
639                    + m_maxStale + " node expansions\n");
640    BfString.append("\tTotal number of subsets evaluated: " 
641                    + m_totalEvals + "\n");
642    BfString.append("\tMerit of best subset found: "
643                    +Utils.doubleToString(Math.abs(m_bestMerit),8,3)+"\n");
644    return  BfString.toString();
645  }
646
647
648  protected void printGroup (BitSet tt, int numAttribs) {
649    int i;
650
651    for (i = 0; i < numAttribs; i++) {
652      if (tt.get(i) == true) {
653        System.out.print((i + 1) + " ");
654      }
655    }
656
657    System.out.println();
658  }
659
660
661  /**
662   * Searches the attribute subset space by best first search
663   *
664   * @param ASEval the attribute evaluator to guide the search
665   * @param data the training instances.
666   * @return an array (not necessarily ordered) of selected attribute indexes
667   * @throws Exception if the search can't be completed
668   */
669  public int[] search (ASEvaluation ASEval, Instances data)
670    throws Exception {
671    m_totalEvals = 0;
672    if (!(ASEval instanceof SubsetEvaluator)) {
673      throw  new Exception(ASEval.getClass().getName() 
674                           + " is not a " 
675                           + "Subset evaluator!");
676    }
677
678    if (ASEval instanceof UnsupervisedSubsetEvaluator) {
679      m_hasClass = false;
680    } else {
681      m_hasClass = true;
682      m_classIndex = data.classIndex();
683    }
684
685    SubsetEvaluator ASEvaluator = (SubsetEvaluator)ASEval;
686    m_numAttribs = data.numAttributes();
687    int i, j;
688    int best_size = 0;
689    int size = 0;
690    int done;
691    int sd = m_searchDirection;
692    BitSet best_group, temp_group;
693    int stale;
694    double best_merit;
695    double merit;
696    boolean z;
697    boolean added;
698    Link2 tl;
699    Hashtable lookup = new Hashtable(m_cacheSize * m_numAttribs);
700    int insertCount = 0;
701    int cacheHits = 0;
702    LinkedList2 bfList = new LinkedList2(m_maxStale);
703    best_merit = -Double.MAX_VALUE;
704    stale = 0;
705    best_group = new BitSet(m_numAttribs);
706
707    m_startRange.setUpper(m_numAttribs-1);
708    if (!(getStartSet().equals(""))) {
709      m_starting = m_startRange.getSelection();
710    }
711    // If a starting subset has been supplied, then initialise the bitset
712    if (m_starting != null) {
713      for (i = 0; i < m_starting.length; i++) {
714        if ((m_starting[i]) != m_classIndex) {
715          best_group.set(m_starting[i]);
716        }
717      }
718
719      best_size = m_starting.length;
720      m_totalEvals++;
721    } else {
722      if (m_searchDirection == SELECTION_BACKWARD) {
723        setStartSet("1-last");
724        m_starting = new int[m_numAttribs];
725
726        // init initial subset to all attributes
727        for (i = 0, j = 0; i < m_numAttribs; i++) {
728          if (i != m_classIndex) {
729            best_group.set(i);
730            m_starting[j++] = i;
731          }
732        }
733
734        best_size = m_numAttribs - 1;
735        m_totalEvals++;
736      }
737    }
738
739    // evaluate the initial subset
740    best_merit = ASEvaluator.evaluateSubset(best_group);
741    // add the initial group to the list and the hash table
742    Object [] best = new Object[1];
743    best[0] = best_group.clone();
744    bfList.addToList(best, best_merit);
745    BitSet tt = (BitSet)best_group.clone();
746    String hashC = tt.toString();
747    lookup.put(hashC, new Double(best_merit));
748
749    while (stale < m_maxStale) {
750      added = false;
751
752      if (m_searchDirection == SELECTION_BIDIRECTIONAL) {
753        // bi-directional search
754        done = 2;
755        sd = SELECTION_FORWARD;
756      } else {
757        done = 1;
758      }
759
760      // finished search?
761      if (bfList.size() == 0) {
762        stale = m_maxStale;
763        break;
764      }
765
766      // copy the attribute set at the head of the list
767      tl = bfList.getLinkAt(0);
768      temp_group = (BitSet)(tl.getData()[0]);
769      temp_group = (BitSet)temp_group.clone();
770      // remove the head of the list
771      bfList.removeLinkAt(0);
772      // count the number of bits set (attributes)
773      int kk;
774
775      for (kk = 0, size = 0; kk < m_numAttribs; kk++) {
776        if (temp_group.get(kk)) {
777          size++;
778        }
779      }
780
781      do {
782        for (i = 0; i < m_numAttribs; i++) {
783          if (sd == SELECTION_FORWARD) {
784            z = ((i != m_classIndex) && (!temp_group.get(i)));
785          } else {
786            z = ((i != m_classIndex) && (temp_group.get(i)));
787          }
788         
789          if (z) {
790            // set the bit (attribute to add/delete)
791            if (sd == SELECTION_FORWARD) {
792              temp_group.set(i);
793              size++;
794            } else {
795              temp_group.clear(i);
796              size--;
797            }
798
799            /* if this subset has been seen before, then it is already
800               in the list (or has been fully expanded) */
801            tt = (BitSet)temp_group.clone();
802            hashC = tt.toString();
803           
804            if (lookup.containsKey(hashC) == false) {
805              merit = ASEvaluator.evaluateSubset(temp_group);
806              m_totalEvals++;
807             
808              // insert this one in the hashtable
809              if (insertCount > m_cacheSize * m_numAttribs) {
810                lookup = new Hashtable(m_cacheSize * m_numAttribs);
811                insertCount = 0;
812              }
813              hashC = tt.toString();
814              lookup.put(hashC, new Double(merit));
815              insertCount++;
816            } else {
817              merit = ((Double)lookup.get(hashC)).doubleValue();
818              cacheHits++; 
819            }
820           
821            // insert this one in the list
822            Object[] add = new Object[1];
823            add[0] = tt.clone();
824            bfList.addToList(add, merit);
825           
826            if (m_debug) {
827              System.out.print("Group: ");
828              printGroup(tt, m_numAttribs);
829              System.out.println("Merit: " + merit);
830            }
831
832            // is this better than the best?
833            if (sd == SELECTION_FORWARD) {
834              z = ((merit - best_merit) > 0.00001);
835            } else {
836              if (merit == best_merit) {
837                z = (size < best_size);
838              } else {
839                z = (merit >  best_merit);
840              } 
841            }
842
843            if (z) {
844              added = true;
845              stale = 0;
846              best_merit = merit;
847              //                best_size = (size + best_size);
848              best_size = size;
849              best_group = (BitSet)(temp_group.clone());
850            }
851
852            // unset this addition(deletion)
853            if (sd == SELECTION_FORWARD) {
854              temp_group.clear(i);
855              size--;
856            } else {
857              temp_group.set(i);
858              size++;
859            }
860          }
861        }
862
863        if (done == 2) {
864          sd = SELECTION_BACKWARD;
865        }
866
867        done--;
868      } while (done > 0);
869
870      /* if we haven't added a new attribute subset then full expansion
871         of this node hasen't resulted in anything better */
872      if (!added) {
873        stale++;
874      }
875    }
876
877    m_bestMerit = best_merit;
878    return  attributeList(best_group);
879  }
880
881
882  /**
883   * Reset options to default values
884   */
885  protected void resetOptions () {
886    m_maxStale = 5;
887    m_searchDirection = SELECTION_FORWARD;
888    m_starting = null;
889    m_startRange = new Range();
890    m_classIndex = -1;
891    m_totalEvals = 0;
892    m_cacheSize = 1;
893    m_debug = false;
894  }
895
896
897  /**
898   * converts a BitSet into a list of attribute indexes
899   * @param group the BitSet to convert
900   * @return an array of attribute indexes
901   **/
902  protected int[] attributeList (BitSet group) {
903    int count = 0;
904
905    // count how many were selected
906    for (int i = 0; i < m_numAttribs; i++) {
907      if (group.get(i)) {
908        count++;
909      }
910    }
911
912    int[] list = new int[count];
913    count = 0;
914
915    for (int i = 0; i < m_numAttribs; i++) {
916      if (group.get(i)) {
917        list[count++] = i;
918      }
919    }
920
921    return  list;
922  }
923 
924  /**
925   * Returns the revision string.
926   *
927   * @return            the revision
928   */
929  public String getRevision() {
930    return RevisionUtils.extract("$Revision: 1.29 $");
931  }
932}
Note: See TracBrowser for help on using the repository browser.