Skip to content

Commit b6544bb

Browse files
authored
Countdown haskell java (#2)
* update readme policy * Add CountDownProblem directory * Update directory name * Change article date * Replace Contributing with Feedback
1 parent c9aa2b1 commit b6544bb

File tree

3 files changed

+339
-1
lines changed

3 files changed

+339
-1
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ Each sample directory contains its own instructions (SETUP.md) on how to run the
88

99
## Content
1010

11-
<!--- Contains a list of directories and link to published article -->
11+
* [CountDownProblem.java](countdown-problem-java21/CountDownProblem.java) published on https://inside.java/2023/11/03/countdown-haskell-java
12+
1213

1314
## Feedback
1415

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
import java.util.ArrayList;
2+
import java.util.List;
3+
import java.util.OptionalInt;
4+
import java.util.Set;
5+
import java.util.stream.Stream;
6+
7+
/*
8+
* This program is Java port of the Haskell example at
9+
* https://www.cs.nott.ac.uk/~pszgmh/pgp-countdown.hs
10+
*
11+
* The problem and the solution approaches are explained
12+
* in Prof. Graham Hutton's youtube video at
13+
* https://youtu.be/CiXDS3bBBUo?list=PLF1Z-APd9zK7usPMx3LGMZEHrECUGodd3
14+
*
15+
* This Java program requires JDK 21+
16+
*/
17+
class CountDownProblem {
18+
19+
// data Op = Add | Sub | Mul | Div
20+
enum Op {
21+
Add, Sub, Mul, Div;
22+
23+
// instance show Op
24+
@Override
25+
public String toString() {
26+
return switch (this) {
27+
case Add -> "+";
28+
case Sub -> "-";
29+
case Mul -> "*";
30+
case Div -> "/";
31+
};
32+
}
33+
}
34+
35+
// cache enum value array
36+
static final Op[] operators = Op.values();
37+
38+
// valid' :: Op -> Int -> Int -> Bool
39+
static boolean isValid(Op op, int x, int y) {
40+
return switch (op) {
41+
case Add -> x <= y;
42+
case Sub -> x > y;
43+
case Mul -> x != 1 && y != 1 && x <= y;
44+
case Div -> y != 1 && x % y == 0;
45+
};
46+
}
47+
48+
// apply :: Op -> Int -> Int -> Int
49+
static int apply(Op op, int x, int y) {
50+
return switch (op) {
51+
case Add -> x + y;
52+
case Sub -> x - y;
53+
case Mul -> x * y;
54+
case Div -> x / y;
55+
};
56+
}
57+
58+
// data Expr = Val Int | App Op Expr Expr
59+
sealed interface Expr {
60+
// brak helper for instance Show Expr
61+
static String brak(Expr expr) {
62+
return switch (expr) {
63+
// brak (Val n) = show n
64+
case Val(var n) -> Integer.toString(n);
65+
66+
// brak e = "(" ++ show e ++ ")"
67+
default -> "(" + toStr(expr) + ")";
68+
};
69+
}
70+
71+
// instance Show Expr
72+
static String toStr(Expr expr) {
73+
return switch (expr) {
74+
// show (Val n) = show n
75+
case Val(var n) -> Integer.toString(n);
76+
77+
// show (App o l r) = brak l ++ show o ++ brak r
78+
// where
79+
// brak (Val n) = show n
80+
// brak e = "(" ++ show e ++ ")"
81+
case App(var op, var l, var r) -> brak(l) + op + brak(r);
82+
};
83+
}
84+
}
85+
86+
record Val(int v) implements Expr {
87+
// instance Show Expr
88+
@Override
89+
public String toString() {
90+
return Expr.toStr(this);
91+
}
92+
}
93+
94+
record App(Op op, Expr l, Expr r) implements Expr {
95+
// instance Show Expr
96+
@Override
97+
public String toString() {
98+
return Expr.toStr(this);
99+
}
100+
}
101+
102+
// eval :: Expr -> [Int]
103+
// Using OptionalInt instead of List<Integer>
104+
static OptionalInt eval(Expr expr) {
105+
return switch (expr) {
106+
// eval (Val n) = [n | n > 0]
107+
case Val(var n) -> n > 0 ? OptionalInt.of(n) : OptionalInt.empty();
108+
109+
110+
// eval (App o l r) = [apply o x y | x <- eval l,
111+
// y <- eval r,
112+
// valid o x y]
113+
case App(var op, var l, var r) -> {
114+
var x = eval(l);
115+
var y = eval(r);
116+
yield (x.isPresent() && y.isPresent() &&
117+
isValid(op, x.getAsInt(), y.getAsInt())) ?
118+
OptionalInt.of(apply(op, x.getAsInt(), y.getAsInt())) :
119+
OptionalInt.empty();
120+
}
121+
};
122+
}
123+
124+
// type Result = (Expr,Int)
125+
record Result(Expr expr, int value) {
126+
@Override
127+
public String toString() {
128+
return expr.toString() + " = " + value;
129+
}
130+
}
131+
132+
// combine'' :: Result -> Result -> [Result]
133+
static List<Result> combine(Result lx, Result ry) {
134+
// (l,x), (r,y) pattern
135+
var l = lx.expr();
136+
var x = lx.value();
137+
var r = ry.expr();
138+
var y = ry.value();
139+
140+
// combine'' (l,x) (r,y) = [(App o l r, apply o x y) | o <- ops, valid' o x y]
141+
return Stream.of(operators).
142+
filter(op -> isValid(op, x, y)).
143+
map(op -> new Result(new App(op, l, r), apply(op, x, y))).
144+
toList();
145+
}
146+
147+
// results' :: [Int] -> [Result]
148+
static List<Result> results(List<Integer> ns) {
149+
// results' [] = []
150+
if (ns.isEmpty()) {
151+
return List.of();
152+
}
153+
154+
// results' [n] = [(Val n,n) | n > 0]
155+
if (ns.size() == 1) {
156+
var n = head(ns);
157+
return n > 0 ? List.of(new Result(new Val(n), n)) : List.of();
158+
}
159+
160+
// results' ns = [res | (ls,rs) <- split ns,
161+
// lx <- results' ls,
162+
// ry <- results' rs,
163+
// res <- combine'' lx ry]
164+
var res = new ArrayList<Result>();
165+
166+
// all possible non-empty splits of the input list
167+
// split :: [a] -> [([a],[a])] equivalent for-loop
168+
for (int i = 1; i < ns.size(); i++) {
169+
var ls = ns.subList(0, i);
170+
var rs = ns.subList(i, ns.size());
171+
var lxs = results(ls);
172+
var rys = results(rs);
173+
for (Result lx : lxs) {
174+
for (Result ry : rys) {
175+
res.addAll(combine(lx, ry));
176+
}
177+
}
178+
}
179+
return res;
180+
}
181+
182+
// List utilities
183+
// : operator
184+
static <T> List<T> cons(T head, List<T> tail) {
185+
final var tailLen = tail.size();
186+
return switch (tailLen) {
187+
case 0 -> List.of(head);
188+
case 1 -> List.of(head, tail.get(0));
189+
case 2 -> List.of(head, tail.get(0), tail.get(1));
190+
case 3 -> List.of(head, tail.get(0), tail.get(1), tail.get(2));
191+
default -> {
192+
var res = new ArrayList<T>(1 + tailLen);
193+
res.add(head);
194+
res.addAll(tail);
195+
yield res;
196+
}
197+
};
198+
}
199+
200+
static <T> T head(List<T> list) {
201+
return list.get(0);
202+
}
203+
204+
static <T> List<T> tail(List<T> list) {
205+
final var len = list.size();
206+
return len == 1 ? List.of() : list.subList(1, len);
207+
}
208+
209+
// subs :: [a] -> [[a]]
210+
static List<List<Integer>> subs(List<Integer> ns) {
211+
// subs [] = [[]]
212+
if (ns.isEmpty()) {
213+
return List.of(List.of());
214+
}
215+
216+
// subs (x:xs)
217+
var x = head(ns);
218+
var xs = tail(ns);
219+
220+
// where yss = sub(xs)
221+
var yss = subs(xs);
222+
223+
// yss ++ map (x:) yss
224+
var res = new ArrayList<List<Integer>>();
225+
res.addAll(yss);
226+
yss.stream().
227+
map(l -> cons(x, l)).
228+
forEach(res::add);
229+
230+
return res;
231+
}
232+
233+
// interleave :: a -> [a] -> [[a]]
234+
// Using Stream<List<Integer> instead of List<List<Integer>>
235+
static Stream<List<Integer>> interleave(int x, List<Integer> ns) {
236+
// interleave x [] = [[x]]
237+
if (ns.isEmpty()) {
238+
return Stream.of(List.of(x));
239+
}
240+
241+
// interleave x (y:ys)
242+
var y = head(ns);
243+
var ys = tail(ns);
244+
245+
// outer : translated as Stream.concat
246+
// (x:y:ys) : map (y:) (interleave x ys)
247+
return Stream.concat(
248+
// x:y:ys == x:ns
249+
Stream.of(cons(x, ns)),
250+
// map (y:) (interleave x ys)
251+
interleave(x, ys).map(l -> cons(y, l))
252+
);
253+
}
254+
255+
// perms :: [a] -> [[a]]
256+
// Using Stream<List<Integer> instead of List<List<Integer>>
257+
static Stream<List<Integer>> perms(List<Integer> ns) {
258+
// perms [] = [[]]
259+
if (ns.isEmpty()) {
260+
return Stream.of(List.of());
261+
}
262+
263+
// perms (x:xs)
264+
var x = head(ns);
265+
var xs = tail(ns);
266+
267+
// concat (map ...) is translated as flatMap
268+
// concat (map (interleave x) (perms xs))
269+
return perms(xs).flatMap(l -> interleave(x, l));
270+
}
271+
272+
// choices :: [a] -> [[a]]
273+
// Using Stream<List<Integer> instead of List<List<Integer>>
274+
static Stream<List<Integer>> choices(List<Integer> ns) {
275+
// concat . map is translated as flatMap
276+
// choices = concat . map perms . subs
277+
return subs(ns).stream().flatMap(CountDownProblem::perms);
278+
}
279+
280+
// solutions'' :: [Int] -> Int -> [Expr]
281+
// Using Stream<Expr> instead of List<Expr>
282+
static Stream<Expr> solutions(List<Integer> ns, int n) {
283+
// solutions'' ns n = [e | ns' <- choices ns, (e,m) <- results' ns', m == n]
284+
return choices(ns).
285+
flatMap(choice -> results(choice).stream()).
286+
filter(res -> res.value() == n).
287+
map(Result::expr);
288+
}
289+
290+
/*
291+
* usage example:
292+
*
293+
* java CountDownProblem.java 1,3,7,10,25,50 765
294+
*/
295+
public static void main(String[] args) {
296+
if (args.length != 2) {
297+
System.err.println("usage: java CountDownProblem.java <comma-separated-values> <target>");
298+
System.exit(1);
299+
}
300+
301+
int target = Integer.parseInt(args[1]);
302+
List<Integer> numbers = Stream.of(args[0].split(",")).map(Integer::parseInt).toList();
303+
// uniqueness check
304+
try {
305+
Set.of(numbers.toArray());
306+
} catch (IllegalArgumentException iae) {
307+
System.err.println(iae);
308+
System.exit(2);
309+
}
310+
311+
var start = System.currentTimeMillis();
312+
solutions(numbers, target).forEach(e -> {
313+
System.out.println(e);
314+
});
315+
System.out.println("Time taken (ms): " + (System.currentTimeMillis() - start));
316+
}
317+
}

countdown-problem-java21/SETUP.md

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Countdown Problem
2+
3+
[CountDownProblem.java](CountDownProblem.java) is the complete example used in the article https://inside.java/2023/11/03/countdown-haskell-java.
4+
The code represents the Java port of [a Haskell example](https://www.cs.nott.ac.uk/~pszgmh/pgp-countdown.hs) authored by Prof. Graham Hutton.
5+
The problem and the solution approaches of the Haskell example are explained in https://youtu.be/CiXDS3bBBUo?list=PLF1Z-APd9zK7usPMx3LGMZEHrECUGodd3.
6+
7+
## Prerequisites
8+
9+
You need JDK 21+ installed on your machine to run this sample.
10+
Find out how to achieve that by going through the [setting up a JDK section](https://dev.java/learn/getting-started/#setting-up-jdk) of https://dev.java/.
11+
12+
## Run the progam
13+
14+
Make sure you are inside the _countdown-haskell-java_ directory and then run the following command in a terminal:
15+
16+
```shell
17+
java CountDownProblem.java 1,3,7,10,25,50 765
18+
```
19+
20+
You may try different arguments to check for different results.

0 commit comments

Comments
 (0)