Skip to content

Instantly share code, notes, and snippets.

@cogmission
Created July 31, 2016 19:28
Show Gist options
  • Save cogmission/1bfa2f82889b2d5bd615e72c15b4584d to your computer and use it in GitHub Desktop.
Save cogmission/1bfa2f82889b2d5bd615e72c15b4584d to your computer and use it in GitHub Desktop.
Java Version of Python Generator
/* ---------------------------------------------------------------------
* Numenta Platform for Intelligent Computing (NuPIC)
* Copyright (C) 2016, Numenta, Inc. Unless you have an agreement
* with Numenta, Inc., for a separate license for this software code, the
* following terms and conditions apply:
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses.
*
* http://numenta.org/licenses/
* ---------------------------------------------------------------------
*/
package org.numenta.nupic.util;
import java.util.Iterator;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.LinkedBlockingQueue;
/**
* Java version of a Python Generator. This generator has no "send"
* method but does have yield. Values are consumed by either a forEach
* loop or by calling {@link #next()} directly.
* <p>
* Typical Usage:
* </p>
* <p>
* Define a class which overrides {@code AbstractGenerator} as such:
* </p>
* <b>For terminable generator...</b>
* <pre>
* class MyGenerator extends AbstractGenerator<Integer> {
* public void exec() {
* int i = 0;
* while(i < 50) {
* // Do some work then call yield
*
* yield(new Integer(i++)); // Execution "pauses" here after yield() is executed...
*
* // Do some more work... // Execution continues here after call to next()...
* }
* }
*
* public boolean isConsumed() {
* return false; // "false" makes this an "infinite" generator.
* }
* }
*
* MyGenerator generator = new MyGenerator();
* while(generator.hasNext()) {
* System.out.println(generator.next());
* }
*
* // Using foreach...
* MyGenerator generator2 = new MyGenerator();
* for(Integer result : generator2) {
* System.out.println(result);
* }
* </pre>
*
* <hr><br>
*
* <b>For infinite generator...</b>
* <pre>
* class MyGenerator extends AbstractGenerator<Integer> {
* public void exec() {
* int i = 0;
* while(true) {
* if(isHalted()) { // Sane implementation must do this first...
break;
}
* // Do some work then call yield
*
* yield(new Integer(i++)); // Execution "pauses" here after yield() is executed...
*
* // Do some more work... // Execution continues here after call to next()...
* }
* }
*
* public boolean isConsumed() {
* return false; // "false" makes this an "infinite" generator.
* }
* }
*
* MyGenerator generator = new MyGenerator();
* while(generator.hasNext()) {
* System.out.println(generator.next());
* }
*
* // Using foreach...
* MyGenerator generator2 = new MyGenerator();
* for(Integer result : generator2) {
* System.out.println(result);
* }
* </pre>
*
* @author cogmission
*
* @param <T> the return type of the generator
*/
public abstract class AbstractGenerator<T> implements Generator<T> {
/** Default serial id*/
protected static final long serialVersionUID = 1L;
/** The object of type &lt;T&gt; returned from {@link #yield(Object)}*/
private T returnVal;
/** The {@link Runnable} code body containing the call to {@link #exec} */
private Runnable body;
/** Signals the {@link #yield} point of execution halting */
private CyclicBarrier barrier = new CyclicBarrier(2);
/** Set to true during generator initialization */
private volatile boolean running;
/** Transfers the execution result from the inner thread to the caller */
private LinkedBlockingQueue<T> queue = new LinkedBlockingQueue<>();
/** The inner execution thread */
private Thread mainThread;
/**
* Constructs a new {@code AbstractGenerator}
*/
public AbstractGenerator() {
body = () -> {
while(running) {
try {
exec();
}catch(Exception e) {
//e.printStackTrace();
System.out.println("Was interrupted");
}
}
};
}
/**
* Override this method in a subclass to execute the
* body of code which will do the processing during
* every iteration. This method contains the call to
* {@link #yield(Object)} which stores the processed
* state for retrieval on the next call to {@link #next()}.
*/
public abstract void exec();
/**
* Called during the execution of the {@link #exec()}
* method to signal the availability of the result from
* one iteration of processing.
*
* @param t the object of type &lt;T&gt; to return
*/
@Override
public void yield(T t) {
returnVal = t;
try {
queue.offer(t);
barrier.await();
}catch(Exception e) {
// Halted execution ends here
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean hasNext() {
return !isConsumed();
}
/**
* Halts the main thread
*/
@Override
public void halt() {
running = false;
mainThread.interrupt();
}
/**
* Used by the main {@link #exec} loop to query if
* the execution is to be stopped.
* @return true if halt requested, false if not
*/
@Override
public boolean haltRequested() {
return !running;
}
/**
* {@inheritDoc}
*/
@Override
public T next() {
if(!running) {
running = true;
mainThread = new Thread(body);
mainThread.setDaemon(true);
mainThread.start();
} else {
try { barrier.await(); }catch(Exception e) { e.printStackTrace(); }
}
try { returnVal = queue.take(); }catch(Exception e) { e.printStackTrace(); }
return returnVal;
}
/**
* {@inheritDoc}
*/
@Override
public Iterator<T> iterator() {
return this;
}
}
/* ---------------------------------------------------------------------
* Numenta Platform for Intelligent Computing (NuPIC)
* Copyright (C) 2016, Numenta, Inc. Unless you have an agreement
* with Numenta, Inc., for a separate license for this software code, the
* following terms and conditions apply:
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses.
*
* http://numenta.org/licenses/
* ---------------------------------------------------------------------
*/
package org.numenta.nupic.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
public class AbstractGeneratorTest {
/////////////////////////////////
// Utility Methods //
/////////////////////////////////
/**
* Returns an {@link AbstractGenerator} that runs for 30 iterations
*
* @return an {@link AbstractGenerator} that runs for 30 iterations
*/
private Generator<Integer> getTerminableGenerator() {
@SuppressWarnings("serial")
class TerminableGenerator extends AbstractGenerator<Integer> {
int i = 0;
public void exec() {
while(i < 31) {
yield(new Integer(i++));
}
}
public boolean isConsumed() {
return i > 30;
}
}
return new TerminableGenerator();
}
/**
* Returns an {@link AbstractGenerator} that runs infinitely
* until halted.
*
* @return an {@link AbstractGenerator} that runs infinitely
* until halted.
*/
private Generator<Integer> getInfiniteGenerator() {
@SuppressWarnings("serial")
class InfiniteGenerator extends AbstractGenerator<Integer> {
int i = 0;
public void exec() {
while(true) {
if(haltRequested()) {
break;
}
yield(new Integer(i++));
}
}
public boolean isConsumed() {
return false;
}
}
return new InfiniteGenerator();
}
/////////////////////////////////
// Test Methods //
/////////////////////////////////
/**
* Test that iteration control is managed by the {@link AbstractGenerator#exec()}
* method, and that the execution can be precisely terminated.
*/
@Test
public void testTerminableGenerator() {
int i = 0;
Generator<Integer> generator = getTerminableGenerator();
for(Integer result : generator) {
assertNotEquals(result, Integer.valueOf(i - 1));
assertNotEquals(result, Integer.valueOf(i + 1));
assertEquals(result, Integer.valueOf(i));
i++;
}
assertTrue(i == 31);
assertFalse(i == 32);
}
/**
* Test that we can create an generator that can run infinitely, but
* still have its execution time managed by the methods on {@link AbstractGenerator}
*/
@Test
public void testInfiniteGenerator() {
int i = 0;
Generator<Integer> generator = getInfiniteGenerator();
for(Integer result : generator) {
if(result == 30) {
generator.halt();
break;
}
assertNotEquals(result, Integer.valueOf(i - 1));
assertNotEquals(result, Integer.valueOf(i + 1));
assertEquals(result, Integer.valueOf(i));
i++;
}
assertTrue(i == 30);
assertFalse(i == 31);
}
}
/* ---------------------------------------------------------------------
* Numenta Platform for Intelligent Computing (NuPIC)
* Copyright (C) 2016, Numenta, Inc. Unless you have an agreement
* with Numenta, Inc., for a separate license for this software code, the
* following terms and conditions apply:
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses.
*
* http://numenta.org/licenses/
* ---------------------------------------------------------------------
*/
package org.numenta.nupic.util;
import java.io.Serializable;
import java.util.Iterator;
public interface Generator<T> extends Iterable<T>, Iterator<T>, Serializable {
/**
* Called during the execution of the {@link #exec()}
* method to signal the availability of the result from
* one iteration of processing.
*
* @param t the object of type &lt;T&gt; to return
*/
default void yield(T t) {}
/**
* Halts the main thread
*/
default void halt() {}
/**
* Used by the main {@link #exec} loop to query if
* the execution is to be stopped.
* @return true if halt requested, false if not
*/
default boolean haltRequested() { return false; }
/**
* Overridden to identify when this generator has
* concluded its processing. For infinite generators
* simply return "false" here.
*
* @return a flag indicating whether the last iteration
* was the last processing cycle.
*/
boolean isConsumed();
/**
* Returns a flag indicating whether another iteration
* of processing may occur.
*
* @return true if so, false if not
*/
boolean hasNext();
/**
* Returns the object of type &lt;T&gt; which is the
* result of one iteration of processing.
*
* @return the object of type &lt;T&gt; to return
*/
T next();
/**
* {@inheritDoc}
*/
Iterator<T> iterator();
}
package org.numenta.nupic.util;
/**
* Generates a range of integers while exhibiting the specific behaviors
* of a Python generator.
*
* @author cogmission
* @see AbstractGenerator
*/
public interface IntGenerator {
/**
* Returns an {@link AbstractGenerator} capable of returning a range of integers
* specified by the lower and upper bound arguments.
*
* @param lower the lower bound <b><em>(inclusive)</em></b>
* @param upper the upper bound <b><em>(exclusive)</em></b>
* @return an {@link AbstractGenerator} capable of returning a range of integers
*/
public static Generator<Integer> of(int lower, int upper) {
/**
* Inner implementation of an {@code AbstractGenerator} for {@code Integer}s
*/
class TerminableGenerator extends AbstractGenerator<Integer> {
private static final long serialVersionUID = 1L;
int i = lower;
@Override
public void exec() {
while(i < upper) {
yield(Integer.valueOf(i++));
}
}
@Override
public boolean isConsumed() {
return i > upper - 1;
}
}
return new TerminableGenerator();
}
}
/* ---------------------------------------------------------------------
* Numenta Platform for Intelligent Computing (NuPIC)
* Copyright (C) 2016, Numenta, Inc. Unless you have an agreement
* with Numenta, Inc., for a separate license for this software code, the
* following terms and conditions apply:
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses.
*
* http://numenta.org/licenses/
* ---------------------------------------------------------------------
*/
package org.numenta.nupic.util;
import static org.junit.Assert.*;
import org.junit.Test;
public class IntGeneratorTest {
/**
* Test that iteration control is managed by the {@link AbstractGenerator#exec()}
* method, and that the execution can be precisely terminated.
*/
@Test
public void testIntegerGenerator() {
int i = 0;
Generator<Integer> generator = IntGenerator.of(0, 31);
for(Integer result : generator) {
assertNotEquals(result, Integer.valueOf(i - 1));
assertNotEquals(result, Integer.valueOf(i + 1));
assertEquals(result, Integer.valueOf(i));
i++;
}
assertTrue(i == 31);
assertFalse(i == 32);
}
/**
* Test that iteration control is managed by the {@link AbstractGenerator#exec()}
* method, and that the execution can be precisely terminated.
*/
@Test
public void testIntegerGenerator_SpecifyNext() {
int i = 28;
Generator<Integer> generator = IntGenerator.of(i, 31);
assertFalse(generator.next() == 29);
assertTrue(generator.next() == 29);
assertTrue(generator.next() == 30);
assertFalse(generator.hasNext());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment