├── .gitignore ├── README.md ├── pom.xml └── src ├── main └── java │ └── com │ └── balarawool │ └── cryptarithmetic │ ├── Expression.java │ ├── Generator.java │ ├── Puzzle.java │ ├── PuzzleConstraint.java │ └── Variable.java └── test └── java └── com └── balarawool └── cryptarithmetic └── PuzzleTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/ 8 | .idea/modules.xml 9 | .idea/jarRepositories.xml 10 | .idea/compiler.xml 11 | .idea/libraries/ 12 | *.iws 13 | *.iml 14 | *.ipr 15 | 16 | ### Eclipse ### 17 | .apt_generated 18 | .classpath 19 | .factorypath 20 | .project 21 | .settings 22 | .springBeans 23 | .sts4-cache 24 | 25 | ### NetBeans ### 26 | /nbproject/private/ 27 | /nbbuild/ 28 | /dist/ 29 | /nbdist/ 30 | /.nb-gradle/ 31 | build/ 32 | !**/src/main/**/build/ 33 | !**/src/test/**/build/ 34 | 35 | ### VS Code ### 36 | .vscode/ 37 | 38 | ### Mac OS ### 39 | .DS_Store 40 | **/.DS_Store 41 | 42 | /target/ 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Example for Data Oriented Programming in Java 2 | 3 | This repo contain source code for my talk "Algebraic Data Types + Pattern Matching = Elegant and readable Java code" 4 | 5 | This program solves a [cryptarithmetic puzzle](https://en.wikipedia.org/wiki/Verbal_arithmetic): SEND + MORE = MONEY 6 | 7 | The talk shows how you can write readable, understandable code with Data Oriented Programming in Java using records, sealed types and pattern matching features. 8 | If you have questions, suggestions to improve or any other feedback, please reach me out at [@BalaRawool](https://twitter.com/BalaRawool) 9 | 10 | ## Links to articles on Data Oriented Programming 11 | 12 | [InfoQ Article from Brian Goetz](https://www.infoq.com/articles/data-oriented-programming-java/) 13 | 14 | [Inside Java Article from Nicolai Parlog](https://inside.java/2024/05/23/dop-v1-1-introduction/) 15 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.example 8 | cryptarithmetic 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 23 13 | 23 14 | UTF-8 15 | 16 | 17 | 18 | 19 | org.apache.maven.plugins 20 | maven-compiler-plugin 21 | 22 | 23 23 | 23 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | junit 34 | junit 35 | 4.13.2 36 | test 37 | 38 | 39 | org.junit.jupiter 40 | junit-jupiter-api 41 | 5.8.2 42 | test 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/main/java/com/balarawool/cryptarithmetic/Expression.java: -------------------------------------------------------------------------------- 1 | package com.balarawool.cryptarithmetic; 2 | 3 | import java.util.Map; 4 | 5 | sealed interface Expression { 6 | 7 | record ConstExpr(int value) implements Expression { } 8 | record VarExpr(String name) implements Expression { } 9 | record AdditionExpr(Expression left, Expression right) implements Expression { } 10 | record MultiplicationExpr(Expression left, Expression right) implements Expression { } 11 | 12 | static int evaluate(Expression expr, Map values) { 13 | return switch (expr) { 14 | case ConstExpr(var value) -> value; 15 | case VarExpr(var name) -> values.get(name); 16 | case AdditionExpr(var left, var right) -> evaluate(left, values) + evaluate(right, values); 17 | case MultiplicationExpr(var left, var right) -> evaluate(left, values) * evaluate(right, values); 18 | }; 19 | } 20 | 21 | static Expression constant(int value) { 22 | return new ConstExpr(value); 23 | } 24 | static Expression varExpr(String name) { 25 | return new VarExpr(name); 26 | } 27 | static Expression add(Expression left, Expression right) { 28 | return new AdditionExpr(left, right); 29 | } 30 | static Expression multiply(Expression left, Expression right) { 31 | return new MultiplicationExpr(left, right); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/balarawool/cryptarithmetic/Generator.java: -------------------------------------------------------------------------------- 1 | package com.balarawool.cryptarithmetic; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.HashSet; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.Set; 9 | import java.util.stream.Collectors; 10 | import java.util.stream.IntStream; 11 | 12 | record Generator(List variables, boolean uniqueValues) { 13 | 14 | List> combinations() { 15 | var values = new int[variables.size()]; 16 | return dfs(0, values, new ArrayList<>()); 17 | } 18 | 19 | private List> dfs(int i, int[] values, List> accumulator) { 20 | if (i < values.length) { //non-leaf nodes 21 | var nextValues = getNextValuesFor(i, values); 22 | for (var newValues : nextValues) { 23 | if (!uniqueValues || allValuesUnique(newValues, i + 1)) { 24 | dfs(i + 1, newValues, accumulator); 25 | } 26 | } 27 | } 28 | if (i == values.length) { // leaf nodes 29 | if (!uniqueValues || allValuesUnique(values, i)) { 30 | accumulator.add(createVarValuesMap(values)); 31 | } 32 | } 33 | return accumulator; 34 | } 35 | 36 | private Map createVarValuesMap(int[] values) { 37 | return Arrays.stream(intArray(values.length)) 38 | .collect(Collectors.toMap(j -> variables.get(j).name(), j -> values[j])); 39 | } 40 | 41 | private Integer[] intArray(int length) { 42 | return IntStream.range(0, length) 43 | .boxed() 44 | .toArray(Integer[]::new); 45 | } 46 | 47 | private ArrayList getNextValuesFor(int i, int[] values) { 48 | var result = new ArrayList(); 49 | var range = variables.get(i).range(); 50 | for (int j = range.from(); j <= range.to(); j++) { 51 | var arr = values.clone(); 52 | arr[i] = j; 53 | result.add(arr); 54 | } 55 | return result; 56 | } 57 | 58 | private boolean allValuesUnique(int[] values, int length) { 59 | Set set = new HashSet<>(); 60 | for (int i = 0; i < length; i++) { 61 | set.add(values[i]); 62 | } 63 | return set.size() == length; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/balarawool/cryptarithmetic/Puzzle.java: -------------------------------------------------------------------------------- 1 | package com.balarawool.cryptarithmetic; 2 | 3 | import com.balarawool.cryptarithmetic.PuzzleConstraint.EqualityConstraint; 4 | import com.balarawool.cryptarithmetic.PuzzleConstraint.UniqueValuesConstraint; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | record Puzzle(List variables, List constraints) { 10 | 11 | public Map solve() { 12 | var uniqueValues = constraints.stream().anyMatch(constraint -> constraint instanceof UniqueValuesConstraint); 13 | var generator = new Generator(variables, uniqueValues); 14 | for (var values: generator.combinations()) { 15 | if (isSolution(values, uniqueValues)) { 16 | return values; 17 | } 18 | } 19 | throw new IllegalStateException("No solution found!"); 20 | } 21 | 22 | private boolean isSolution(Map values, boolean uniqueValues) { 23 | return constraints.stream().allMatch(constraint -> isSatisfied(constraint, values, uniqueValues)); 24 | } 25 | 26 | private boolean isSatisfied(PuzzleConstraint constraint, Map values, boolean uniqueValues) { 27 | return switch (constraint) { 28 | case UniqueValuesConstraint _ -> uniqueValues; 29 | case EqualityConstraint equalityConstraint -> equalityConstraint.holdsTrueFor(values); 30 | }; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/balarawool/cryptarithmetic/PuzzleConstraint.java: -------------------------------------------------------------------------------- 1 | package com.balarawool.cryptarithmetic; 2 | 3 | import java.util.Map; 4 | 5 | import static com.balarawool.cryptarithmetic.Expression.evaluate; 6 | 7 | sealed interface PuzzleConstraint { 8 | record UniqueValuesConstraint() implements PuzzleConstraint { } 9 | record EqualityConstraint(Expression left, Expression right) implements PuzzleConstraint { 10 | boolean holdsTrueFor(Map values) { 11 | return evaluate(left, values) == evaluate(right, values); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/balarawool/cryptarithmetic/Variable.java: -------------------------------------------------------------------------------- 1 | package com.balarawool.cryptarithmetic; 2 | 3 | record Variable(String name, RangeConstraint range) { 4 | record RangeConstraint(int from, int to) { } 5 | } 6 | -------------------------------------------------------------------------------- /src/test/java/com/balarawool/cryptarithmetic/PuzzleTest.java: -------------------------------------------------------------------------------- 1 | package com.balarawool.cryptarithmetic; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import static com.balarawool.cryptarithmetic.Expression.add; 9 | import static com.balarawool.cryptarithmetic.Expression.constant; 10 | import static com.balarawool.cryptarithmetic.Expression.evaluate; 11 | import static com.balarawool.cryptarithmetic.Expression.multiply; 12 | import static com.balarawool.cryptarithmetic.Expression.varExpr; 13 | import static org.junit.jupiter.api.Assertions.*; 14 | 15 | class PuzzleTest { 16 | 17 | @Test 18 | public void testSolve() { 19 | var s = new Variable("S", new Variable.RangeConstraint(0, 9)); 20 | var e = new Variable("E", new Variable.RangeConstraint(0, 9)); 21 | var n = new Variable("N", new Variable.RangeConstraint(0, 9)); 22 | var d = new Variable("D", new Variable.RangeConstraint(0, 9)); 23 | var m = new Variable("M", new Variable.RangeConstraint(1, 9)); 24 | var o = new Variable("O", new Variable.RangeConstraint(0, 9)); 25 | var r = new Variable("R", new Variable.RangeConstraint(0, 9)); 26 | var y = new Variable("Y", new Variable.RangeConstraint(0, 9)); 27 | 28 | var solution = new Puzzle(List.of(s,e,n,d,m,o,r,y), 29 | List.of( 30 | new PuzzleConstraint.UniqueValuesConstraint(), 31 | new PuzzleConstraint.EqualityConstraint(add(createNumber(s,e,n,d), createNumber(m,o,r,e)), createNumber(m,o,n,e,y)) 32 | )) 33 | .solve(); 34 | assertEquals(evaluate(add(createNumber(s,e,n,d), createNumber(m,o,r,e)), solution), evaluate(createNumber(m,o,n,e,y), solution)); 35 | prettyPrint(solution); 36 | } 37 | 38 | private Expression createNumber(Variable... vars) { 39 | var expr = constant(0); 40 | for (var variables: vars) { 41 | expr = add(multiply(expr, constant(10)), varExpr(variables.name())); 42 | } 43 | return expr; 44 | } 45 | 46 | private void prettyPrint(Map values) { 47 | var equation = """ 48 | SEND 49 | + MORE 50 | ------ 51 | MONEY 52 | """; 53 | var solution = ""+ 54 | " "+values.get("S")+values.get("E")+values.get("N")+values.get("D")+"\n"+ 55 | "+ "+values.get("M")+values.get("O")+values.get("R")+values.get("E")+"\n"+ 56 | "------"+"\n"+ 57 | " "+values.get("M")+values.get("O")+values.get("N")+values.get("E")+values.get("Y"); 58 | System.out.println(equation); 59 | System.out.println(solution); 60 | } 61 | 62 | } --------------------------------------------------------------------------------