001package com.hfg.math;
002
003import com.hfg.util.collection.CollectionUtil;
004
005import java.util.ArrayList;
006import java.util.Collection;
007import java.util.List;
008
009//------------------------------------------------------------------------------
010/**
011 * Iterator that steps through the possible combinations of a List of Lists.
012 * @author J. Alex Taylor, hairyfatguy.com
013 */
014//------------------------------------------------------------------------------
015// com.hfg Library
016//
017// This library is free software; you can redistribute it and/or
018// modify it under the terms of the GNU Lesser General Public
019// License as published by the Free Software Foundation; either
020// version 2.1 of the License, or (at your option) any later version.
021//
022// This library is distributed in the hope that it will be useful,
023// but WITHOUT ANY WARRANTY; without even the implied warranty of
024// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
025// Lesser General Public License for more details.
026//
027// You should have received a copy of the GNU Lesser General Public
028// License along with this library; if not, write to the Free Software
029// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
030//
031// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
032// jataylor@hairyfatguy.com
033//------------------------------------------------------------------------------
034
035public class ListCombinationIterator<T>
036{
037   private List<List<T>>       mListOfLists;
038   private Range<Integer>      mCombinationSizeRange;
039   private Integer             mCurrentCombinationSize;
040   private List<List<Integer>> mCurrentPositionIndexCombinations;
041   private int                 mCurrentPositionIndexCombinationsIndex;
042   private List<Integer>       mCurrentPositionIndices;
043   private int[]               mCurrentIndices;
044   private int                 mSize;
045
046   //###########################################################################
047   // CONSTRUCTORS
048   //###########################################################################
049
050   //---------------------------------------------------------------------------
051   public ListCombinationIterator(List<? extends Collection<T>> inListOfLists)
052   {
053     this(inListOfLists, inListOfLists != null ? new Range<>(inListOfLists.size(), inListOfLists.size()) : null);
054   }
055
056   //---------------------------------------------------------------------------
057   public ListCombinationIterator(List<? extends Collection<T>> inListOfLists, Range<Integer> inCombinationSizeRange)
058   {
059      if (inListOfLists.get(0) instanceof List)
060      {
061         mListOfLists = (List<List<T>>) inListOfLists;
062      }
063      else
064      {
065         mListOfLists = new ArrayList<>(inListOfLists.size());
066         for (Collection<T> collection : inListOfLists)
067         {
068            mListOfLists.add(new ArrayList<>(collection));
069         }
070      }
071      mCombinationSizeRange = inCombinationSizeRange;
072
073      // Set the size - the total number of combinations
074      if (CollectionUtil.hasValues(mListOfLists))
075      {
076         mSize = 0;
077
078         Integer minSize = (inCombinationSizeRange != null && inCombinationSizeRange.getStart() != null ? inCombinationSizeRange.getStart() : 1);
079         Integer maxSize = (inCombinationSizeRange != null && inCombinationSizeRange.getEnd() != null ? inCombinationSizeRange.getEnd() : mListOfLists.size());
080         if (maxSize > mListOfLists.size())
081         {
082            maxSize = mListOfLists.size();
083         }
084
085         mCombinationSizeRange = new Range<>();
086         mCombinationSizeRange.setStart(minSize).setEnd(maxSize);
087
088         for (int combinationSize = minSize; combinationSize <= maxSize; combinationSize++)
089         {
090            List<Integer> indexList = new ArrayList<>(inListOfLists.size());
091            for (int i = 0; i < inListOfLists.size(); i++)
092            {
093               indexList.add(i);
094            }
095
096            List<List<Integer>> indexCombinations = Combinations.combinations(indexList, combinationSize);
097
098            for (List<Integer> indices : indexCombinations)
099            {
100               int size = 1;
101               for (int index : indices)
102               {
103                  size *= inListOfLists.get(index).size();
104               }
105
106               mSize += size;
107            }
108         }
109      }
110   }
111
112   //###########################################################################
113   // PUBLIC METHODS
114   //###########################################################################
115
116   //---------------------------------------------------------------------------
117   private List<List<Integer>> getPositionIndexCombinations()
118   {
119      List<Integer> indexList = new ArrayList<>(mListOfLists.size());
120      for (int i = 0; i < mListOfLists.size(); i++)
121      {
122         indexList.add(i);
123      }
124
125      return Combinations.combinations(indexList, mCurrentCombinationSize);
126   }
127
128   //---------------------------------------------------------------------------
129   public boolean hasNext()
130   {
131      boolean hasNext = false;
132      if (CollectionUtil.hasValues(mListOfLists))
133      {
134         if (null == mCurrentCombinationSize)
135         {
136            mCurrentCombinationSize = mCombinationSizeRange.getStart();
137            hasNext = true;
138         }
139         else
140         {
141            while (! hasNext
142                   && mCurrentCombinationSize <= mCombinationSizeRange.getEnd()
143                   && mCurrentPositionIndexCombinationsIndex < mCurrentPositionIndexCombinations.size())
144            {
145               if (null == mCurrentPositionIndexCombinations
146                     || mCurrentPositionIndexCombinationsIndex < mCurrentPositionIndexCombinations.size() - 1
147                     || null == mCurrentIndices)
148               {
149                  hasNext = true;
150               }
151               else
152               {
153                  for (int i = 0; i < mCurrentIndices.length; i++)
154                  {
155                     if (mCurrentIndices[i] < mListOfLists.get(mCurrentPositionIndices.get(i)).size() - 1)
156                     {
157                        hasNext = true;
158                        break;
159                     }
160                  }
161
162                  if (! hasNext)
163                  {
164                     // We've hit the end of combinations for this combination size.
165                     mCurrentCombinationSize++;
166                     if (mCurrentCombinationSize <= mCombinationSizeRange.getEnd())
167                     {
168                        mCurrentPositionIndexCombinations = getPositionIndexCombinations();
169                        mCurrentPositionIndexCombinationsIndex = 0;
170                        mCurrentPositionIndices = mCurrentPositionIndexCombinations.get(mCurrentPositionIndexCombinationsIndex);
171                        mCurrentIndices = null;
172                     }
173                  }
174               }
175            }
176         }
177      }
178
179      return hasNext;
180   }
181
182   //---------------------------------------------------------------------------
183   public List<T> next()
184   {
185      List<T> combination = null;
186
187      if (null == mCurrentCombinationSize
188            || mCurrentCombinationSize <= mCombinationSizeRange.getEnd())
189      {
190         boolean combinationFound = false;
191
192
193         if (null == mCurrentCombinationSize)
194         {
195            mCurrentCombinationSize = mCombinationSizeRange.getStart();
196         }
197
198         if (null == mCurrentPositionIndexCombinations)
199         {
200            mCurrentPositionIndexCombinations = getPositionIndexCombinations();
201            mCurrentPositionIndexCombinationsIndex = 0;
202            mCurrentPositionIndices = mCurrentPositionIndexCombinations.get(mCurrentPositionIndexCombinationsIndex);
203         }
204
205         if (null == mCurrentIndices)
206         {
207            mCurrentIndices = new int[mCurrentCombinationSize];
208            for (int i = 0; i < mCurrentCombinationSize; i++)
209            {
210               mCurrentIndices[i] = 0;
211            }
212
213            combinationFound = true;
214         }
215         else
216         {
217            // Iterate the indices
218            int i;
219
220            while (! combinationFound
221                   && mCurrentCombinationSize <= mCombinationSizeRange.getEnd()
222                   && mCurrentPositionIndexCombinationsIndex < mCurrentPositionIndexCombinations.size())
223            {
224               for (i = 0; i < mCurrentCombinationSize; i++)
225               {
226                  if (mCurrentIndices[i] < mListOfLists.get(mCurrentPositionIndices.get(i)).size() - 1)
227                  {
228                     mCurrentIndices[i]++;
229                     break;
230                  }
231                  else
232                  {
233                     mCurrentIndices[i] = 0;
234                  }
235               }
236
237               // Have we run out of combinations for this set of positions?
238               if (i < mCurrentCombinationSize)
239               {
240                  combinationFound = true;
241               }
242               else
243               {
244                  mCurrentPositionIndexCombinationsIndex++;
245                  if (mCurrentPositionIndexCombinationsIndex >= mCurrentPositionIndexCombinations.size())
246                  {
247                     mCurrentCombinationSize++;
248                     if (mCurrentCombinationSize <= mCombinationSizeRange.getEnd())
249                     {
250                        mCurrentPositionIndexCombinations = getPositionIndexCombinations();
251                        mCurrentPositionIndexCombinationsIndex = 0;
252                        mCurrentPositionIndices = mCurrentPositionIndexCombinations.get(mCurrentPositionIndexCombinationsIndex);
253                        mCurrentIndices = new int[mCurrentCombinationSize];
254                        for (int index = 0; index < mCurrentCombinationSize; index++)
255                        {
256                           mCurrentIndices[index] = 0;
257                        }
258
259                        combinationFound = true;
260                     }
261                  }
262                  else
263                  {
264                     mCurrentPositionIndices = mCurrentPositionIndexCombinations.get(mCurrentPositionIndexCombinationsIndex);
265                     for (int index = 0; index < mCurrentCombinationSize; index++)
266                     {
267                        mCurrentIndices[index] = 0;
268                     }
269
270                     combinationFound = true;
271                  }
272               }
273            }
274         }
275
276         if (combinationFound)
277         {
278            combination = new ArrayList<>(mCurrentCombinationSize);
279
280            for (int i = 0; i < mCurrentCombinationSize; i++)
281            {
282               combination.add(mListOfLists.get(mCurrentPositionIndices.get(i)).get(mCurrentIndices[i]));
283            }
284         }
285      }
286
287      return combination;
288   }
289
290   //---------------------------------------------------------------------------
291   public int size()
292   {
293      return mSize;
294   }
295
296   //---------------------------------------------------------------------------
297   public List<List<T>> getAll()
298   {
299      mCurrentCombinationSize = null;
300      mCurrentPositionIndexCombinations = null;
301      mCurrentPositionIndices = null;
302      mCurrentIndices = null;
303
304      List<List<T>> combinations = new ArrayList<>(mSize);
305      while (hasNext())
306      {
307         combinations.add(next());
308      }
309
310      return combinations;
311   }
312}