diff --git a/lang/pom.xml b/lang/pom.xml new file mode 100644 index 0000000..1fa88c0 --- /dev/null +++ b/lang/pom.xml @@ -0,0 +1,23 @@ + + + + java-cookbook + fr.gasser + 1.0-SNAPSHOT + + 4.0.0 + + core + + + junit + junit + 4.12 + test + + + + + \ No newline at end of file diff --git a/lang/src/main/java/patternmatching/Main.java b/lang/src/main/java/patternmatching/Main.java new file mode 100644 index 0000000..cdaa70d --- /dev/null +++ b/lang/src/main/java/patternmatching/Main.java @@ -0,0 +1,72 @@ +package patternmatching; + +import java.io.FileNotFoundException; +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.function.Consumer; + +public class Main { + + private static class CustomObject { + + private int a; + + CustomObject(int a) { + this.a = a; + } + + @Override + public String toString() { + return "CustomObject{" + + "a=" + a + + '}'; + } + } + + private static String formatObject(Object o) { + if (o instanceof Integer) return "int " + o; + if (o instanceof Byte) return "byte " + o; + if (o instanceof Double) return "double " + o; + if (o instanceof String) return "string " + o; + return String.format("Object %s", o); + } + + private static long[] runNTimes(int numberOfRuns, Object[] toTest, Consumer code) { + long runs[] = new long[numberOfRuns]; + for (int i = 0; i < numberOfRuns; i++) { + long startTime = System.nanoTime(); + Arrays.stream(toTest).forEach(code); + long endTime = System.nanoTime(); + long duration = (endTime - startTime); + runs[i] = duration; + } + + return runs; + } + + public static void main(String[] args) { + Object[] toTest = {"Hello", 1.2, new CustomObject(5)}; + + final PatternMatcher pm = PatternMatcher + .when(Integer.class::isInstance, i -> "int " + i) + .orWhen(Byte.class::isInstance, b -> "byte " + b) + .orWhen(Double.class::isInstance, d -> "double " + d) + .orWhen(String.class::isInstance, s -> "String " + s) + .otherwise(o -> "Object " + o); + + final int nRuns = 100000; + final Statistics formatObjectStats = Statistics.from(runNTimes(nRuns, toTest, Main::formatObject)); + System.out.println(String.format("Time out of %d runs for formatObject(Object o) : %s", nRuns, formatObjectStats)); + final Statistics patternMatcherStats = Statistics.from(runNTimes(nRuns, toTest, pm::matches)); + System.out.println(String.format("Time out of %d runs for patternMatcher : %s", nRuns, patternMatcherStats)); + + final PrintWriter writer; + try { + writer = new PrintWriter("/tmp/stats.csv"); + writer.println(formatObjectStats.asCSv()); + writer.println(patternMatcherStats.asCSv()); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } +} diff --git a/lang/src/main/java/patternmatching/PatternMatcher.java b/lang/src/main/java/patternmatching/PatternMatcher.java new file mode 100644 index 0000000..173739e --- /dev/null +++ b/lang/src/main/java/patternmatching/PatternMatcher.java @@ -0,0 +1,37 @@ +package patternmatching; + +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Predicate; + +@FunctionalInterface +public interface PatternMatcher { + + static PatternMatcher when(Predicate predicate, Function action) { + Objects.requireNonNull(predicate); + Objects.requireNonNull(action); + return value -> predicate.test(value) ? Optional.of(action.apply(value)) : Optional.empty(); + } + + default PatternMatcher orWhen(Predicate predicate, Function action) { + Objects.requireNonNull(predicate); + Objects.requireNonNull(action); + return value -> { + final Optional result = matches(value); + if (result.isPresent()) return result; + if (predicate.test(value)) return Optional.of(action.apply(value)); + return Optional.empty(); + }; + } + + default PatternMatcher otherwise(Function action) { + Objects.requireNonNull(action); + return value -> { + final Optional result = matches(value); + return result.isPresent() ? result : Optional.of(action.apply(value)); + }; + } + + Optional matches(T value); +} diff --git a/lang/src/main/java/patternmatching/Statistics.java b/lang/src/main/java/patternmatching/Statistics.java new file mode 100644 index 0000000..5e4187c --- /dev/null +++ b/lang/src/main/java/patternmatching/Statistics.java @@ -0,0 +1,66 @@ +package patternmatching; + +import java.io.FileNotFoundException; +import java.util.Arrays; +import java.util.stream.Collectors; + +public final class Statistics { + + private final long[] data; + private Double mean = null; + private Double variance = null; + + private Statistics(long[] data) { + this.data = data; + } + + double getMean() { + if (mean == null) { + mean = (double) Arrays.stream(data).sum() / data.length; + } + + return mean; + } + + double getVariance() { + if (variance == null) { + final double mean = getMean(); + double temp = 0; + for(double a : data) { + temp += (a-mean)*(a-mean); + } + variance = temp/(data.length-1); + } + + return variance; + } + + double getStddev() { + return Math.sqrt(getVariance()); + } + + double getMedian() { + Arrays.sort(data); + if (data.length % 2 == 0) return (data[(data.length / 2) - 1] + data[data.length / 2]) / 2.0; + return data[data.length / 2]; + } + + static Statistics from(long[] data) { + return new Statistics(data); + } + + @Override + public String toString() { + return String.format( + "mean=%.2fms stddev=%.2fms median=%.2fms", + getMean()/1000, + getStddev()/1000, + getMedian()/1000 + ); + } + + public String asCSv() { + final String csv = Arrays.stream(data).mapToObj(String::valueOf).collect(Collectors.joining(",")); + return csv; + } +} diff --git a/lang/src/test/java/patternmatching/PatternMatcherTest.java b/lang/src/test/java/patternmatching/PatternMatcherTest.java new file mode 100644 index 0000000..454b832 --- /dev/null +++ b/lang/src/test/java/patternmatching/PatternMatcherTest.java @@ -0,0 +1,61 @@ +package patternmatching; + +import org.junit.Test; + +import java.util.Optional; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.*; + +public class PatternMatcherTest { + + @Test + public void integerMatching() { + final PatternMatcher pm = PatternMatcher + .when(Number.class::isInstance, x -> "Integer: " + x); + assertEquals(pm.matches(42), Optional.of("Integer: 42")); + } + + @Test + public void stringMatching() { + final PatternMatcher pm = PatternMatcher + .when("world"::equals, x -> "Hello, " + x); + assertThat(pm.matches("world"), is(Optional.of("Hello, world"))); + assertThat(pm.matches("Bob"), is(Optional.empty())); + } + + @Test + public void emptyMatching() { + final PatternMatcher> pm = PatternMatcher + .when(Integer.class::isInstance, x -> Optional.empty()); + assertEquals(pm.matches(42), Optional.of(Optional.empty())); + } + + @Test + public void testOrWhen() { + final PatternMatcher pm = PatternMatcher + .when("world"::equals, x -> "Hello, " + x) + .orWhen(Double.class::isInstance, x -> "Double: " + x); + assertThat(pm.matches("world"), is(Optional.of("Hello, world"))); + assertThat(pm.matches(1.42), is(Optional.of("Double: 1.42"))); + } + + @Test + public void testOtherwise() { + final PatternMatcher pm = PatternMatcher.when("world"::equals, x -> "Hello, " + x) + .otherwise(x -> "got this object: " + x); + assertThat(pm.matches("world"), is(Optional.of("Hello, world"))); + assertThat(pm.matches("foo"), is(Optional.of("got this object: foo"))); + } + + @Test + public void testCombine() { + final PatternMatcher pm = PatternMatcher + .when("world"::equals, x -> "Hello, " + x) + .orWhen(Double.class::isInstance, x -> "Double: " + x) + .otherwise(x -> "got this object: " + x); + assertThat(pm.matches("world"), is(Optional.of("Hello, world"))); + assertThat(pm.matches(1.42), is(Optional.of("Double: 1.42"))); + assertThat(pm.matches("foo"), is(Optional.of("got this object: foo"))); + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 0263028..4b17a1e 100644 --- a/pom.xml +++ b/pom.xml @@ -10,6 +10,7 @@ reflection gui async + core