View Javadoc
1   package com.github.lstephen.ai.search;
2   
3   import com.github.lstephen.ai.search.action.ActionGenerator;
4   
5   import java.util.Optional;
6   
7   import com.google.common.base.Preconditions;
8   import com.google.common.collect.Ordering;
9   
10  /**
11   * <p>
12   *   Hillclimbing search to optimize a solution for a given {@link Heuristic}.
13   * </p>
14   *
15   * <p>
16   *   The following are provided to enable the search:
17   * </p>
18   * <ul>
19   *   <li>
20   *     {@link Heuristic} -
21   *     To determine which solutions are better than another.
22   *   </li>
23   *
24   *   <li>
25   *     {@link ActionGenerator} -
26   *     Provides the possible {@link Action}s that transform the current solution into
27   *     another solution.
28   *   </li>
29   *
30   *   <li>
31   *     {@link Validator} -
32   *     Determines if a given solution is valid.
33   *     This allows the {@link ActionGenerator} to provide {@link Action}s that may lead
34   *     to invalid solutions.
35   *     Sometimes this is useful for performance reasons.
36   *   </li>
37   * </ul>
38   *
39   * <p>
40   *   The {@link Validator} is optional and if not provided it's assumed that all solutions
41   *   are valid.
42   * </p>
43   *
44   * @param <S> type of solution to optimize
45   */
46  public final class HillClimbing<S> {
47  
48    private final Validator<S> validator;
49  
50    private final Heuristic<S> heuristic;
51  
52    private final ActionGenerator<S> actionGenerator;
53  
54    private HillClimbing(Builder<S> builder) {
55      Preconditions.checkNotNull(builder.validator);
56      Preconditions.checkNotNull(builder.heuristic);
57      Preconditions.checkNotNull(builder.actionGenerator);
58  
59      this.validator = builder.validator;
60      this.heuristic = builder.heuristic;
61      this.actionGenerator = builder.actionGenerator;
62    }
63  
64    /**
65     * The {@link Heuristic} being used for the search.
66     *
67     * @return heuristic used for search.
68     */
69    public Heuristic<S> getHeuristic() {
70      return heuristic;
71    }
72  
73    /**
74     * Search the solution space starting from the given starting point.
75     *
76     * @param initial the starting point
77     * @return the optimized solution
78     */
79    public S search(S initial) {
80      S current = initial;
81  
82      Optional<S> next = next(current);
83  
84      while (next.isPresent()) {
85        current = next.get();
86  
87        next = next(current);
88      }
89  
90      return current;
91    }
92  
93    private Optional<S> next(S current) {
94      return actionGenerator
95        .apply(current)
96        .map((a) -> a.apply(current))
97        .filter(validator)
98        .filter((n) -> heuristic.compare(current, n) < 0)
99        .findFirst();
100   }
101 
102   /**
103    * Get a {@link Builder} that can be used to create a {@link HillClimbing} instance.
104    *
105    * @param <S> type of solution to optimize
106    * @return a builder for constructing an instance
107    */
108   public static <S> Builder<S> builder() {
109     return Builder.create();
110   }
111 
112   private static <S> HillClimbing<S> build(Builder<S> builder) {
113     return new HillClimbing<>(builder);
114   }
115 
116   /**
117    * <p>
118    *   Builder for creating {@link HillClimbing} instances.
119    * </p>
120    *
121    * <p>
122    *   Example:
123    * </p>
124    *
125    * <p><blockquote><pre>{@code
126    *   HillClimbing<MyClass> search = Hillclimbing
127    *     .builder()
128    *     .validator((s) -> s.isValid())
129    *     .heuristic((s) -> s.getScore())
130    *     .actionGenerator((s) -> s.getTransformations())
131    *     .build();
132    * }</pre></blockquote></p>
133    *
134    * @param <S> solution type
135    */
136   public static final class Builder<S> {
137 
138     private Validator<S> validator = (s) -> true;
139 
140     private Heuristic<S> heuristic;
141 
142     private ActionGenerator<S> actionGenerator;
143 
144     private Builder() { }
145 
146     public Builder<S> validator(Validator<S> validator) {
147       this.validator = validator;
148       return this;
149     }
150 
151     public Builder<S> heuristic(Heuristic<S> heurisitc) {
152       this.heuristic = heurisitc;
153       return this;
154     }
155 
156     public Builder<S> heuristic(final Ordering<? super S> ordering) {
157       return heuristic(ordering::compare);
158     }
159 
160     public Builder<S> actionGenerator(ActionGenerator<S> actionGenerator) {
161       this.actionGenerator = actionGenerator;
162       return this;
163     }
164 
165     /**
166      * Build the {@link HillClimbing} instance.
167      *
168      * @return the constructed instance
169      */
170     public HillClimbing<S> build() {
171       return HillClimbing.build(this);
172     }
173 
174     private static <S> Builder<S> create() {
175       return new Builder<S>();
176     }
177   }
178 
179 }