Skip to content

Commit 427a377

Browse files
authored
Add react exercise (exercism#2266)
* Add react exercise * Tidy up react exercise - Remove unneeded generics parameter - Remove unneeded projectsEvaluated config from gradle.build - Add ComputeCell stub for student - Change stubs to use interface instead of static inner classes - Update config.json to use prerequisites and practices instead of topics * Refactor example solution * Refactor to example solution to propagate changes instead of tracking a tentative value and requiring a separate commit step. This approach is more consistent with the example solution in other tracks for this exercise. * Change Cells to use just inner classes instead of interfaces.
1 parent 7feee0c commit 427a377

9 files changed

Lines changed: 472 additions & 0 deletions

File tree

config.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1548,6 +1548,21 @@
15481548
"strings"
15491549
]
15501550
},
1551+
{
1552+
"slug": "react",
1553+
"name": "React",
1554+
"uuid": "33531718-1d8a-4abd-bd2a-090303ad0c39",
1555+
"practices": [
1556+
"classes",
1557+
"generic-types"
1558+
],
1559+
"prerequisites": [
1560+
"classes",
1561+
"generic-types"
1562+
],
1563+
"difficulty": 8,
1564+
"topics": []
1565+
},
15511566
{
15521567
"slug": "rectangles",
15531568
"name": "Rectangles",
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Instructions
2+
3+
Implement a basic reactive system.
4+
5+
Reactive programming is a programming paradigm that focuses on how values are computed in terms of each other to allow a change to one value to automatically propagate to other values, like in a spreadsheet.
6+
7+
Implement a basic reactive system with cells with settable values ("input" cells) and cells with values computed in terms of other cells ("compute" cells).
8+
Implement updates so that when an input value is changed, values propagate to reach a new stable system state.
9+
10+
In addition, compute cells should allow for registering change notification callbacks.
11+
Call a cell’s callbacks when the cell’s value in a new stable state has changed from the previous stable state.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"authors": [
3+
"kahgoh"
4+
],
5+
"files": {
6+
"solution": [
7+
"src/main/java/React.java"
8+
],
9+
"test": [
10+
"src/test/java/ReactTest.java"
11+
],
12+
"example": [
13+
".meta/src/reference/java/React.java"
14+
],
15+
"invalidator": [
16+
"build.gradle"
17+
]
18+
},
19+
"blurb": "Implement a basic reactive system."
20+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import java.util.*;
2+
import java.util.function.Consumer;
3+
import java.util.function.Function;
4+
import java.util.function.Supplier;
5+
import java.util.stream.Collectors;
6+
7+
public class React {
8+
9+
public static class Cell<T> {
10+
11+
T value;
12+
13+
public T getValue() {
14+
return value;
15+
}
16+
17+
final Collection<ComputeCell<T>> dependentCells = new ArrayList<>();
18+
}
19+
20+
public static class InputCell<T> extends Cell<T> {
21+
22+
InputCell(T initialValue) {
23+
this.value = initialValue;
24+
}
25+
26+
public void setValue(T newValue) {
27+
this.value = newValue;
28+
dependentCells.forEach(cell -> cell.propagateUpdate());
29+
dependentCells.forEach(cell -> cell.fireCallbacks());
30+
}
31+
}
32+
33+
public static class ComputeCell<T> extends Cell<T> {
34+
35+
private T lastFiredValue;
36+
37+
private final Supplier<T> formula;
38+
39+
final Collection<Consumer<T>> callbacks = new ArrayList<>();
40+
41+
ComputeCell(Supplier<T> formula) {
42+
this.formula = formula;
43+
this.value = formula.get();
44+
this.lastFiredValue = value;
45+
}
46+
47+
private void propagateUpdate() {
48+
value = formula.get();
49+
dependentCells.forEach(cell -> cell.propagateUpdate());
50+
}
51+
52+
private void fireCallbacks() {
53+
if (!Objects.equals(value, lastFiredValue)) {
54+
callbacks.forEach(callback -> callback.accept(value));
55+
dependentCells.forEach(cell -> cell.fireCallbacks());
56+
lastFiredValue = value;
57+
}
58+
}
59+
60+
public void addCallback(Consumer<T> callback) {
61+
callbacks.add(callback);
62+
}
63+
64+
public void removeCallback(Consumer<T> callback) {
65+
callbacks.remove(callback);
66+
}
67+
}
68+
69+
public static <T> InputCell<T> inputCell(T initialValue) {
70+
return new InputCell<>(initialValue);
71+
}
72+
73+
74+
public static <T> ComputeCell<T> computeCell(Function<List<T>, T> function, List<Cell<T>> cells) {
75+
Supplier<T> formula = () -> {
76+
List<T> cellValues = cells.stream().map(Cell::getValue).collect(Collectors.toList());
77+
return function.apply(cellValues);
78+
};
79+
80+
var computeCell = new ComputeCell<>(formula);
81+
cells.forEach(cell -> cell.dependentCells.add(computeCell));
82+
return computeCell;
83+
}
84+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# This is an auto-generated file.
2+
#
3+
# Regenerating this file via `configlet sync` will:
4+
# - Recreate every `description` key/value pair
5+
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
6+
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
7+
# - Preserve any other key/value pair
8+
#
9+
# As user-added comments (using the # character) will be removed when this file
10+
# is regenerated, comments can be added via a `comment` key.
11+
12+
[c51ee736-d001-4f30-88d1-0c8e8b43cd07]
13+
description = "input cells have a value"
14+
15+
[dedf0fe0-da0c-4d5d-a582-ffaf5f4d0851]
16+
description = "an input cell's value can be set"
17+
18+
[5854b975-f545-4f93-8968-cc324cde746e]
19+
description = "compute cells calculate initial value"
20+
21+
[25795a3d-b86c-4e91-abe7-1c340e71560c]
22+
description = "compute cells take inputs in the right order"
23+
24+
[c62689bf-7be5-41bb-b9f8-65178ef3e8ba]
25+
description = "compute cells update value when dependencies are changed"
26+
27+
[5ff36b09-0a88-48d4-b7f8-69dcf3feea40]
28+
description = "compute cells can depend on other compute cells"
29+
30+
[abe33eaf-68ad-42a5-b728-05519ca88d2d]
31+
description = "compute cells fire callbacks"
32+
33+
[9e5cb3a4-78e5-4290-80f8-a78612c52db2]
34+
description = "callback cells only fire on change"
35+
36+
[ada17cb6-7332-448a-b934-e3d7495c13d3]
37+
description = "callbacks do not report already reported values"
38+
39+
[ac271900-ea5c-461c-9add-eeebcb8c03e5]
40+
description = "callbacks can fire from multiple cells"
41+
42+
[95a82dcc-8280-4de3-a4cd-4f19a84e3d6f]
43+
description = "callbacks can be added and removed"
44+
45+
[f2a7b445-f783-4e0e-8393-469ab4915f2a]
46+
description = "removing a callback multiple times doesn't interfere with other callbacks"
47+
48+
[daf6feca-09e0-4ce5-801d-770ddfe1c268]
49+
description = "callbacks should only be called once even if multiple dependencies change"
50+
51+
[9a5b159f-b7aa-4729-807e-f1c38a46d377]
52+
description = "callbacks should not be called if dependencies change but output value doesn't change"
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
apply plugin: "java"
2+
apply plugin: "eclipse"
3+
apply plugin: "idea"
4+
5+
// set default encoding to UTF-8
6+
compileJava.options.encoding = "UTF-8"
7+
compileTestJava.options.encoding = "UTF-8"
8+
9+
repositories {
10+
mavenCentral()
11+
}
12+
13+
dependencies {
14+
testImplementation "junit:junit:4.13"
15+
testImplementation "org.assertj:assertj-core:3.15.0"
16+
}
17+
18+
test {
19+
testLogging {
20+
exceptionFormat = 'full'
21+
showStandardStreams = true
22+
events = ["passed", "failed", "skipped"]
23+
}
24+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import java.util.List;
2+
import java.util.function.Consumer;
3+
import java.util.function.Function;
4+
5+
public class React {
6+
7+
public static class Cell<T> {
8+
public T getValue() {
9+
throw new UnsupportedOperationException("Please implement the Cell.getValue() method");
10+
}
11+
}
12+
13+
public static class InputCell<T> extends Cell<T> {
14+
public void setValue(T newValue) {
15+
throw new UnsupportedOperationException("Please implement the InputCell.setValue() method");
16+
}
17+
}
18+
19+
public static class ComputeCell<T> extends Cell<T> {
20+
public void addCallback(Consumer<T> callback) {
21+
throw new UnsupportedOperationException("Please implement the ComputeCell.addCallback() method");
22+
}
23+
24+
public void removeCallback(Consumer<T> callback) {
25+
throw new UnsupportedOperationException("Please implement the ComputeCell.removeCallback() method");
26+
}
27+
}
28+
29+
public static <T> InputCell<T> inputCell(T initialValue) {
30+
throw new UnsupportedOperationException("Please implement the React.inputCell() method");
31+
}
32+
33+
public static <T> ComputeCell<T> computeCell(Function<List<T>, T> function, List<Cell<T>> cells) {
34+
throw new UnsupportedOperationException("Please implement the React.computeCell() method");
35+
}
36+
}

0 commit comments

Comments
 (0)