View Javadoc

1   /*
2    * Copyright 2010 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.callbackparams.combine;
18  
19  import java.util.IdentityHashMap;
20  import java.util.Iterator;
21  import java.util.Map;
22  import java.util.Random;
23  
24  /**
25   * @author Henrik Kaipe
26   */
27  class Default2CombinationPicker implements AllPossible2Combinations.Picker {
28  
29      private final int index;
30      private final ValueWrapper[] wrappedValues;
31      private int greatestCombinationCount = 1;
32      private int nbrOf2CombinationsToSatisfy = 0;
33      private final Random random;
34      
35      private int totalCount;
36  
37      class CombinationCounter {
38          private int count = 0;
39  
40          public CombinationCounter() {
41              ++nbrOf2CombinationsToSatisfy;
42          }
43  
44          int getCount() {return count; }
45  
46          void increaseCount() {
47              if (1 == ++count) {
48                  if (--nbrOf2CombinationsToSatisfy < 0) {
49                      throw new AssertionError("Negativ number of 2-combinations"
50                              + " at total count = " + totalCount);
51                  }
52              } else if (greatestCombinationCount < count) {
53                  greatestCombinationCount = count;
54              }
55          }
56          
57          /** Useful for debugging */
58          public String toString() {return "" + count;}
59      }
60      
61      private transient Object[] record;
62      private transient int currentRecordIndex;
63      private transient int currentCount;
64      private transient final ValueWrapper[] options, selections;
65      private transient int nbrOfOptions, nbrOfSelections;
66      private transient boolean needyFirst;
67      
68      private transient boolean selectedCountCorrect;
69      private transient int selectedTotalNeeds;
70      
71      class ValueWrapper {
72          
73          private final Object value;
74          private final Map[] combinationCounters = new Map[index];
75          
76          private int valueCount = 0;
77  
78          ValueWrapper(final Object value, final Object[][] allValues) {
79              this.value = value;
80              
81              for (int mapIx = 0 ; mapIx < index ; ++mapIx) {
82                  combinationCounters[mapIx] = new IdentityHashMap();
83                  
84                  final Object[] combinationsOtherValues = allValues[mapIx];
85                  for (int otherValuesIx = 0
86                          ; otherValuesIx < combinationsOtherValues.length
87                          ; ++otherValuesIx) {
88                      combinationCounters[mapIx].put(
89                              combinationsOtherValues[otherValuesIx],
90                              new CombinationCounter());
91                  }
92              }
93          }
94          
95          private void select(boolean isCountCorrect, int totalNeeds) {
96              selectedCountCorrect = isCountCorrect;
97              selectedTotalNeeds = totalNeeds;
98              selections[0] = this;
99              nbrOfSelections = 1;
100         }
101         
102         private CombinationCounter combinationCount() {
103             return (CombinationCounter) combinationCounters[currentRecordIndex]
104                     .get(record[currentRecordIndex]);
105         }
106         
107         private int totalNeeds() {
108             int needCount = 0;
109             for (Iterator iter =
110                     combinationCounters[currentRecordIndex].values().iterator()
111                     ; iter.hasNext() ;) {
112                 final CombinationCounter cc = (CombinationCounter) iter.next();
113                 
114                 if (currentCount == cc.getCount()) {
115                     ++needCount;
116                 }
117             }
118             return needCount;
119         }
120         
121         void consider() {
122             final boolean isThisCountCorrect =
123                     currentCount == combinationCount().getCount();
124             if (0 == nbrOfSelections) {
125                 select(isThisCountCorrect, totalNeeds());
126                 return;
127             }
128             
129             if (selectedCountCorrect && false == isThisCountCorrect) {
130                 /* This option is dropped */
131                 return;
132             }
133             
134             final int thisTotalNeeds = totalNeeds();
135             
136             if (isThisCountCorrect == selectedCountCorrect
137                     && thisTotalNeeds == selectedTotalNeeds) {
138                 selections[nbrOfSelections] = this;
139                 ++nbrOfSelections;
140                 return;
141                 
142             } else if (false == isThisCountCorrect) {
143                 if (thisTotalNeeds < selectedTotalNeeds) {
144                     select(isThisCountCorrect, thisTotalNeeds);
145                 } else {
146                     /* This option is dropped */
147                 }
148                 return;
149                 
150             } else if (false == selectedCountCorrect
151                     || needyFirst && selectedTotalNeeds < thisTotalNeeds
152                     || !needyFirst && thisTotalNeeds < selectedTotalNeeds) {
153                 select(isThisCountCorrect, thisTotalNeeds);
154                 return;
155                 
156             } else {
157                 /* This option is dropped */
158                 return;
159             }
160         }
161         
162         int getCount() {
163             return valueCount;
164         }
165         
166         void pickValue() {
167             record[index] = value;
168             for (currentRecordIndex = 0
169                     ; currentRecordIndex < index ; ++currentRecordIndex) {
170                 combinationCount().increaseCount();
171             }
172             record = null;//To raise NullPointer if same record is used twice
173             ++totalCount;
174             ++valueCount;
175         }
176     }
177 
178     Default2CombinationPicker(
179             final int index, final Object[][] values, final Random random) {
180         this.index = index;
181         this.wrappedValues = new ValueWrapper[values[index].length];
182         this.random = random;
183         
184         for (int i = 0 ; i < this.wrappedValues.length ; ++i) {
185             wrappedValues[i] = new ValueWrapper(values[index][i], values);
186         }
187         this.options = new ValueWrapper[wrappedValues.length];
188         this.selections = new ValueWrapper[wrappedValues.length];
189     }
190 
191     private void setupOptions() {
192         final int countForOptions = totalCount / wrappedValues.length;
193         nbrOfOptions = 0;
194         for (int i = 0 ; i < wrappedValues.length ; ++i) {
195             final ValueWrapper vw = wrappedValues[i];
196             
197             if (countForOptions == vw.getCount()) {
198                 options[nbrOfOptions] = vw;
199                 ++nbrOfOptions;
200             }
201         }
202 
203         /*
204          * The rules by which needyFirst is determined are questionable.
205          * The value set below has proven to be better than both of the
206          * constant values "true" and "false" when using the random-seed
207          * 1216208375531 and running the test
208          * TestAllPossible2CombinatinsWith3ValuesArraysLongerThan1.test444()
209          * (As least if we look to how things were on 2008-07-16).
210          */
211         needyFirst = 2 * nbrOfOptions <= wrappedValues.length;
212         
213         nbrOfSelections = 0;
214     }
215 
216     public void pick(final Object[] record) {
217         this.record = record;
218         
219         if (totalCount < wrappedValues.length) {
220             wrappedValues[totalCount].pickValue();
221             return;
222         }
223         
224         setupOptions();
225         if (1 == nbrOfOptions) {
226             options[0].pickValue();
227             return;
228         }
229         
230         assert 2 <= nbrOfOptions;
231         for (currentCount = all2CombinationsAreSatisfied() ? 1 : 0
232                 ; currentCount < greatestCombinationCount ; ++currentCount)
233         for (currentRecordIndex = 0
234                 ; currentRecordIndex < index ; ++currentRecordIndex)
235         {
236             for (int i = 0 ; i < nbrOfOptions ; ++i) {
237                 options[i].consider();
238             }
239             
240             if (1 == nbrOfSelections) {
241                 selections[0].pickValue();
242                 return;
243                 
244             } else {
245                 assert 2 <= nbrOfSelections;
246                 System.arraycopy(selections, 0, options, 0, nbrOfSelections);
247                 nbrOfOptions = nbrOfSelections;
248                 nbrOfSelections = 0;
249             }
250         }
251         
252         /* Last resort - pick randomly among the remaining options */
253         options[random.nextInt(nbrOfOptions)].pickValue();
254     }
255 
256     public boolean all2CombinationsAreSatisfied() {
257         return 0 == nbrOf2CombinationsToSatisfy;
258     }
259 }