trackler 2.2.1.135 → 2.2.1.136
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/trackler/version.rb +1 -1
- data/problem-specifications/exercises/pov/canonical-data.json +33 -1
- data/tracks/c/exercises/acronym/test/test_acronym.c +25 -17
- data/tracks/csharp/exercises/binary-search-tree/BinarySearchTreeTest.cs +37 -28
- data/tracks/csharp/generators/Exercises/BinarySearchTree.cs +69 -0
- data/tracks/haskell/config.json +1 -0
- data/tracks/haskell/exercises/accumulate/package.yaml +1 -1
- data/tracks/haskell/exercises/acronym/package.yaml +1 -1
- data/tracks/haskell/exercises/all-your-base/package.yaml +1 -1
- data/tracks/haskell/exercises/allergies/package.yaml +1 -1
- data/tracks/haskell/exercises/alphametics/package.yaml +1 -1
- data/tracks/haskell/exercises/anagram/package.yaml +1 -1
- data/tracks/haskell/exercises/atbash-cipher/package.yaml +1 -1
- data/tracks/haskell/exercises/bank-account/package.yaml +1 -1
- data/tracks/haskell/exercises/beer-song/package.yaml +1 -1
- data/tracks/haskell/exercises/binary-search-tree/package.yaml +1 -1
- data/tracks/haskell/exercises/binary/package.yaml +1 -1
- data/tracks/haskell/exercises/bob/package.yaml +1 -1
- data/tracks/haskell/exercises/bowling/package.yaml +1 -1
- data/tracks/haskell/exercises/bracket-push/package.yaml +1 -1
- data/tracks/haskell/exercises/change/package.yaml +1 -1
- data/tracks/haskell/exercises/clock/package.yaml +1 -1
- data/tracks/haskell/exercises/collatz-conjecture/package.yaml +1 -1
- data/tracks/haskell/exercises/complex-numbers/package.yaml +1 -1
- data/tracks/haskell/exercises/connect/package.yaml +1 -1
- data/tracks/haskell/exercises/crypto-square/package.yaml +1 -1
- data/tracks/haskell/exercises/custom-set/package.yaml +1 -1
- data/tracks/haskell/exercises/diamond/package.yaml +1 -1
- data/tracks/haskell/exercises/difference-of-squares/package.yaml +1 -1
- data/tracks/haskell/exercises/dominoes/package.yaml +1 -1
- data/tracks/haskell/exercises/etl/package.yaml +1 -1
- data/tracks/haskell/exercises/food-chain/package.yaml +1 -1
- data/tracks/haskell/exercises/forth/package.yaml +1 -1
- data/tracks/haskell/exercises/gigasecond/package.yaml +1 -1
- data/tracks/haskell/exercises/go-counting/package.yaml +1 -1
- data/tracks/haskell/exercises/grade-school/package.yaml +1 -1
- data/tracks/haskell/exercises/grains/package.yaml +1 -1
- data/tracks/haskell/exercises/hamming/package.yaml +1 -1
- data/tracks/haskell/exercises/hello-world/package.yaml +1 -1
- data/tracks/haskell/exercises/hexadecimal/package.yaml +1 -1
- data/tracks/haskell/exercises/house/package.yaml +1 -1
- data/tracks/haskell/exercises/isbn-verifier/package.yaml +1 -1
- data/tracks/haskell/exercises/isogram/package.yaml +1 -1
- data/tracks/haskell/exercises/kindergarten-garden/examples/success-standard/src/Garden.hs +1 -10
- data/tracks/haskell/exercises/kindergarten-garden/package.yaml +2 -3
- data/tracks/haskell/exercises/kindergarten-garden/src/Garden.hs +3 -7
- data/tracks/haskell/exercises/kindergarten-garden/test/Tests.hs +10 -16
- data/tracks/haskell/exercises/largest-series-product/package.yaml +1 -1
- data/tracks/haskell/exercises/leap/package.yaml +1 -1
- data/tracks/haskell/exercises/lens-person/examples/success-standard/package.yaml +0 -1
- data/tracks/haskell/exercises/lens-person/package.yaml +1 -1
- data/tracks/haskell/exercises/linked-list/package.yaml +1 -1
- data/tracks/haskell/exercises/list-ops/package.yaml +1 -1
- data/tracks/haskell/exercises/luhn/package.yaml +1 -1
- data/tracks/haskell/exercises/matrix/package.yaml +1 -1
- data/tracks/haskell/exercises/meetup/package.yaml +1 -1
- data/tracks/haskell/exercises/minesweeper/package.yaml +1 -1
- data/tracks/haskell/exercises/nth-prime/package.yaml +1 -1
- data/tracks/haskell/exercises/nucleotide-count/package.yaml +1 -1
- data/tracks/haskell/exercises/ocr-numbers/package.yaml +1 -1
- data/tracks/haskell/exercises/octal/package.yaml +1 -1
- data/tracks/haskell/exercises/palindrome-products/package.yaml +1 -1
- data/tracks/haskell/exercises/pangram/package.yaml +1 -1
- data/tracks/haskell/exercises/parallel-letter-frequency/package.yaml +1 -1
- data/tracks/haskell/exercises/pascals-triangle/package.yaml +1 -1
- data/tracks/haskell/exercises/perfect-numbers/package.yaml +1 -1
- data/tracks/haskell/exercises/phone-number/package.yaml +1 -1
- data/tracks/haskell/exercises/pig-latin/package.yaml +1 -1
- data/tracks/haskell/exercises/poker/package.yaml +1 -1
- data/tracks/haskell/exercises/pov/package.yaml +2 -2
- data/tracks/haskell/exercises/pov/test/Tests.hs +15 -0
- data/tracks/haskell/exercises/prime-factors/package.yaml +1 -1
- data/tracks/haskell/exercises/protein-translation/package.yaml +1 -1
- data/tracks/haskell/exercises/pythagorean-triplet/package.yaml +1 -1
- data/tracks/haskell/exercises/queen-attack/package.yaml +1 -1
- data/tracks/haskell/exercises/rail-fence-cipher/package.yaml +1 -1
- data/tracks/haskell/exercises/raindrops/package.yaml +1 -1
- data/tracks/haskell/exercises/rna-transcription/package.yaml +1 -1
- data/tracks/haskell/exercises/robot-name/package.yaml +1 -1
- data/tracks/haskell/exercises/robot-simulator/package.yaml +1 -1
- data/tracks/haskell/exercises/roman-numerals/package.yaml +1 -1
- data/tracks/haskell/exercises/rotational-cipher/package.yaml +1 -1
- data/tracks/haskell/exercises/run-length-encoding/package.yaml +1 -1
- data/tracks/haskell/exercises/saddle-points/package.yaml +1 -1
- data/tracks/haskell/exercises/say/package.yaml +1 -1
- data/tracks/haskell/exercises/scrabble-score/package.yaml +1 -1
- data/tracks/haskell/exercises/secret-handshake/package.yaml +1 -1
- data/tracks/haskell/exercises/series/package.yaml +1 -1
- data/tracks/haskell/exercises/sgf-parsing/package.yaml +1 -1
- data/tracks/haskell/exercises/sieve/package.yaml +1 -1
- data/tracks/haskell/exercises/simple-cipher/package.yaml +1 -1
- data/tracks/haskell/exercises/simple-linked-list/package.yaml +1 -1
- data/tracks/haskell/exercises/space-age/package.yaml +1 -1
- data/tracks/haskell/exercises/spiral-matrix/package.yaml +1 -1
- data/tracks/haskell/exercises/strain/package.yaml +1 -1
- data/tracks/haskell/exercises/sublist/package.yaml +1 -1
- data/tracks/haskell/exercises/sum-of-multiples/package.yaml +1 -1
- data/tracks/haskell/exercises/transpose/package.yaml +1 -1
- data/tracks/haskell/exercises/triangle/package.yaml +1 -1
- data/tracks/haskell/exercises/trinary/package.yaml +1 -1
- data/tracks/haskell/exercises/twelve-days/package.yaml +1 -1
- data/tracks/haskell/exercises/word-count/package.yaml +1 -1
- data/tracks/haskell/exercises/wordy/package.yaml +1 -1
- data/tracks/haskell/exercises/zebra-puzzle/package.yaml +1 -1
- data/tracks/haskell/exercises/zipper/package.yaml +1 -1
- data/tracks/java/CONTRIBUTING.md +39 -3
- data/tracks/java/POLICIES.md +68 -3
- data/tracks/java/exercises/crypto-square/README.md +4 -5
- data/tracks/java/exercises/error-handling/README.md +38 -0
- data/tracks/java/exercises/series/README.md +6 -6
- data/tracks/java/exercises/sieve/README.md +5 -3
- data/tracks/java/scripts/canonical_data_check.sh +1 -1
- data/tracks/python/exercises/react/example.py +54 -63
- data/tracks/python/exercises/react/react.py +10 -10
- data/tracks/python/exercises/react/react_test.py +197 -131
- data/tracks/python/exercises/sieve/README.md +5 -3
- data/tracks/rust/exercises/react/example.rs +103 -64
- data/tracks/rust/exercises/react/src/lib.rs +31 -17
- data/tracks/rust/exercises/react/tests/react.rs +52 -61
- data/tracks/swift/config.json +11 -0
- data/tracks/swift/exercises/circular-buffer/Sources/CircularBufferExample.swift +12 -12
- data/tracks/swift/exercises/circular-buffer/Tests/CircularBufferTests/CircularBufferTests.swift +1 -1
- data/tracks/swift/exercises/proverb/Package.swift +5 -0
- data/tracks/swift/exercises/proverb/README.md +25 -0
- data/tracks/swift/exercises/proverb/Sources/Proverb.swift +1 -0
- data/tracks/swift/exercises/proverb/Sources/ProverbExample.swift +28 -0
- data/tracks/swift/exercises/proverb/Tests/LinuxMain.swift +6 -0
- data/tracks/swift/exercises/proverb/Tests/ProverbTests/ProverbTests.swift +61 -0
- data/tracks/typescript/config.json +14 -0
- data/tracks/typescript/exercises/palindrome-products/README.md +65 -0
- data/tracks/typescript/exercises/palindrome-products/package.json +36 -0
- data/tracks/typescript/exercises/palindrome-products/palindrome-products.example.ts +38 -0
- data/tracks/typescript/exercises/palindrome-products/palindrome-products.test.ts +69 -0
- data/tracks/typescript/exercises/palindrome-products/palindrome-products.ts +0 -0
- data/tracks/typescript/exercises/palindrome-products/tsconfig.json +22 -0
- data/tracks/typescript/exercises/palindrome-products/tslint.json +127 -0
- data/tracks/typescript/exercises/palindrome-products/yarn.lock +2624 -0
- metadata +17 -2
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
class
|
|
2
|
-
def
|
|
3
|
-
|
|
1
|
+
class InputCell(object):
|
|
2
|
+
def __init__(self, initial_value):
|
|
3
|
+
self.value = None
|
|
4
4
|
|
|
5
|
-
def add_watcher(self, watcher_callback):
|
|
6
|
-
pass
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
class ComputeCell(object):
|
|
7
|
+
def __init__(self, inputs, compute_function):
|
|
8
|
+
self.value = None
|
|
10
9
|
|
|
10
|
+
def add_callback(self, callback):
|
|
11
|
+
pass
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
def create_input_cell(self, value):
|
|
13
|
+
def remove_callback(self, callback):
|
|
14
14
|
pass
|
|
15
15
|
|
|
16
|
-
def
|
|
16
|
+
def expect_callback_values(self, callback):
|
|
17
17
|
pass
|
|
@@ -1,135 +1,201 @@
|
|
|
1
1
|
import unittest
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
self.assertEqual(
|
|
51
|
-
|
|
52
|
-
self.assertEqual(
|
|
53
|
-
|
|
54
|
-
def
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
self.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
self.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
self.
|
|
116
|
-
self.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
self.assertEqual(
|
|
2
|
+
from functools import partial
|
|
3
|
+
|
|
4
|
+
from react import InputCell, ComputeCell
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# Tests adapted from `problem-specifications//canonical-data.json` @ v2.0.0
|
|
8
|
+
|
|
9
|
+
class ReactTests(unittest.TestCase):
|
|
10
|
+
|
|
11
|
+
def test_input_cells_have_a_value(self):
|
|
12
|
+
input_ = InputCell(10)
|
|
13
|
+
self.assertEqual(input_.value, 10)
|
|
14
|
+
|
|
15
|
+
def test_can_set_input_cell_value(self):
|
|
16
|
+
input_ = InputCell(4)
|
|
17
|
+
input_.value = 20
|
|
18
|
+
self.assertEqual(input_.value, 20)
|
|
19
|
+
|
|
20
|
+
def test_compute_cells_calculate_initial_value(self):
|
|
21
|
+
input_ = InputCell(1)
|
|
22
|
+
output = ComputeCell([input_], lambda inputs: inputs[0] + 1)
|
|
23
|
+
self.assertEqual(output.value, 2)
|
|
24
|
+
|
|
25
|
+
def test_compute_cells_take_inputs_in_right_order(self):
|
|
26
|
+
one = InputCell(1)
|
|
27
|
+
two = InputCell(2)
|
|
28
|
+
output = ComputeCell(
|
|
29
|
+
[one, two],
|
|
30
|
+
lambda inputs: inputs[0] + inputs[1]*10
|
|
31
|
+
)
|
|
32
|
+
self.assertEqual(output.value, 21)
|
|
33
|
+
|
|
34
|
+
def test_compute_cells_update_value_when_dependencies_are_changed(self):
|
|
35
|
+
input_ = InputCell(1)
|
|
36
|
+
output = ComputeCell([input_], lambda inputs: inputs[0] + 1)
|
|
37
|
+
|
|
38
|
+
input_.value = 3
|
|
39
|
+
self.assertEqual(output.value, 4)
|
|
40
|
+
|
|
41
|
+
def test_compute_cells_can_depend_on_other_compute_cells(self):
|
|
42
|
+
input_ = InputCell(1)
|
|
43
|
+
times_two = ComputeCell([input_], lambda inputs: inputs[0] * 2)
|
|
44
|
+
times_thirty = ComputeCell([input_], lambda inputs: inputs[0] * 30)
|
|
45
|
+
output = ComputeCell(
|
|
46
|
+
[times_two, times_thirty],
|
|
47
|
+
lambda inputs: inputs[0] + inputs[1]
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
self.assertEqual(output.value, 32)
|
|
51
|
+
input_.value = 3
|
|
52
|
+
self.assertEqual(output.value, 96)
|
|
53
|
+
|
|
54
|
+
def test_compute_cells_fire_callbacks(self):
|
|
55
|
+
input_ = InputCell(1)
|
|
56
|
+
output = ComputeCell([input_], lambda inputs: inputs[0] + 1)
|
|
57
|
+
|
|
58
|
+
observer = []
|
|
59
|
+
callback1 = self.callback_factory(observer)
|
|
60
|
+
|
|
61
|
+
output.add_callback(callback1)
|
|
62
|
+
input_.value = 3
|
|
63
|
+
self.assertEqual(observer[-1], 4)
|
|
64
|
+
|
|
65
|
+
def test_callbacks_only_fire_on_change(self):
|
|
66
|
+
input_ = InputCell(1)
|
|
67
|
+
output = ComputeCell(
|
|
68
|
+
[input_],
|
|
69
|
+
lambda inputs: 111 if inputs[0] < 3 else 222
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
observer = []
|
|
73
|
+
callback1 = self.callback_factory(observer)
|
|
74
|
+
|
|
75
|
+
output.add_callback(callback1)
|
|
76
|
+
input_.value = 2
|
|
77
|
+
self.assertEqual(observer, [])
|
|
78
|
+
input_.value = 4
|
|
79
|
+
self.assertEqual(observer[-1], 222)
|
|
80
|
+
|
|
81
|
+
def test_callbacks_do_not_report_already_reported_values(self):
|
|
82
|
+
input_ = InputCell(1)
|
|
83
|
+
output = ComputeCell([input_], lambda inputs: inputs[0] + 1)
|
|
84
|
+
|
|
85
|
+
observer = []
|
|
86
|
+
callback1 = self.callback_factory(observer)
|
|
87
|
+
|
|
88
|
+
output.add_callback(callback1)
|
|
89
|
+
input_.value = 2
|
|
90
|
+
self.assertEqual(observer[-1], 3)
|
|
91
|
+
input_.value = 3
|
|
92
|
+
self.assertEqual(observer[-1], 4)
|
|
93
|
+
|
|
94
|
+
def test_callbacks_can_fire_from_multiple_cells(self):
|
|
95
|
+
input_ = InputCell(1)
|
|
96
|
+
plus_one = ComputeCell([input_], lambda inputs: inputs[0] + 1)
|
|
97
|
+
minus_one = ComputeCell([input_], lambda inputs: inputs[0] - 1)
|
|
98
|
+
|
|
99
|
+
cb1_observer, cb2_observer = [], []
|
|
100
|
+
callback1 = self.callback_factory(cb1_observer)
|
|
101
|
+
callback2 = self.callback_factory(cb2_observer)
|
|
102
|
+
|
|
103
|
+
plus_one.add_callback(callback1)
|
|
104
|
+
minus_one.add_callback(callback2)
|
|
105
|
+
input_.value = 10
|
|
106
|
+
|
|
107
|
+
self.assertEqual(cb1_observer[-1], 11)
|
|
108
|
+
self.assertEqual(cb2_observer[-1], 9)
|
|
109
|
+
|
|
110
|
+
def test_callbacks_can_be_added_and_removed(self):
|
|
111
|
+
input_ = InputCell(11)
|
|
112
|
+
output = ComputeCell([input_], lambda inputs: inputs[0] + 1)
|
|
113
|
+
|
|
114
|
+
cb1_observer, cb2_observer, cb3_observer = [], [], []
|
|
115
|
+
callback1 = self.callback_factory(cb1_observer)
|
|
116
|
+
callback2 = self.callback_factory(cb2_observer)
|
|
117
|
+
callback3 = self.callback_factory(cb3_observer)
|
|
118
|
+
|
|
119
|
+
output.add_callback(callback1)
|
|
120
|
+
output.add_callback(callback2)
|
|
121
|
+
input_.value = 31
|
|
122
|
+
self.assertEqual(cb1_observer[-1], 32)
|
|
123
|
+
self.assertEqual(cb2_observer[-1], 32)
|
|
124
|
+
|
|
125
|
+
output.remove_callback(callback1)
|
|
126
|
+
output.add_callback(callback3)
|
|
127
|
+
input_.value = 41
|
|
128
|
+
self.assertEqual(cb2_observer[-1], 42)
|
|
129
|
+
self.assertEqual(cb3_observer[-1], 42)
|
|
130
|
+
|
|
131
|
+
# Expect callback1 not to be called.
|
|
132
|
+
self.assertEqual(len(cb1_observer), 1)
|
|
133
|
+
|
|
134
|
+
def test_removing_a_callback_multiple_times(self):
|
|
135
|
+
"""Guard against incorrect implementations which store their
|
|
136
|
+
callbacks in an array."""
|
|
137
|
+
input_ = InputCell(1)
|
|
138
|
+
output = ComputeCell([input_], lambda inputs: inputs[0] + 1)
|
|
139
|
+
|
|
140
|
+
cb1_observer, cb2_observer = [], []
|
|
141
|
+
callback1 = self.callback_factory(cb1_observer)
|
|
142
|
+
callback2 = self.callback_factory(cb2_observer)
|
|
143
|
+
|
|
144
|
+
output.add_callback(callback1)
|
|
145
|
+
output.add_callback(callback2)
|
|
146
|
+
output.remove_callback(callback1)
|
|
147
|
+
output.remove_callback(callback1)
|
|
148
|
+
output.remove_callback(callback1)
|
|
149
|
+
input_.value = 2
|
|
150
|
+
|
|
151
|
+
self.assertEqual(cb1_observer, [])
|
|
152
|
+
self.assertEqual(cb2_observer[-1], 3)
|
|
153
|
+
|
|
154
|
+
def test_callbacks_should_only_be_called_once(self):
|
|
155
|
+
"""Guard against incorrect implementations which call a callback
|
|
156
|
+
function multiple times when multiple dependencies change."""
|
|
157
|
+
input_ = InputCell(1)
|
|
158
|
+
plus_one = ComputeCell([input_], lambda inputs: inputs[0] + 1)
|
|
159
|
+
minus_one1 = ComputeCell([input_], lambda inputs: inputs[0] - 1)
|
|
160
|
+
minus_one2 = ComputeCell([minus_one1], lambda inputs: inputs[0] - 1)
|
|
161
|
+
output = ComputeCell(
|
|
162
|
+
[plus_one, minus_one2],
|
|
163
|
+
lambda inputs: inputs[0] * inputs[1]
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
observer = []
|
|
167
|
+
callback1 = self.callback_factory(observer)
|
|
168
|
+
|
|
169
|
+
output.add_callback(callback1)
|
|
170
|
+
input_.value = 4
|
|
171
|
+
self.assertEqual(observer[-1], 10)
|
|
172
|
+
|
|
173
|
+
def test_callbacks_not_called_so_long_as_output_not_changed(self):
|
|
174
|
+
"""Guard against incorrect implementations which call callbacks
|
|
175
|
+
if dependencies change but output value doesn't change."""
|
|
176
|
+
input_ = InputCell(1)
|
|
177
|
+
plus_one = ComputeCell([input_], lambda inputs: inputs[0] + 1)
|
|
178
|
+
minus_one = ComputeCell([input_], lambda inputs: inputs[0] - 1)
|
|
179
|
+
always_two = ComputeCell(
|
|
180
|
+
[plus_one, minus_one],
|
|
181
|
+
lambda inputs: inputs[0] - inputs[1]
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
observer = []
|
|
185
|
+
callback1 = self.callback_factory(observer)
|
|
186
|
+
|
|
187
|
+
always_two.add_callback(callback1)
|
|
188
|
+
input_.value = 2
|
|
189
|
+
input_.value = 3
|
|
190
|
+
input_.value = 4
|
|
191
|
+
input_.value = 5
|
|
192
|
+
self.assertEqual(observer, [])
|
|
193
|
+
|
|
194
|
+
# Utility functions.
|
|
195
|
+
def callback_factory(self, observer):
|
|
196
|
+
def callback(observer, value):
|
|
197
|
+
observer.append(value)
|
|
198
|
+
return partial(callback, observer)
|
|
133
199
|
|
|
134
200
|
|
|
135
201
|
if __name__ == '__main__':
|
|
@@ -5,8 +5,8 @@ number.
|
|
|
5
5
|
|
|
6
6
|
The Sieve of Eratosthenes is a simple, ancient algorithm for finding all
|
|
7
7
|
prime numbers up to any given limit. It does so by iteratively marking as
|
|
8
|
-
composite (i.e. not prime) the multiples of each prime,
|
|
9
|
-
|
|
8
|
+
composite (i.e. not prime) the multiples of each prime, starting with the
|
|
9
|
+
multiples of 2. It does not use any division or remainder operation.
|
|
10
10
|
|
|
11
11
|
Create your range, starting at two and continuing up to and including the given limit. (i.e. [2, limit])
|
|
12
12
|
|
|
@@ -25,7 +25,9 @@ https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
|
|
|
25
25
|
|
|
26
26
|
Notice that this is a very specific algorithm, and the tests don't check
|
|
27
27
|
that you've implemented the algorithm, only that you've come up with the
|
|
28
|
-
correct list of primes.
|
|
28
|
+
correct list of primes. A good first test is to check that you do not use
|
|
29
|
+
division or remainder operations (div, /, mod or % depending on the
|
|
30
|
+
language).
|
|
29
31
|
|
|
30
32
|
## Exception messages
|
|
31
33
|
|
|
@@ -1,12 +1,31 @@
|
|
|
1
1
|
use std::collections::HashMap;
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
/// `InputCellID` is a unique identifier for an input cell.
|
|
4
|
+
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
5
|
+
pub struct InputCellID(usize);
|
|
6
|
+
/// `ComputeCellID` is a unique identifier for a compute cell.
|
|
7
|
+
/// Values of type `InputCellID` and `ComputeCellID` should not be mutually assignable,
|
|
8
|
+
/// demonstrated by the following tests:
|
|
9
|
+
///
|
|
10
|
+
/// ```compile_fail
|
|
11
|
+
/// let mut r = react::Reactor::new();
|
|
12
|
+
/// let input: react::ComputeCellID = r.create_input(111);
|
|
13
|
+
/// ```
|
|
14
|
+
///
|
|
15
|
+
/// ```compile_fail
|
|
16
|
+
/// let mut r = react::Reactor::new();
|
|
17
|
+
/// let input = r.create_input(111);
|
|
18
|
+
/// let compute: react::InputCellID = r.create_compute(&[react::CellID::Input(input)], |_| 222).unwrap();
|
|
19
|
+
/// ```
|
|
20
|
+
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
21
|
+
pub struct ComputeCellID(usize);
|
|
22
|
+
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
23
|
+
pub struct CallbackID(usize);
|
|
24
|
+
|
|
25
|
+
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
26
|
+
pub enum CellID {
|
|
27
|
+
Input(InputCellID),
|
|
28
|
+
Compute(ComputeCellID),
|
|
10
29
|
}
|
|
11
30
|
|
|
12
31
|
#[derive(Debug, PartialEq)]
|
|
@@ -15,27 +34,38 @@ pub enum RemoveCallbackError {
|
|
|
15
34
|
NonexistentCallback,
|
|
16
35
|
}
|
|
17
36
|
|
|
18
|
-
struct Cell<
|
|
37
|
+
struct Cell<T: Copy> {
|
|
19
38
|
value: T,
|
|
20
39
|
last_value: T,
|
|
21
|
-
dependents: Vec<
|
|
22
|
-
cell_type: CellType<'a, T>,
|
|
23
|
-
callbacks_issued: usize,
|
|
24
|
-
callbacks: HashMap<CallbackID, Box<FnMut(T) -> () + 'a>>,
|
|
40
|
+
dependents: Vec<ComputeCellID>,
|
|
25
41
|
}
|
|
26
42
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
43
|
+
struct ComputeCell<'a, T: Copy> {
|
|
44
|
+
cell: Cell<T>,
|
|
45
|
+
|
|
46
|
+
dependencies: Vec<CellID>,
|
|
47
|
+
f: Box<Fn(&[T]) -> T + 'a>,
|
|
48
|
+
callbacks_issued: usize,
|
|
49
|
+
callbacks: HashMap<CallbackID, Box<FnMut(T) -> () + 'a>>,
|
|
30
50
|
}
|
|
31
51
|
|
|
32
|
-
impl <
|
|
33
|
-
fn new(initial: T
|
|
52
|
+
impl <T: Copy> Cell<T> {
|
|
53
|
+
fn new(initial: T) -> Self {
|
|
34
54
|
Cell {
|
|
35
55
|
value: initial,
|
|
36
56
|
last_value: initial,
|
|
37
57
|
dependents: Vec::new(),
|
|
38
|
-
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
impl <'a, T: Copy> ComputeCell<'a, T> {
|
|
63
|
+
fn new<F: Fn(&[T]) -> T + 'a>(initial: T, dependencies: Vec<CellID>, f: F) -> Self {
|
|
64
|
+
ComputeCell {
|
|
65
|
+
cell: Cell::new(initial),
|
|
66
|
+
|
|
67
|
+
dependencies,
|
|
68
|
+
f: Box::new(f),
|
|
39
69
|
callbacks_issued: 0,
|
|
40
70
|
callbacks: HashMap::new(),
|
|
41
71
|
}
|
|
@@ -43,52 +73,58 @@ impl <'a, T: Copy> Cell<'a, T> {
|
|
|
43
73
|
}
|
|
44
74
|
|
|
45
75
|
pub struct Reactor<'a, T: Copy> {
|
|
46
|
-
|
|
76
|
+
inputs: Vec<Cell<T>>,
|
|
77
|
+
computes: Vec<ComputeCell<'a, T>>,
|
|
47
78
|
}
|
|
48
79
|
|
|
49
80
|
impl <'a, T: Copy + PartialEq> Reactor<'a, T> {
|
|
50
81
|
pub fn new() -> Self {
|
|
51
82
|
Reactor{
|
|
52
|
-
|
|
83
|
+
inputs: Vec::new(),
|
|
84
|
+
computes: Vec::new(),
|
|
53
85
|
}
|
|
54
86
|
}
|
|
55
87
|
|
|
56
|
-
pub fn create_input(&mut self, initial: T) ->
|
|
57
|
-
self.
|
|
58
|
-
self.
|
|
88
|
+
pub fn create_input(&mut self, initial: T) -> InputCellID {
|
|
89
|
+
self.inputs.push(Cell::new(initial));
|
|
90
|
+
InputCellID(self.inputs.len() - 1)
|
|
59
91
|
}
|
|
60
92
|
|
|
61
|
-
pub fn create_compute<F: Fn(&[T]) -> T + 'a>(&mut self, dependencies: &[CellID], compute_func: F) -> Result<
|
|
93
|
+
pub fn create_compute<F: Fn(&[T]) -> T + 'a>(&mut self, dependencies: &[CellID], compute_func: F) -> Result<ComputeCellID, CellID> {
|
|
62
94
|
// Check all dependencies' validity before modifying any of them,
|
|
63
95
|
// so that we don't perform an incorrect partial write.
|
|
64
|
-
|
|
65
|
-
|
|
96
|
+
for &dep in dependencies {
|
|
97
|
+
match dep {
|
|
98
|
+
CellID::Input(InputCellID(id)) => if id >= self.inputs.len() { return Err(dep) },
|
|
99
|
+
CellID::Compute(ComputeCellID(id)) => if id >= self.computes.len() { return Err(dep) },
|
|
100
|
+
}
|
|
66
101
|
}
|
|
67
|
-
let new_id = self.
|
|
68
|
-
for &
|
|
69
|
-
|
|
102
|
+
let new_id = ComputeCellID(self.computes.len());
|
|
103
|
+
for &dep in dependencies {
|
|
104
|
+
match dep {
|
|
105
|
+
CellID::Input(InputCellID(id)) => self.inputs[id].dependents.push(new_id),
|
|
106
|
+
CellID::Compute(ComputeCellID(id)) => self.computes[id].cell.dependents.push(new_id),
|
|
107
|
+
}
|
|
70
108
|
}
|
|
71
109
|
let inputs: Vec<_> = dependencies.iter().map(|&id| self.value(id).unwrap()).collect();
|
|
72
110
|
let initial = compute_func(&inputs);
|
|
73
|
-
self.
|
|
111
|
+
self.computes.push(ComputeCell::new(initial, dependencies.to_vec(), compute_func));
|
|
74
112
|
Ok(new_id)
|
|
75
113
|
}
|
|
76
114
|
|
|
77
115
|
pub fn value(&self, id: CellID) -> Option<T> {
|
|
78
|
-
|
|
116
|
+
match id {
|
|
117
|
+
CellID::Input(InputCellID(id)) => self.inputs.get(id).map(|c| c.value),
|
|
118
|
+
CellID::Compute(ComputeCellID(id)) => self.computes.get(id).map(|c| c.cell.value),
|
|
119
|
+
}
|
|
79
120
|
}
|
|
80
121
|
|
|
81
|
-
pub fn set_value(&mut self, id:
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
},
|
|
88
|
-
CellType::Compute(_, _) => Err(SetValueError::ComputeCell),
|
|
89
|
-
},
|
|
90
|
-
None => Err(SetValueError::NonexistentCell),
|
|
91
|
-
}.map(|deps| {
|
|
122
|
+
pub fn set_value(&mut self, id: InputCellID, new_value: T) -> bool {
|
|
123
|
+
let InputCellID(id) = id;
|
|
124
|
+
self.inputs.get_mut(id).map(|c| {
|
|
125
|
+
c.value = new_value;
|
|
126
|
+
c.dependents.clone()
|
|
127
|
+
}).map(|deps| {
|
|
92
128
|
for &d in deps.iter() {
|
|
93
129
|
self.update_dependent(d);
|
|
94
130
|
}
|
|
@@ -97,19 +133,22 @@ impl <'a, T: Copy + PartialEq> Reactor<'a, T> {
|
|
|
97
133
|
for d in deps {
|
|
98
134
|
self.fire_callbacks(d);
|
|
99
135
|
}
|
|
100
|
-
})
|
|
136
|
+
}).is_some()
|
|
101
137
|
}
|
|
102
138
|
|
|
103
|
-
pub fn add_callback<F: FnMut(T) -> () + 'a>(&mut self, id:
|
|
104
|
-
|
|
139
|
+
pub fn add_callback<F: FnMut(T) -> () + 'a>(&mut self, id: ComputeCellID, callback: F) -> Option<CallbackID> {
|
|
140
|
+
let ComputeCellID(id) = id;
|
|
141
|
+
self.computes.get_mut(id).map(|c| {
|
|
105
142
|
c.callbacks_issued += 1;
|
|
106
|
-
|
|
107
|
-
c.
|
|
143
|
+
let cbid = CallbackID(c.callbacks_issued);
|
|
144
|
+
c.callbacks.insert(cbid, Box::new(callback));
|
|
145
|
+
cbid
|
|
108
146
|
})
|
|
109
147
|
}
|
|
110
148
|
|
|
111
|
-
pub fn remove_callback(&mut self, cell:
|
|
112
|
-
|
|
149
|
+
pub fn remove_callback(&mut self, cell: ComputeCellID, callback: CallbackID) -> Result<(), RemoveCallbackError> {
|
|
150
|
+
let ComputeCellID(cell) = cell;
|
|
151
|
+
match self.computes.get_mut(cell) {
|
|
113
152
|
Some(c) => match c.callbacks.remove(&callback) {
|
|
114
153
|
Some(_) => Ok(()),
|
|
115
154
|
None => Err(RemoveCallbackError::NonexistentCallback),
|
|
@@ -118,29 +157,28 @@ impl <'a, T: Copy + PartialEq> Reactor<'a, T> {
|
|
|
118
157
|
}
|
|
119
158
|
}
|
|
120
159
|
|
|
121
|
-
fn update_dependent(&mut self, id:
|
|
160
|
+
fn update_dependent(&mut self, id: ComputeCellID) {
|
|
161
|
+
let ComputeCellID(id) = id;
|
|
162
|
+
|
|
122
163
|
let (new_value, dependents) = {
|
|
123
164
|
// This block limits the scope of the self.cells borrow.
|
|
124
165
|
// This is necessary becaue we borrow it mutably below.
|
|
125
|
-
let (dependencies, f, dependents) = match self.
|
|
126
|
-
Some(c) =>
|
|
127
|
-
CellType::Input => panic!("Input cell can't be a dependent"),
|
|
128
|
-
CellType::Compute(ref dependencies, ref f) => (dependencies, f, c.dependents.clone()),
|
|
129
|
-
},
|
|
166
|
+
let (dependencies, f, dependents) = match self.computes.get(id) {
|
|
167
|
+
Some(c) => (&c.dependencies, &c.f, c.cell.dependents.clone()),
|
|
130
168
|
None => panic!("Cell to update disappeared while querying"),
|
|
131
169
|
};
|
|
132
170
|
let inputs: Vec<_> = dependencies.iter().map(|&id| self.value(id).unwrap()).collect();
|
|
133
171
|
(f(&inputs), dependents)
|
|
134
172
|
};
|
|
135
173
|
|
|
136
|
-
match self.
|
|
174
|
+
match self.computes.get_mut(id) {
|
|
137
175
|
Some(c) => {
|
|
138
|
-
if c.value == new_value {
|
|
176
|
+
if c.cell.value == new_value {
|
|
139
177
|
// No change here, we don't need to update our dependents.
|
|
140
178
|
// (It wouldn't hurt to, but it would be unnecessary work)
|
|
141
179
|
return;
|
|
142
180
|
}
|
|
143
|
-
c.value = new_value;
|
|
181
|
+
c.cell.value = new_value;
|
|
144
182
|
},
|
|
145
183
|
None => panic!("Cell to update disappeared while updating"),
|
|
146
184
|
}
|
|
@@ -150,19 +188,20 @@ impl <'a, T: Copy + PartialEq> Reactor<'a, T> {
|
|
|
150
188
|
}
|
|
151
189
|
}
|
|
152
190
|
|
|
153
|
-
fn fire_callbacks(&mut self, id:
|
|
154
|
-
let
|
|
191
|
+
fn fire_callbacks(&mut self, id: ComputeCellID) {
|
|
192
|
+
let ComputeCellID(id) = id;
|
|
193
|
+
let dependents = match self.computes.get_mut(id) {
|
|
155
194
|
Some(c) => {
|
|
156
|
-
if c.value == c.last_value {
|
|
195
|
+
if c.cell.value == c.cell.last_value {
|
|
157
196
|
// Value hasn't changed since last callback fire.
|
|
158
197
|
// We thus shouldn't fire the callbacks.
|
|
159
198
|
return
|
|
160
199
|
}
|
|
161
200
|
for cb in c.callbacks.values_mut() {
|
|
162
|
-
cb(c.value);
|
|
201
|
+
cb(c.cell.value);
|
|
163
202
|
}
|
|
164
|
-
c.last_value = c.value;
|
|
165
|
-
c.dependents.clone()
|
|
203
|
+
c.cell.last_value = c.cell.value;
|
|
204
|
+
c.cell.dependents.clone()
|
|
166
205
|
},
|
|
167
206
|
None => panic!("Callback cell disappeared"),
|
|
168
207
|
};
|