reactivestreams version, still flaky
This commit is contained in:
		
							
								
								
									
										16
									
								
								src/main/java/reactivestreams/ColumnChecker.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/main/java/reactivestreams/ColumnChecker.java
									
									
									
									
									
										Normal file
									
								
							| @@ -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; | ||||
| 	} | ||||
|  | ||||
| } | ||||
							
								
								
									
										58
									
								
								src/main/java/reactivestreams/DigitBlockChecker.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/main/java/reactivestreams/DigitBlockChecker.java
									
									
									
									
									
										Normal file
									
								
							| @@ -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(); | ||||
| 	} | ||||
|  | ||||
| } | ||||
							
								
								
									
										16
									
								
								src/main/java/reactivestreams/RowChecker.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/main/java/reactivestreams/RowChecker.java
									
									
									
									
									
										Normal file
									
								
							| @@ -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; | ||||
| 	} | ||||
|  | ||||
| } | ||||
							
								
								
									
										24
									
								
								src/main/java/reactivestreams/Solution.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/main/java/reactivestreams/Solution.java
									
									
									
									
									
										Normal file
									
								
							| @@ -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()); | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										30
									
								
								src/main/java/reactivestreams/SquareChecker.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/main/java/reactivestreams/SquareChecker.java
									
									
									
									
									
										Normal file
									
								
							| @@ -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; | ||||
| 	} | ||||
|  | ||||
| } | ||||
							
								
								
									
										80
									
								
								src/main/java/reactivestreams/SudokuVerifier.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/main/java/reactivestreams/SudokuVerifier.java
									
									
									
									
									
										Normal file
									
								
							| @@ -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(); | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										92
									
								
								src/test/java/reactivestreams/SolutionTest.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								src/test/java/reactivestreams/SolutionTest.java
									
									
									
									
									
										Normal file
									
								
							| @@ -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 ); | ||||
| 	} | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 paul
					paul