Skip to content

Instantly share code, notes, and snippets.

@muescha
Forked from JosePaumard/Countdown.java
Last active February 19, 2024 02:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save muescha/a7cd2ddd8082e520d545206cfb8650f7 to your computer and use it in GitHub Desktop.
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
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");
});
}
}
@muescha
Copy link
Author

muescha commented Feb 19, 2024

changed more fancy output, print also the result of each operation

@muescha
Copy link
Author

muescha commented Feb 19, 2024

Bildschirmfoto 2024-02-19 um 03 13 14

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment