use JUnit5 Extensions to redirect System.in and System.out and run benchmarks

added apache-commons libs
applied new code formatting rules
streams
Lothar Buchholz 4 years ago
parent 2656ded38f
commit 84dbcf81ee

@ -0,0 +1 @@
buy-and-sell-n-times

@ -4,5 +4,5 @@
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="false" project-jdk-name="11" project-jdk-type="JavaSDK" />
</project>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK" />
</project>

@ -1,5 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GitSharedSettings">
<option name="FORCE_PUSH_PROHIBITED_PATTERNS">
<list />
</option>
</component>
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>

@ -16,6 +16,9 @@ allprojects {
}
dependencies {
implementation 'org.apache.commons:commons-lang3:3.10'
implementation 'org.apache.commons:commons-collections4:4.4'
implementation 'org.apache.commons:commons-math3:3.6.1'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.6.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.0'

@ -2,56 +2,61 @@ import java.util.*;
import java.util.stream.IntStream;
class Solution {
public static void main( String[] args ) {
// read values with in.next...() methods
var scanner = new Scanner( System.in );
var sellCount = scanner.nextInt();
var prices = scanner.tokens()
.mapToInt( Integer::valueOf )
.toArray();
// Write result with System.out.println()
System.out.println( maxProfits( sellCount, prices ));
}
// code your solution here
// 0 - 1 | ... | x-sellCount - x --> 0 - x-1 | ... | x-1 - x
private static int maxProfits( int sellCount, int[] prices ) {
return IntStream.range( 0, sellCount )
.parallel()
.map( sale -> {
// recursion end condition
if ( sellCount == 1 ) {
return maxProfit( prices );
}
else {
return IntStream.range( 2, prices.length )
.parallel()
.map( index -> {
var firstRange = Arrays.copyOfRange( prices, 0, index );
final int max1 = maxProfit( firstRange );
var secondRange = Arrays.copyOfRange( prices, index - 1, prices.length );
// reduce range sizes recursively
final int max2 = maxProfits( sellCount - 1, secondRange );
return max1 + max2;
}).max().orElse( 0 );
}
})
.max().orElse( 0 );
}
private static int maxProfit( int[] prices ){
return IntStream.range( 0, prices.length )
.map( index -> {
var range = Arrays.copyOfRange( prices, Math.min( index + 1, prices.length - 1 ), prices.length );
int max = IntStream.of( range ).max().orElse( 0 );
// difference between current price and future maximum
return max - prices[index];
})
.max().orElse( 0 );
}
public static void main( String[] args ) {
// read values with in.next...() methods
var scanner = new Scanner( System.in );
var sellCount = scanner.nextInt();
var prices = scanner.tokens()
.mapToInt( Integer::valueOf )
.toArray();
// Write result with System.out.println()
System.out.println( maxProfits( sellCount, prices ));
}
// code your solution here
// 0 - 1 | ... | x-sellCount - x --> 0 - x-1 | ... | x-1 - x
private static int maxProfits( int sellCount, int[] prices ) {
return IntStream.range( 0, sellCount )
.parallel()
.map( sale -> {
// recursion end condition
if ( sellCount == 1 ) {
return maxProfit( prices );
}
else {
return IntStream.range( 2, prices.length )
.parallel()
.map( index -> {
var firstRange = Arrays.copyOfRange( prices, 0, index );
final int max1 = maxProfit( firstRange );
var secondRange = Arrays.copyOfRange( prices, index - 1,
prices.length
);
// reduce range sizes recursively
final int max2 = maxProfits( sellCount - 1, secondRange );
return max1 + max2;
})
.max().orElse( 0 );
}
})
.max().orElse( 0 );
}
private static int maxProfit( int[] prices ) {
return IntStream.range( 0, prices.length )
.map( index -> {
var range = Arrays.copyOfRange( prices, Math.min( index + 1, prices.length - 1 ),
prices.length
);
int max = IntStream.of( range ).max().orElse( 0 );
// difference between current price and future maximum
return max - prices[index];
})
.max().orElse( 0 );
}
}

@ -0,0 +1,51 @@
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import java.util.logging.Logger;
import static java.lang.System.currentTimeMillis;
/**
* Based on TimingExtension from JUnit 5 user guide, and BenchmarkExtension from Nicolai Parlog
* https://github.com/CodeFX-org/demo-junit-5/blob/master/src/main/java/org/codefx/demo/junit5/BenchmarkExtension.java
*/
public class BenchmarkExtension implements BeforeAllCallback, BeforeTestExecutionCallback, AfterTestExecutionCallback, AfterAllCallback {
private static final Namespace NAMESPACE = Namespace.create( BenchmarkExtension.class );
private static final Logger LOGGER = Logger.getLogger( BenchmarkExtension.class.getName() );
@Override
public void beforeAll( ExtensionContext context ) {
context.getStore( NAMESPACE ).put( context.getRequiredTestClass(), currentTimeMillis() );
}
@Override
public void afterAll( ExtensionContext context ) {
long launchTime = context.getStore( NAMESPACE ).get( context.getRequiredTestClass(), long.class );
long elapsedTime = currentTimeMillis() - launchTime;
report( "Test class", context, elapsedTime );
}
@Override
public void beforeTestExecution( ExtensionContext context ) {
context.getStore( NAMESPACE ).put( context.getRequiredTestMethod(), currentTimeMillis() );
}
@Override
public void afterTestExecution( ExtensionContext context ) {
long launchTime = context.getStore( NAMESPACE ).get( context.getRequiredTestMethod(), long.class );
long elapsedTime = currentTimeMillis() - launchTime;
report( "Test", context, elapsedTime );
}
private static void report( String unit, ExtensionContext context, long elapsedTime ) {
String message = String.format( "%s '%s' took %d ms.", unit, context.getDisplayName(), elapsedTime );
context.publishReportEntry( "Benchmark", message );
LOGGER.info( message );
}
}

@ -1,77 +1,23 @@
import java.io.*;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import com.github.stefanbirkner.systemlambda.SystemLambda;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvFileSource;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith( BenchmarkExtension.class )
class SolutionTest {
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", numLinesToSkip = 1 )
void main( final String sellCount, 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( (sellCount + " " + 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 );
}
@ParameterizedTest
@CsvFileSource( resources = "testdata.csv" )
void main( final String sellCount, final String input, final String expected ) throws Exception {
String output = SystemLambda.tapSystemOutNormalized( () ->
SystemLambda.withTextFromSystemIn( sellCount + " " + input )
.execute( () -> Solution.main( new String[0] ))
);
assertEquals( expected, StringUtils.chomp( output.trim() ));
}
}

@ -0,0 +1,15 @@
package com.github.stefanbirkner.systemlambda;
/**
* Code that should be executed by on of the methods of {@link SystemLambda}.
* This code may throw an {@link Exception}. Therefore we cannot use
* {@link Runnable}.
*/
public interface Statement {
/**
* Execute the statement.
*
* @throws Exception the statement may throw an arbitrary exception.
*/
void execute() throws Exception;
}

@ -1,4 +1,3 @@
sellCount, prices, maxProfit
2, 12 11 13 9 12 8 14 13 15, 10
3, 12 11 13 9 12 8 14 13 15, 12
2, 310 315 275 295 260 270 290 230 255 250, 55

1 sellCount 2 prices 12 11 13 9 12 8 14 13 15 maxProfit 10
sellCount prices maxProfit
1 2 2 12 11 13 9 12 8 14 13 15 12 11 13 9 12 8 14 13 15 10 10
2 3 3 12 11 13 9 12 8 14 13 15 12 11 13 9 12 8 14 13 15 12 12
3 2 2 310 315 275 295 260 270 290 230 255 250 310 315 275 295 260 270 290 230 255 250 55 55
Loading…
Cancel
Save