/*************************************************************************** * Copyright (C) 2012 by H-Store Project * * Brown University * * Massachusetts Institute of Technology * * Yale University * * * * http://hstore.cs.brown.edu/ * * * * Permission is hereby granted, free of charge, to any person obtaining * * a copy of this software and associated documentation files (the * * "Software"), to deal in the Software without restriction, including * * without limitation the rights to use, copy, modify, merge, publish, * * distribute, sublicense, and/or sell copies of the Software, and to * * permit persons to whom the Software is furnished to do so, subject to * * the following conditions: * * * * The above copyright notice and this permission notice shall be * * included in all copies or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * * OTHER DEALINGS IN THE SOFTWARE. * ***************************************************************************/ package edu.brown.utils; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Vector; import org.apache.commons.collections15.map.ListOrderedMap; import org.apache.commons.collections15.set.ListOrderedSet; import org.apache.log4j.Logger; /** * @author pavlo * @param <E> */ public class UniqueCombinationIterator<E> implements Iterator<Set<E>> { private static final Logger LOG = Logger.getLogger(UniqueCombinationIterator.class); private final List<E> data; private final int combo_size; private final int num_elements; private final Vector<Integer> last; private ListOrderedSet<E> next = null; private Boolean finished = null; private int attempt_ctr = 0; public UniqueCombinationIterator(Collection<E> data, int size) { this.data = (data instanceof List ? (List<E>) data : new ArrayList<E>(data)); this.combo_size = size; this.num_elements = this.data.size(); // Initialize last counters this.last = new Vector<Integer>(); this.last.setSize(this.combo_size); this.initializeLast(0); } private void initializeLast(int start) { for (int i = 0; i < this.combo_size; i++) { this.last.set(i, start + i); } } @Override public boolean hasNext() { if (this.finished == null) { try { this.getNext(); } catch (RuntimeException ex) { LOG.info(this); throw ex; } } assert (this.finished != null); return (this.finished == false); } @Override public Set<E> next() { boolean valid = this.hasNext(); Set<E> ret = (this.finished == false ? this.next : null); this.next = null; // If we're suppose to have more, then queue it up! if (valid) { this.getNext(); } return (ret); } /** * Find the next unique combination */ private void getNext() { assert (this.next == null); final boolean trace = LOG.isTraceEnabled(); final boolean debug = LOG.isDebugEnabled(); if (debug) LOG.debug("Finding next combination [call=" + (this.attempt_ctr++) + "]"); boolean valid = false; Vector<Integer> buffer = null; for (int i = this.last.get(0); i < this.num_elements; i++) { if (trace) LOG.trace("\n" + this); buffer = new Vector<Integer>(); buffer.setSize(this.combo_size); buffer.set(0, i); // We have a new combination! if (this.calculateCombinations(buffer, 1)) { if (trace) LOG.trace("Found new combination: " + buffer); valid = true; break; } if (trace) LOG.trace("No combination found that starts with index #" + i); buffer = null; this.initializeLast(i + 1); } // FOR if (trace) LOG.trace("VALID = " + valid); if (valid) { assert (this.combo_size == buffer.size()); this.next = new ListOrderedSet<E>(); for (int i = 0; i < this.combo_size; i++) { this.next.add(this.data.get(buffer.get(i))); } // FOR if (trace) LOG.trace("NEXT = " + this.next); // Increase the last position's counter so that it is different next // time this.last.set(this.combo_size - 1, this.last.lastElement() + 1); if (trace) LOG.trace("NEW LAST = " + this.last); this.finished = false; } else { this.finished = true; } } private boolean calculateCombinations(List<Integer> buffer, int position) { // If we're at the last position, then just return true if (position == this.combo_size) return (true); // Get the last element counter for this position int last_value = this.last.get(position); // Now if we go past the total number of elements, then we need to // return false // Make sure that we reset ourselves to be one more than our preceding // position for (int i = last_value; i < this.num_elements; i++) { this.last.set(position, i); buffer.set(position, i); if (this.calculateCombinations(buffer, position + 1)) { return (true); } } // FOR // Note that we have to increase ourselves by two, because we know // that the preceding position // is going to get increased by one, so we want to make sure we're one // more than that // But everyone else should just be one more than the preceeding int base_value = this.last.get(position - 1) + 1; for (int i = position; i < this.combo_size; i++) { this.last.set(position, base_value + (i == position ? 1 : 0)); } return (false); } @Override public void remove() { assert (false); } /** * Helper method that returns this iterator wrapped in an Iterable * * @param <E> * @param data * @param combo_size * @return */ public static <E> Iterable<Set<E>> factory(Collection<E> data, int combo_size) { return (CollectionUtil.iterable(new UniqueCombinationIterator<E>(data, combo_size))); } @Override public String toString() { Map<String, Object> m = new ListOrderedMap<String, Object>(); m.put("DATA", this.data); m.put("COMBO_SIZE", this.combo_size); m.put("NUM_ELEMENTS", this.num_elements); m.put("FINISHED", this.finished); m.put("LAST", this.last); m.put("NEXT", this.next); return StringBoxUtil.box(StringUtil.formatMaps("=", m)); } }