/** Licensed under both the GPL and the Apache 2.0 License. */ package net.slightlymagic.braids.util.generator; import java.util.ArrayList; import java.util.NoSuchElementException; import net.slightlymagic.braids.util.lambda.Lambda1; import com.google.code.jyield.Generator; import com.google.code.jyield.YieldUtils; import com.google.code.jyield.Yieldable; /** * For documentation on Java-Yield and its generators, see * {@link http://code.google.com/p/java-yield/} */ public final class GeneratorFunctions { /** * Do not instantiate. */ private GeneratorFunctions() { ; } /** * Estimate the number of items in this generator by traversing all of its * elements. * * Note this only works on a generator that can be reinstantiated once it * has been traversed. This is only an estimate, because a generator's size * may vary been traversals. This is especially true if the generator * relies on external resources, such as a file system. * * If you call this on an infinite generator, this method will never * return. * * @return the estimated number of items provided by this generator */ public static <T> long estimateSize(Generator<T> gen) { long result = 0; for (@SuppressWarnings("unused") T ignored : YieldUtils.toIterable(gen)) { result++; } return result; } /** * Highly efficient means of filtering a long or infinite sequence. * * @param <T> any type * * @param predicate a Lambda (function) whose apply method takes an object * of type <T> and returns a Boolean. If it returns false or null, the * item from the inputGenerator is not yielded by this Generator; * if predicate.apply returns true, then this Generator <i>does</i> * yield the value. * * @param inputGenerator the sequence upon which we operate * * @return a generator which produces a subset <= the inputGenerator */ public static <T> Generator<T> filterGenerator( final Lambda1<Boolean,T> predicate, final Generator<T> inputGenerator) { Generator<T> result = new Generator<T>() { @Override public void generate(final Yieldable<T> outputYield) { Yieldable<T> inputYield = new Yieldable<T>() { Boolean pResult; @Override public void yield(T input) { pResult = predicate.apply(input); if (pResult != null && pResult) { outputYield.yield(input); } } }; inputGenerator.generate(inputYield); } }; return result; } /** * Highly efficient means of applying a transform to a long or infinite * sequence. * * @param <T> any type * * @param transform a Lambda (function) whose apply method takes an object * of type <T> and returns an object of the same type. This transforms * the values from the inputGenerator into this Generator. * * @param inputGenerator the sequence upon which we operate * * @return a generator that yields transform.apply's return value for * each item in the inputGenerator */ public static <T> Generator<T> transformGenerator( final Lambda1<T,T> transform, final Generator<T> inputGenerator) { Generator<T> result = new Generator<T>() { @Override public void generate(final Yieldable<T> outputYield) { Yieldable<T> inputYield = new Yieldable<T>() { @Override public void yield(T input) { outputYield.yield(transform.apply(input)); } }; inputGenerator.generate(inputYield); } }; return result; } /** * Forces a generator to be completely evaluated into a temporary data * structure, then returns the generator over that same structure. * * This effectively returns the same Generator, but it is a faster one. * This trades away heap space for reduced CPU intensity. This is * particuarly helpful if you know that a Generator is going to be * totally evaluated more than once in the near future. * * @param <T> inferred automatically * * @param unevaluated a Generator of T instances * * @return the equivalent Generator, except that the result's generate * method can be invoked multiple times for fast results. */ public static <T> Generator<T> solidify(Generator<T> unevaluated) { ArrayList<T> solidTmp = YieldUtils.toArrayList(unevaluated); solidTmp.trimToSize(); return YieldUtils.toGenerator(solidTmp); } /** * Select an item at random from a Generator; this causes the entire * Generator to be evaluated once, but only once. * * @param generator * the generator from which to select a random item * * @return an item chosen at random from the generator; this may be null, if * the generator contains null items. * * @throws NoSuchElementException * if the generator has no contents */ public static <T> T selectRandom(Generator<T> generator) throws NoSuchElementException { /* * This algorithm requires some explanation. Each time we encounter a * new item from the generator, we determine via random chance if the * item is the one we select. At the end of each iteration, we have a * candidate, and we have a count of the number of items encountered so * far. Each iteration has a 1/n chance of replacing the candidate with * the current item, where n is the number of items encountered so far. * This allows us to randomly select an item from the generated contents * with an equal distribution; and we don't have to count the number of * items first! */ int n = 0; T candidate = null; for (T item : YieldUtils.toIterable(generator)) { n++; int rand = (int) (Math.random() * n); // At this point, 0 <= rand < n. rand++; // Now, 1 <= rand <= n. if (rand == 1) { // We rolled a 1 on an n-sided die. We have a new candidate! // Note that on the first iteration, this always happens, // because n = 1. candidate = item; } } if (n == 0) { // There were no items in the generator! throw new NoSuchElementException("generator is empty"); } return candidate; } }