reactivestreams version, still flaky
parent
dbec772d37
commit
ffe5c8d609
@ -0,0 +1,16 @@
|
|||||||
|
package reactivestreams;
|
||||||
|
|
||||||
|
public class ColumnChecker extends DigitBlockChecker {
|
||||||
|
|
||||||
|
int column;
|
||||||
|
|
||||||
|
public ColumnChecker(int column) {
|
||||||
|
this.column = column;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean currentDigitApplies(int currentRow, int currentColumn) {
|
||||||
|
return currentColumn == column;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
package reactivestreams;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.concurrent.Flow;
|
||||||
|
import java.util.concurrent.SubmissionPublisher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processor consuming a flow of digit Strings and emitting a single Boolean which is true if applying 9 digits are unique.
|
||||||
|
*/
|
||||||
|
public abstract class DigitBlockChecker extends SubmissionPublisher<Boolean> implements Flow.Processor<String, Boolean> {
|
||||||
|
|
||||||
|
int currentRow;
|
||||||
|
int currentColumn;
|
||||||
|
Collection<String> digitsProcessed;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSubscribe(Flow.Subscription subscription) {
|
||||||
|
currentRow = 1;
|
||||||
|
currentColumn = 0;
|
||||||
|
digitsProcessed = new HashSet<>();
|
||||||
|
subscription.request(9*9);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNext(String digit) {
|
||||||
|
currentColumn++;
|
||||||
|
if (currentColumn > 9) {
|
||||||
|
currentRow++;
|
||||||
|
currentColumn = 1;
|
||||||
|
}
|
||||||
|
if (currentDigitApplies(currentRow, currentColumn)) {
|
||||||
|
if (digitsProcessed.contains(digit)) {
|
||||||
|
submit(false);
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
digitsProcessed.add(digit);
|
||||||
|
if (digitsProcessed.size() == 9) {
|
||||||
|
submit(true);
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract boolean currentDigitApplies(int currentRow, int currentColumn);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Throwable throwable) {
|
||||||
|
System.err.println(throwable.fillInStackTrace());
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComplete() {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package reactivestreams;
|
||||||
|
|
||||||
|
public class RowChecker extends DigitBlockChecker {
|
||||||
|
|
||||||
|
int row;
|
||||||
|
|
||||||
|
public RowChecker(int row) {
|
||||||
|
this.row = row;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean currentDigitApplies(int currentRow, int currentColumn) {
|
||||||
|
return currentRow == row;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package reactivestreams;
|
||||||
|
|
||||||
|
import java.util.Scanner;
|
||||||
|
import java.util.Spliterator;
|
||||||
|
import java.util.concurrent.SubmissionPublisher;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* stream.Solution involving a Sukdoku stream.Board, a stream.SudokuVerifier, plus some ReactiveStreams processing.
|
||||||
|
**/
|
||||||
|
class Solution {
|
||||||
|
|
||||||
|
public static void main( String[] args ) {
|
||||||
|
// Setup stream on System.in
|
||||||
|
Scanner in = new Scanner( System.in );
|
||||||
|
Spliterator spliterator = ((Iterable) () -> in).spliterator();
|
||||||
|
Stream<String> digitStream = StreamSupport.stream(spliterator, false);
|
||||||
|
// Setup SudokuVerifier
|
||||||
|
SudokuVerifier verifier = new SudokuVerifier(digitStream);
|
||||||
|
// Verify and print out result
|
||||||
|
System.out.println(verifier.isSolved());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package reactivestreams;
|
||||||
|
|
||||||
|
public class SquareChecker extends DigitBlockChecker {
|
||||||
|
|
||||||
|
int squareRow;
|
||||||
|
int squareColumn;
|
||||||
|
|
||||||
|
public SquareChecker(int squareRow, int squareColumn) {
|
||||||
|
this.squareRow = squareRow;
|
||||||
|
this.squareColumn = squareColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean currentDigitApplies(int currentRow, int currentColumn) {
|
||||||
|
return currentRowApplies(currentRow) && currentColumnApplies(currentColumn);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean currentRowApplies(int currentRow) {
|
||||||
|
int fromRow = squareRow * 3 - 2;
|
||||||
|
int toRow = fromRow + 2;
|
||||||
|
return currentRow >= fromRow && currentRow <= toRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean currentColumnApplies(int currentColumn) {
|
||||||
|
int fromColumn = squareColumn * 3 - 2;
|
||||||
|
int toColumn = fromColumn + 2;
|
||||||
|
return currentColumn >= fromColumn && currentColumn <= toColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
package reactivestreams;
|
||||||
|
|
||||||
|
import java.util.concurrent.Flow;
|
||||||
|
import java.util.concurrent.SubmissionPublisher;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public class SudokuVerifier implements Flow.Subscriber<Boolean> {
|
||||||
|
|
||||||
|
Stream<String> digitStream;
|
||||||
|
SubmissionPublisher<String> digitPublisher;
|
||||||
|
AtomicInteger finished = new AtomicInteger(0);
|
||||||
|
AtomicBoolean solved = new AtomicBoolean(true);
|
||||||
|
|
||||||
|
public SudokuVerifier(Stream<String> digitStream) {
|
||||||
|
this.digitStream = digitStream;
|
||||||
|
// Setup publisher to emit the digitStream
|
||||||
|
digitPublisher = new SubmissionPublisher<>();
|
||||||
|
// Setup row and column checking subscribers
|
||||||
|
IntStream.rangeClosed(1, 9).boxed()
|
||||||
|
.forEach(i -> rowCheckerAndColumnChecker(digitPublisher, i));
|
||||||
|
// Setup square checking subscribers
|
||||||
|
IntStream.rangeClosed(1, 3).boxed()
|
||||||
|
.forEach(squareRow -> {
|
||||||
|
IntStream.rangeClosed(1, 3).boxed()
|
||||||
|
.forEach(squareColumn -> subscribeBlockChecker(digitPublisher, new SquareChecker(squareRow, squareColumn)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSolved() {
|
||||||
|
// Make publisher emit each digit
|
||||||
|
digitStream.forEach(digitPublisher::submit);
|
||||||
|
digitPublisher.close();
|
||||||
|
// Wait for checking subscribers to finish
|
||||||
|
while (finished.get() < digitPublisher.getSubscribers().size() && solved.get()) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(5);
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
System.out.println(ex.fillInStackTrace());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return solved.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rowCheckerAndColumnChecker(SubmissionPublisher<String> digitPublisher, Integer i) {
|
||||||
|
subscribeBlockChecker(digitPublisher, new RowChecker(i));
|
||||||
|
subscribeBlockChecker(digitPublisher, new ColumnChecker(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void subscribeBlockChecker(SubmissionPublisher<String> digitPublisher, DigitBlockChecker blockChecker) {
|
||||||
|
digitPublisher.subscribe(blockChecker);
|
||||||
|
blockChecker.subscribe(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSubscribe(Flow.Subscription subscription) {
|
||||||
|
subscription.request(1); // 1 Boolean result from the block checker
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNext(Boolean solved) {
|
||||||
|
if (! solved) {
|
||||||
|
this.solved.compareAndSet(true, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Throwable throwable) {
|
||||||
|
System.err.println(throwable.fillInStackTrace());
|
||||||
|
solved.compareAndSet(true, false);
|
||||||
|
finished.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComplete() {
|
||||||
|
finished.incrementAndGet();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
package reactivestreams;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.converter.ArgumentConversionException;
|
||||||
|
import org.junit.jupiter.params.converter.ConvertWith;
|
||||||
|
import org.junit.jupiter.params.converter.SimpleArgumentConverter;
|
||||||
|
import org.junit.jupiter.params.provider.CsvFileSource;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
class SolutionTest {
|
||||||
|
|
||||||
|
static class SudokuConverter extends SimpleArgumentConverter {
|
||||||
|
@Override
|
||||||
|
protected Object convert( Object source, Class<?> targetType ) throws ArgumentConversionException {
|
||||||
|
assertEquals(String.class, targetType, "Can only convert to String");
|
||||||
|
BufferedReader in = new BufferedReader( new InputStreamReader( SolutionTest.class.getResourceAsStream( String.valueOf( source ))));
|
||||||
|
return in.lines().collect( Collectors.joining( System.lineSeparator() ));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static long totalDuration;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void beforeAll() {
|
||||||
|
totalDuration = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void afterAll() {
|
||||||
|
System.out.println( String.format( "All solutions took %01d.%03d secs", totalDuration / 1000, totalDuration % 1000 ));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@CsvFileSource( resources = "/testdata.csv" )
|
||||||
|
void main( @ConvertWith( SudokuConverter.class ) final String input, final String expected ) throws IOException {
|
||||||
|
// keep original streams
|
||||||
|
InputStream oldIn = System.in;
|
||||||
|
PrintStream oldOut = System.out;
|
||||||
|
PrintStream oldErr = System.err;
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
try {
|
||||||
|
redirectStreams(
|
||||||
|
new ByteArrayInputStream( input.getBytes( UTF_8 )),
|
||||||
|
new PrintStream( bos, true, UTF_8 ),
|
||||||
|
new PrintStream( new ByteArrayOutputStream(), true, UTF_8 )
|
||||||
|
);
|
||||||
|
|
||||||
|
// start time tracking
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
|
||||||
|
Solution.main( new String[0] );
|
||||||
|
|
||||||
|
// stop time tracking
|
||||||
|
long duration = System.currentTimeMillis() - start;
|
||||||
|
totalDuration += duration;
|
||||||
|
|
||||||
|
// restore streams
|
||||||
|
redirectStreams( oldIn, oldOut, oldErr );
|
||||||
|
|
||||||
|
System.out.println( String.format( "Solution took %01d.%03d secs", duration / 1000, duration % 1000 ));
|
||||||
|
|
||||||
|
try (BufferedReader chk = new BufferedReader( new InputStreamReader( new ByteArrayInputStream( bos.toByteArray() ), UTF_8 ))) {
|
||||||
|
String[] expectedLines = expected.split( "\\s*[|]\\s*" );
|
||||||
|
|
||||||
|
int lineCount = 0;
|
||||||
|
String line;
|
||||||
|
for ( ; (line = chk.readLine()) != null; lineCount++ ) {
|
||||||
|
assertEquals( expectedLines[ lineCount ], line );
|
||||||
|
}
|
||||||
|
assertEquals( expectedLines.length, lineCount );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
// restore streams
|
||||||
|
redirectStreams( oldIn, oldOut, oldErr );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redirectStreams( final InputStream input, final PrintStream output, final PrintStream error ) {
|
||||||
|
System.setIn( input );
|
||||||
|
System.setOut( output );
|
||||||
|
System.setErr( error );
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue