001package com.hfg.math;
002
003import com.hfg.util.collection.OrderedSet;
004
005import java.math.BigInteger;
006import java.util.*;
007
008//------------------------------------------------------------------------------
009/**
010 * Iterator for enumerating the possible set partitions of a specified set.
011 * @author J. Alex Taylor, hairyfatguy.com
012 */
013//------------------------------------------------------------------------------
014// com.hfg Library
015//
016// This library is free software; you can redistribute it and/or
017// modify it under the terms of the GNU Lesser General Public
018// License as published by the Free Software Foundation; either
019// version 2.1 of the License, or (at your option) any later version.
020//
021// This library is distributed in the hope that it will be useful,
022// but WITHOUT ANY WARRANTY; without even the implied warranty of
023// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
024// Lesser General Public License for more details.
025//
026// You should have received a copy of the GNU Lesser General Public
027// License along with this library; if not, write to the Free Software
028// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
029//
030// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
031// jataylor@hairyfatguy.com
032//------------------------------------------------------------------------------
033
034public class SetPartitionIterator<T> implements Iterator<List<Set<T>>>
035{
036   public static enum Flag
037   {
038      GROUP_ORDER_MATTERS
039   }
040
041   private Set<Flag> mFlags = new HashSet<>(4);
042
043   private OrderedSet<T> mSetToPartition;
044   private Integer       mNumGroups;
045   private Counter[]     mPermutationIndices;
046
047   //##########################################################################
048   // CONSTRUCTORS
049   //##########################################################################
050
051   //--------------------------------------------------------------------------
052   public SetPartitionIterator(Set<T> inSetToPartition, Integer inNumGroups, Flag... inFlags)
053   {
054      mSetToPartition = new OrderedSet<T>(inSetToPartition);
055      mNumGroups = inNumGroups;
056
057      if (inFlags != null)
058      {
059         for (int i = 0; i < inFlags.length; i++)
060         {
061            mFlags.add(inFlags[i]);
062         }
063      }
064
065      init();
066   }
067
068   //##########################################################################
069   // PUBLIC FUNCTIONS
070   //##########################################################################
071
072   //--------------------------------------------------------------------------
073   public boolean hasNext()
074   {
075      return iterate();
076   }
077
078   //--------------------------------------------------------------------------
079   @Override
080   public List<Set<T>> next()
081   {
082      int size = numOfGroupsInCurrentReading();
083      List<Set<T>> partitionedSetCombination = new ArrayList<Set<T>>(size);
084      for (int i = 0; i < size; i++)
085      {
086         partitionedSetCombination.add(new OrderedSet<T>(mSetToPartition.size()));
087      }
088
089      int i = 0;
090      for (T obj : mSetToPartition)
091      {
092         partitionedSetCombination.get(mPermutationIndices[i].intValue()).add(obj);
093         i++;
094      }
095
096      return partitionedSetCombination;
097   }
098
099
100   //##########################################################################
101   // PRIVATE FUNCTIONS
102   //##########################################################################
103
104   //--------------------------------------------------------------------------
105   private void init()
106   {
107      mPermutationIndices = new Counter[mSetToPartition.size()];
108      for (int i = 0; i < mSetToPartition.size(); i++)
109      {
110         mPermutationIndices[i] = new Counter();
111      }
112   }
113
114   //--------------------------------------------------------------------------
115   private boolean iterate()
116   {
117      boolean result = true;
118      boolean acceptable = false;
119
120      while (result && ! acceptable)
121      {
122         // Uses the permutation indices like an odometer to generate the permutations
123         for (int idx = mPermutationIndices.length - 1; idx >= 0; idx--)
124         {
125            Counter counter = mPermutationIndices[idx];
126            counter.increment();
127
128            if (mNumGroups != null)
129            {
130               if (counter.intValue() < mNumGroups)
131               {
132                  // All is good.
133                  break;
134               }
135            }
136            else if (counter.intValue() < mSetToPartition.size())
137            {
138               // All is good.
139               break;
140            }
141
142            if (0 == idx)
143            {
144               // End of the last wheel
145               result = false;
146               break;
147            }
148
149            // 'roll over' this wheel of the odometer
150            counter.reset();
151         }
152
153         if (result)
154         {
155            acceptable = true;
156
157            if (mNumGroups != null
158                  && !mNumGroups.equals(numOfGroupsInCurrentReading()))
159            {
160               // Skip this result and keep going
161               acceptable = false;
162            }
163
164            if (acceptable
165                  && !mFlags.contains(Flag.GROUP_ORDER_MATTERS)
166                  && inverseOfCurrentReadingHasAlreadyBeenEnumerated())
167            {
168               // Keep going if the inverse of the odometer reading has been previously seen
169               acceptable = false;
170            }
171         }
172      }
173
174      return result;
175   }
176
177   //--------------------------------------------------------------------------
178   private int numOfGroupsInCurrentReading()
179   {
180      Set<Integer> groups = new HashSet<>(mSetToPartition.size());
181
182      for (Counter counter : mPermutationIndices)
183      {
184         groups.add(counter.intValue());
185      }
186
187      return groups.size();
188   }
189
190   //--------------------------------------------------------------------------
191   private boolean inverseOfCurrentReadingHasAlreadyBeenEnumerated()
192   {
193      StringBuilder buffer = new StringBuilder();
194      StringBuilder inverseBuffer = new StringBuilder();
195
196      int maxGroupIdx = numOfGroupsInCurrentReading() - 1;
197      for (Counter counter : mPermutationIndices)
198      {
199         buffer.append(counter.toString());
200         inverseBuffer.append((maxGroupIdx - counter.intValue()) + "");
201      }
202
203      // value greater than the inverse?
204      return 1 == new BigInteger(buffer.toString()).compareTo(new BigInteger(inverseBuffer.toString()));
205   }
206}