-
-
Save muescha/a7cd2ddd8082e520d545206cfb8650f7 to your computer and use it in GitHub Desktop.
This is the Countdown class used for the episode 22 of the JEP Café series
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.time.Duration; | |
import java.time.Instant; | |
import java.util.*; | |
import java.util.function.Function; | |
import java.util.stream.Collectors; | |
import java.util.stream.Gatherer; | |
import java.util.stream.IntStream; | |
import java.util.stream.Stream; | |
/** | |
* This is the code used for the episode 22 of the JEP Café series, on Data Oriented Programming. | |
* You can watch this episode here: https://youtu.be/Y2pmZlP-cOU | |
* The full JEP Café series is here: https://www.youtube.com/playlist?list=PLX8CzqL3ArzV4BpOzLanxd4bZr46x5e87 | |
*/ | |
public record Countdown(List<Integer> ints, int target) { | |
public void printInfo(){ | |
System.out.println("\n\n"); | |
System.out.println(STR."Numbers: \{ints.stream().map(integer -> integer.toString()).collect(Collectors.joining(", "))}"); | |
System.out.println(STR."Target: \{target}"); | |
} | |
public void printSolutions(){ | |
Collection<String> solutions = solve(); | |
printSolutions(solutions); | |
} | |
private static void printSolutions(Collection<String> solutions) { | |
System.out.println(STR."Solutions: \{solutions.size()}"); | |
solutions.forEach(System.out::println); | |
} | |
public enum Operator { | |
ADD, SUB, MULT, DIV | |
} | |
public Countdown { | |
Elements.allElements = HashSet.newHashSet(600_000); | |
} | |
private static final | |
Function<Element, Gatherer<Element, ?, Element>> insertPreservingOrder = | |
element -> Gatherer.ofSequential( | |
() -> new Object() { | |
Element previous; | |
{ | |
this.previous = element; | |
} | |
}, | |
(state, e, downstream) -> { | |
if (e.compareTo(state.previous) < 0) { | |
return downstream.push(e); | |
} else { | |
var previous = state.previous; | |
state.previous = e; | |
return downstream.push(previous); | |
} | |
}, | |
(state, downstream) -> { | |
downstream.push(state.previous); | |
}); | |
sealed interface Element | |
extends Comparable<Element> | |
permits Number, Operation { | |
default int compareTo(Element other) { | |
return compareElement(this, other); | |
} | |
} | |
record Target(int value) { | |
public boolean matches(Element element) { | |
return switch (element) { | |
case Number(int n) -> n == value; | |
case Add(_, _, int result) -> result == value; | |
case Sub(_, _, int result) -> result == value; | |
case Mult(_, _, int result) -> result == value; | |
case Div(_, _, int result) -> result == value; | |
}; | |
} | |
public boolean doesntMatch(Element element) { | |
return !matches(element); | |
} | |
} | |
sealed interface Operation extends Element permits Add, Sub, Mult, Div { | |
static Operation of(Operator operator, Element left, Element right) { | |
return switch (operator) { | |
case ADD -> new Add(left, right); | |
case SUB -> new Sub(left, right); | |
case MULT -> new Mult(left, right); | |
case DIV -> new Div(left, right); | |
}; | |
} | |
} | |
record Add(Element e1, Element e2, int result) implements Operation { | |
public Add(Element e1, Element e2) { | |
this(e1, e2, resolve(e1) + resolve(e2)); | |
} | |
} | |
record Sub(Element e1, Element e2, int result) implements Operation { | |
public Sub(Element e1, Element e2) { | |
this(e1, e2, resolve(e1) - resolve(e2)); | |
} | |
} | |
record Mult(Element e1, Element e2, int result) implements Operation { | |
public Mult(Element e1, Element e2) { | |
this(e1, e2, resolve(e1) * resolve(e2)); | |
} | |
} | |
record Div(Element e1, Element e2, int result) implements Operation { | |
public Div(Element e1, Element e2) { | |
this(e1, e2, resolve(e1) / resolve(e2)); | |
} | |
} | |
record Number(int value) implements Element { | |
} | |
record Elements(List<Element> elements) implements Iterable<Element> { | |
private static Set<Elements> allElements; | |
public Iterator<Element> iterator() { | |
return elements.iterator(); | |
} | |
public int size() { | |
return this.elements.size(); | |
} | |
public Stream<Element> stream() { | |
return elements.stream(); | |
} | |
public List<Elements> mergeElements(int leftIndex, int rightIndex) { | |
var left = this.get(leftIndex); | |
var right = this.get(rightIndex); | |
Elements elements = remove(rightIndex).remove(leftIndex); | |
return Arrays.stream(Operator.values()) | |
.<Element>mapMulti((op, downstream) -> { | |
switch (op) { | |
case ADD -> downstream.accept(Operation.of(op, left, right)); | |
case SUB -> { | |
if (resolve(left) > resolve(right)) { | |
downstream.accept(Operation.of(op, left, right)); | |
} | |
} | |
case MULT -> { | |
if (resolve(left) > 1 && resolve(right) > 1) { | |
downstream.accept(Operation.of(op, left, right)); | |
} | |
} | |
case DIV -> { | |
if (resolve(right) > 1 && resolve(left) % resolve(right) == 0) { | |
downstream.accept(Operation.of(op, left, right)); | |
} | |
} | |
} | |
}) | |
.map(elements::add) | |
.filter(allElements::add) | |
.toList(); | |
} | |
private Elements add(Element element) { | |
return new Elements( | |
this.elements.stream() | |
.gather(insertPreservingOrder.apply(element)) | |
.toList()); | |
} | |
private Elements remove(int removedIndex) { | |
return new Elements(IntStream.range(0, elements.size()) | |
.filter(index -> index != removedIndex) | |
.mapToObj(elements::get) | |
.toList()); | |
} | |
private Element get(int index) { | |
return this.elements.get(index); | |
} | |
} | |
record Solution(Stream<Element> elements) { | |
public Solution(Stream<Element> solutions, Stream<Element> moreSolutions) { | |
this(Stream.of(solutions, moreSolutions).flatMap(Function.identity())); | |
} | |
} | |
record CountdownSolver(Elements elements, Target target) { | |
public Solution solve() { | |
var solutions = | |
elements.stream().filter(target::matches).toList(); | |
var nextElements = | |
elements.stream().filter(target::doesntMatch).toList(); | |
if (nextElements.isEmpty()) { | |
return new Solution(solutions.stream()); | |
} else { | |
var moreSolutions = | |
reduce(nextElements).stream() | |
.map(elements -> new CountdownSolver(elements, target)) | |
.map(CountdownSolver::solve) | |
.flatMap(Solution::elements) | |
.toList(); | |
return new Solution(solutions.stream(), moreSolutions.stream()); | |
} | |
} | |
} | |
private static int compareElement(Element e1, Element e2) { | |
return Integer.compare(resolve(e2), resolve(e1)); | |
} | |
private static int resolve(Element element) { | |
return switch (element) { | |
case Number number -> number.value(); | |
case Add(_, _, int result) -> result; | |
case Mult(_, _, int result) -> result; | |
case Sub(_, _, int result) -> result; | |
case Div(_, _, int result) -> result; | |
}; | |
} | |
private static List<Elements> reduce(List<Element> elements) { | |
return reduce(new Elements(elements)); | |
} | |
private static List<Elements> reduce(Elements elements) { | |
return IntStream.range(0, elements.size()) | |
.boxed() | |
.flatMap(leftIndex -> | |
IntStream.range(leftIndex + 1, elements.size()) | |
.boxed() | |
.flatMap(rightIndex -> elements.mergeElements(leftIndex, rightIndex).stream()) | |
) | |
.toList(); | |
} | |
static String formatResult(int result){ | |
final String ANSI_RESET = "\u001B[0m"; | |
final String ANSI_BLACK = "\u001B[30m"; | |
final String ANSI_YELLOW = "\u001B[33m"; | |
final String ANSI_WHITE_BACKGROUND = "\u001B[47m"; | |
return STR."\{ANSI_BLACK}\{ANSI_WHITE_BACKGROUND}[\{result}]\{ANSI_RESET}"; | |
} | |
private static String composeElement(Element element) { | |
return switch (element) { | |
case Number(int value) -> Integer.toString(value); | |
case Add(Element left, Element right, int result) -> STR."(\{composeElement(left)} + \{composeElement(right)})\{formatResult(result)}"; | |
case Mult(Element left, Element right, int result) -> STR."(\{composeElement(left)} * \{composeElement(right)})\{formatResult(result)}"; | |
case Sub(Element left, Element right, int result) -> STR."(\{composeElement(left)} - \{composeElement(right)})\{formatResult(result)}"; | |
case Div(Element left, Element right, int result) -> STR."(\{composeElement(left)} / \{composeElement(right)})\{formatResult(result)}"; | |
}; | |
} | |
private static Collection<String> composeResult(Solution solutions) { | |
return solutions.elements() | |
.map(Countdown::composeElement) | |
.toList(); | |
} | |
public Collection<String> solve() { | |
var intsAsElements = ints.stream() | |
.map(Number::new) | |
.map(Element.class::cast) | |
.sorted() | |
.toList(); | |
var elements = new Elements(intsAsElements); | |
var target = new Target(this.target); | |
var solver = new CountdownSolver(elements, target); | |
var solutions = solver.solve(); | |
Collection<String> composedSolutions = composeResult(solutions); | |
return composedSolutions; | |
} | |
public static void main(String... args) { | |
var problems = List.of( | |
new Countdown(List.of(3, 1, 7, 8, 1, 4), 246), | |
new Countdown(List.of(1, 3, 5, 10, 25, 50), 999), | |
// https://youtu.be/sKdM82SELsU | |
new Countdown(List.of(25, 100, 75, 50, 6, 4), 821), | |
// https://youtu.be/mRLW_iZVmHU | |
new Countdown(List.of(75, 25, 50, 100, 8, 2), 431), | |
// https://youtu.be/_JQYYz92-Uk | |
new Countdown(List.of(25, 50, 75, 100, 1, 10), 813) | |
); | |
problems.forEach(countdown -> { | |
var start = Instant.now(); | |
countdown.printInfo(); | |
countdown.printSolutions(); | |
var end = Instant.now(); | |
System.out.println(STR."Time taken: \{Duration.between(start, end).toMillis()}ms"); | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
changed more fancy output, print also the result of each operation