瀏覽代碼

Implement new exercise rational-numbers (#1274)

* Initial

* Extract certain logic to methods

* Implement and add tests

* Use 'One' and 'Zero' in test method names instead of '1' and '0'

* Change spacing

* Add remaining tests

* Fix indentation

* Use better method for calculating rth root of x

* Move reference implementation to .meta

* Include exercise in settings.gradle

* Add README

* Add reference to config.js

* Add UUID and unlocked-by

* Make methods package-private; Ignore all but first test

* Add 'algorithms' to topics because of gcd

* Give @Ignores the proper message and put in the proper order

* Generate README using configlet

* Add version file

* Remove 'classes' from topics

* Make class package private

* Make class package private

* Use equals method and assertEquals (and toString) for comparing Rationals

* Stub out Rational with equals and toString

* Stub out methods

* Remove comment
Jeffrey Pratt 8 年之前
父節點
當前提交
71a1fcb239

+ 12
- 0
config.json 查看文件

@@ -1119,6 +1119,18 @@
1119 1119
     {
1120 1120
       "core": false,
1121 1121
       "difficulty": 8,
1122
+      "slug": "rational-numbers",
1123
+      "topics": [
1124
+        "floating_point_numbers",
1125
+        "mathematics",
1126
+        "algorithms"
1127
+      ],
1128
+      "unlocked_by": "triangle",
1129
+      "uuid": "50ed54ce-3047-4590-9fda-0a7e9aeeba30"
1130
+    },
1131
+    {
1132
+      "core": false,
1133
+      "difficulty": 8,
1122 1134
       "slug": "list-ops",
1123 1135
       "topics": [
1124 1136
         "filtering",

+ 118
- 0
exercises/rational-numbers/.meta/src/reference/java/Rational.java 查看文件

@@ -0,0 +1,118 @@
1
+class Rational {
2
+    private int numerator;
3
+    private int denominator;
4
+
5
+    Rational(final int numerator, final int denominator) {
6
+        if (denominator == 0) {
7
+            throw new IllegalArgumentException("Denominator must not be zero.");
8
+        }
9
+
10
+        this.numerator = numerator;
11
+        this.denominator = denominator;
12
+
13
+        this.reduce();
14
+        this.ensureSignInNumerator();
15
+    }
16
+
17
+    private void reduce() {
18
+        final int commonDivisor = gcd(this.numerator, this.denominator);
19
+
20
+        this.numerator /= commonDivisor;
21
+        this.denominator /= commonDivisor;
22
+    }
23
+
24
+    private int gcd(int a, int b) {
25
+        while (b != 0) {
26
+            final int t = b;
27
+            b = a % b;
28
+            a = t;
29
+        }
30
+        return a;
31
+    }
32
+
33
+    private void ensureSignInNumerator() {
34
+        if (this.denominator < 0) {
35
+            this.denominator = -this.denominator;
36
+            this.numerator = -this.numerator;
37
+        }
38
+    }
39
+
40
+    int getNumerator() {
41
+        return this.numerator;
42
+    }
43
+
44
+    int getDenominator() {
45
+        return this.denominator;
46
+    }
47
+
48
+    Rational abs() {
49
+        return new Rational(abs(this.numerator), abs(this.denominator));
50
+    }
51
+
52
+    private int abs(int x) {
53
+        final int mask = x >> 31;
54
+        return (mask ^ x) - mask;
55
+    }
56
+
57
+    Rational add(Rational that) {
58
+        final int commonDenominator = this.denominator * that.denominator;
59
+
60
+        return new Rational(
61
+                this.numerator * that.denominator + that.numerator * this.denominator,
62
+                commonDenominator);
63
+    }
64
+
65
+    Rational subtract(Rational that) {
66
+        final int commonDenominator = this.denominator * that.denominator;
67
+
68
+        return new Rational(
69
+                this.numerator * that.denominator - that.numerator * this.denominator,
70
+                commonDenominator);
71
+    }
72
+
73
+    Rational multiply(Rational that) {
74
+        return new Rational(this.numerator * that.numerator, this.denominator * that.denominator);
75
+    }
76
+
77
+    Rational divide(Rational that) {
78
+        return new Rational(this.numerator * that.denominator, this.denominator * that.numerator);
79
+    }
80
+
81
+    Rational pow(int n) {
82
+        return new Rational(pow(this.numerator, n), pow(this.denominator, n));
83
+    }
84
+
85
+    double pow(double x) {
86
+        return Math.pow(this.numerator, x) / Math.pow(this.denominator, x);
87
+    }
88
+
89
+    private int pow(final int base, final int exponent) {
90
+        int product = 1;
91
+
92
+        for (int i = 0; i < exponent; i++) {
93
+            product *= base;
94
+        }
95
+
96
+        return product;
97
+    }
98
+
99
+    double exp(double base) {
100
+        return Math.pow(10.0, Math.log10(Math.pow(base, this.numerator)) / this.denominator);
101
+    }
102
+
103
+    @Override
104
+    public String toString() {
105
+        return String.format("%d/%d", this.getNumerator(), this.getDenominator());
106
+    }
107
+
108
+    @Override
109
+    public boolean equals(Object obj) {
110
+        if (obj == null || !this.getClass().isAssignableFrom(obj.getClass())) {
111
+            return false;
112
+        }
113
+
114
+        Rational other = (Rational) obj;
115
+        return this.getNumerator() == other.getNumerator()
116
+            && this.getDenominator() == other.getDenominator();
117
+    }
118
+}

+ 1
- 0
exercises/rational-numbers/.meta/version 查看文件

@@ -0,0 +1 @@
1
+1.0.0

+ 47
- 0
exercises/rational-numbers/README.md 查看文件

@@ -0,0 +1,47 @@
1
+# Rational Numbers
2
+
3
+A rational number is defined as the quotient of two integers `a` and `b`, called the numerator and denominator, respectively, where `b != 0`.
4
+
5
+The absolute value `|r|` of the rational number `r = a/b` is equal to `|a|/|b|`.
6
+
7
+The sum of two rational numbers `r1 = a1/b1` and `r2 = a2/b2` is `r1 + r2 = a1/b1 + a2/b2 = (a1 * b2 + a2 * b1) / (b1 * b2)`.
8
+
9
+The difference of two rational numbers `r1 = a1/b1` and `r2 = a2/b2` is `r1 - r2 = a1/b1 - a2/b2 = (a1 * b2 - a2 * b1) / (b1 * b2)`.
10
+
11
+The product (multiplication) of two rational numbers `r1 = a1/b1` and `r2 = a2/b2` is `r1 * r2 = (a1 * a2) / (b1 * b2)`.
12
+
13
+Dividing a rational number `r1 = a1/b1` by another `r2 = a2/b2` is `r1 / r2 = (a1 * b2) / (a2 * b1)` if `a2 * b1` is not zero.
14
+
15
+Exponentiation of a rational number `r = a/b` to a non-negative integer power `n` is `r^n = (a^n)/(b^n)`.
16
+
17
+Exponentiation of a rational number `r = a/b` to a negative integer power `n` is `r^n = (b^m)/(a^m)`, where `m = |n|`.
18
+
19
+Exponentiation of a rational number `r = a/b` to a real (floating-point) number `x` is the quotient `(a^x)/(b^x)`, which is a real number.
20
+
21
+Exponentiation of a real number `x` to a rational number `r = a/b` is `x^(a/b) = root(x^a, b)`, where `root(p, q)` is the `q`th root of `p`.
22
+
23
+Implement the following operations:
24
+ - addition, subtraction, multiplication and division of two rational numbers,
25
+ - absolute value, exponentiation of a given rational number to an integer power, exponentiation of a given rational number to a real (floating-point) power, exponentiation of a real number to a rational number.
26
+
27
+Your implementation of rational numbers should always be reduced to lowest terms. For example, `4/4` should reduce to `1/1`, `30/60` should reduce to `1/2`, `12/8` should reduce to `3/2`, etc. To reduce a rational number `r = a/b`, divide `a` and `b` by the greatest common divisor (gcd) of `a` and `b`. So, for example, `gcd(12, 8) = 4`, so `r = 12/8` can be reduced to `(12/4)/(8/4) = 3/2`.
28
+
29
+Assume that the programming language you are using does not have an implementation of rational numbers.
30
+
31
+# Running the tests
32
+
33
+You can run all the tests for an exercise by entering
34
+
35
+```sh
36
+$ gradle test
37
+```
38
+
39
+in your terminal.
40
+
41
+## Source
42
+
43
+Wikipedia [https://en.wikipedia.org/wiki/Rational_number](https://en.wikipedia.org/wiki/Rational_number)
44
+
45
+## Submitting Incomplete Solutions
46
+
47
+It's possible to submit an incomplete solution so you can see how others have completed the exercise.

+ 17
- 0
exercises/rational-numbers/build.gradle 查看文件

@@ -0,0 +1,17 @@
1
+apply plugin: "java"
2
+apply plugin: "eclipse"
3
+apply plugin: "idea"
4
+
5
+repositories {
6
+    mavenCentral()
7
+}
8
+
9
+dependencies {
10
+    testCompile "junit:junit:4.12"
11
+}
12
+test {
13
+    testLogging {
14
+        exceptionFormat = 'full'
15
+        events = ["passed", "failed", "skipped"]
16
+    }
17
+}

+ 30
- 0
exercises/rational-numbers/src/main/java/Rational.java 查看文件

@@ -0,0 +1,30 @@
1
+class Rational {
2
+
3
+    Rational(int numerator, int denominator) {
4
+        throw new UnsupportedOperationException("Delete this statement and write your own implementation.");
5
+    }
6
+
7
+    int getNumerator() {
8
+        throw new UnsupportedOperationException("Delete this statement and write your own implementation.");
9
+    }
10
+
11
+    int getDenominator() {
12
+        throw new UnsupportedOperationException("Delete this statement and write your own implementation.");
13
+    }
14
+
15
+    @Override
16
+    public String toString() {
17
+        return String.format("%d/%d", this.getNumerator(), this.getDenominator());
18
+    }
19
+
20
+    @Override
21
+    public boolean equals(Object obj) {
22
+        if (obj == null || !this.getClass().isAssignableFrom(obj.getClass())) {
23
+            return false;
24
+        }
25
+
26
+        Rational other = (Rational) obj;
27
+        return this.getNumerator() == other.getNumerator()
28
+            && this.getDenominator() == other.getDenominator();
29
+    }
30
+}

+ 288
- 0
exercises/rational-numbers/src/test/java/RationalTest.java 查看文件

@@ -0,0 +1,288 @@
1
+import org.junit.Ignore;
2
+import org.junit.Test;
3
+
4
+import static org.junit.Assert.assertEquals;
5
+
6
+public class RationalTest {
7
+
8
+    // Helper methods
9
+
10
+    private static final double DOUBLE_EQUALITY_TOLERANCE = 1e-15;
11
+
12
+    private void assertDoublesEqual(double x, double y) {
13
+        assertEquals(x, y, DOUBLE_EQUALITY_TOLERANCE);
14
+    }
15
+
16
+    // Tests
17
+
18
+    @Test
19
+    public void testAddTwoPositiveRationalNumbers() {
20
+        Rational expected = new Rational(7, 6);
21
+        Rational actual = new Rational(1, 2).add(new Rational(2, 3));
22
+        assertEquals(expected, actual);
23
+    }
24
+
25
+    @Ignore("Remove to run test")
26
+    @Test
27
+    public void testAddAPositiveRationalNumberAndANegativeRationalNumber() {
28
+        Rational expected = new Rational(-1, 6);
29
+        Rational actual = new Rational(1, 2).add(new Rational(-2, 3));
30
+        assertEquals(expected, actual);
31
+    }
32
+
33
+    @Ignore("Remove to run test")
34
+    @Test
35
+    public void testAddTwoNegativeRationalNumbers() {
36
+        Rational expected = new Rational(-7, 6);
37
+        Rational actual = new Rational(-1, 2).add(new Rational(-2, 3));
38
+        assertEquals(expected, actual);
39
+    }
40
+
41
+    @Ignore("Remove to run test")
42
+    @Test
43
+    public void testAddARationalNumberToItsAdditiveInverse() {
44
+        Rational expected = new Rational(0, 1);
45
+        Rational actual = new Rational(1, 2).add(new Rational(-1, 2));
46
+        assertEquals(expected, actual);
47
+    }
48
+
49
+    @Ignore("Remove to run test")
50
+    @Test
51
+    public void testSubtractTwoPositiveRationalNumbers() {
52
+        Rational expected = new Rational(-1, 6);
53
+        Rational actual = new Rational(1, 2).subtract(new Rational(2, 3));
54
+        assertEquals(expected, actual);
55
+    }
56
+
57
+    @Ignore("Remove to run test")
58
+    @Test
59
+    public void testSubtractAPositiveRationalNumberAndANegativeRationalNumber() {
60
+        Rational expected = new Rational(7, 6);
61
+        Rational actual = new Rational(1, 2).subtract(new Rational(-2, 3));
62
+        assertEquals(expected, actual);
63
+    }
64
+
65
+    @Ignore("Remove to run test")
66
+    @Test
67
+    public void testSubtractTwoNegativeRationalNumbers() {
68
+        Rational expected = new Rational(1, 6);
69
+        Rational actual = new Rational(-1, 2).subtract(new Rational(-2, 3));
70
+        assertEquals(expected, actual);
71
+    }
72
+
73
+    @Ignore("Remove to run test")
74
+    @Test
75
+    public void testSubtractARationalNumberFromItself() {
76
+        Rational expected = new Rational(0, 1);
77
+        Rational actual = new Rational(1, 2).subtract(new Rational(1, 2));
78
+        assertEquals(expected, actual);
79
+    }
80
+
81
+    @Ignore("Remove to run test")
82
+    @Test
83
+    public void testMultiplyTwoPositiveRationalNumbers() {
84
+        Rational expected = new Rational(1, 3);
85
+        Rational actual = new Rational(1, 2).multiply(new Rational(2, 3));
86
+        assertEquals(expected, actual);
87
+    }
88
+
89
+    @Ignore("Remove to run test")
90
+    @Test
91
+    public void testMultiplyANegativeRationalNumberByAPositiveRationalNumber() {
92
+        Rational expected = new Rational(-1, 3);
93
+        Rational actual = new Rational(-1, 2).multiply(new Rational(2, 3));
94
+        assertEquals(expected, actual);
95
+    }
96
+
97
+    @Ignore("Remove to run test")
98
+    @Test
99
+    public void testMultiplyTwoNegativeRationalNumbers() {
100
+        Rational expected = new Rational(1, 3);
101
+        Rational actual = new Rational(-1, 2).multiply(new Rational(-2, 3));
102
+        assertEquals(expected, actual);
103
+    }
104
+
105
+    @Ignore("Remove to run test")
106
+    @Test
107
+    public void testMultiplyARationalNumberByItsReciprocal() {
108
+        Rational expected = new Rational(1, 1);
109
+        Rational actual = new Rational(1, 2).multiply(new Rational(2, 1));
110
+        assertEquals(expected, actual);
111
+    }
112
+
113
+    @Ignore("Remove to run test")
114
+    @Test
115
+    public void testMultiplyARationalNumberByOne() {
116
+        Rational expected = new Rational(1, 2);
117
+        Rational actual = new Rational(1, 2).multiply(new Rational(1, 1));
118
+        assertEquals(expected, actual);
119
+    }
120
+
121
+    @Ignore("Remove to run test")
122
+    @Test
123
+    public void testMultiplyARationalNumberByZero() {
124
+        Rational expected = new Rational(0, 1);
125
+        Rational actual = new Rational(1, 2).multiply(new Rational(0, 1));
126
+        assertEquals(expected, actual);
127
+    }
128
+
129
+    @Ignore("Remove to run test")
130
+    @Test
131
+    public void testDivideTwoPositiveRationalNumbers() {
132
+        Rational expected = new Rational(3, 4);
133
+        Rational actual = new Rational(1, 2).divide(new Rational(2, 3));
134
+        assertEquals(expected, actual);
135
+    }
136
+
137
+    @Ignore("Remove to run test")
138
+    @Test
139
+    public void testDivideAPositiveRationalNumberByANegativeRationalNumber() {
140
+        Rational expected = new Rational(-3, 4);
141
+        Rational actual = new Rational(1, 2).divide(new Rational(-2, 3));
142
+        assertEquals(expected, actual);
143
+    }
144
+
145
+    @Ignore("Remove to run test")
146
+    @Test
147
+    public void testDivideTwoNegativeRationalNumbers() {
148
+        Rational expected = new Rational(3, 4);
149
+        Rational actual = new Rational(-1, 2).divide(new Rational(-2, 3));
150
+        assertEquals(expected, actual);
151
+    }
152
+
153
+    @Ignore("Remove to run test")
154
+    @Test
155
+    public void testDivideARationalNumberByOne() {
156
+        Rational expected = new Rational(1, 2);
157
+        Rational actual = new Rational(1, 2).divide(new Rational(1, 1));
158
+        assertEquals(expected, actual);
159
+    }
160
+
161
+    @Ignore("Remove to run test")
162
+    @Test
163
+    public void testAbsoluteValueOfAPositiveRationalNumber() {
164
+        Rational expected = new Rational(1, 2);
165
+        Rational actual = new Rational(1, 2).abs();
166
+        assertEquals(expected, actual);
167
+    }
168
+
169
+    @Ignore("Remove to run test")
170
+    @Test
171
+    public void testAbsoluteValueOfANegativeRationalNumber() {
172
+        Rational expected = new Rational(1, 2);
173
+        Rational actual = new Rational(-1, 2).abs();
174
+        assertEquals(expected, actual);
175
+    }
176
+
177
+    @Ignore("Remove to run test")
178
+    @Test
179
+    public void testAbsoluteValueOfZero() {
180
+        Rational expected = new Rational(0, 1);
181
+        Rational actual = new Rational(0, 1).abs();
182
+        assertEquals(expected, actual);
183
+    }
184
+
185
+    @Ignore("Remove to run test")
186
+    @Test
187
+    public void testRaiseAPositiveRationalNumberToAPositiveIntegerPower() {
188
+        Rational expected = new Rational(1, 8);
189
+        Rational actual = new Rational(1, 2).pow(3);
190
+        assertEquals(expected, actual);
191
+    }
192
+
193
+    @Ignore("Remove to run test")
194
+    @Test
195
+    public void testRaiseANegativeRationalNumberToAPositiveIntegerPower() {
196
+        Rational expected = new Rational(-1, 8);
197
+        Rational actual = new Rational(-1, 2).pow(3);
198
+        assertEquals(expected, actual);
199
+    }
200
+
201
+    @Ignore("Remove to run test")
202
+    @Test
203
+    public void testRaiseZeroToAnIntegerPower() {
204
+        Rational expected = new Rational(0, 1);
205
+        Rational actual = new Rational(0, 1).pow(5);
206
+        assertEquals(expected, actual);
207
+    }
208
+
209
+    @Ignore("Remove to run test")
210
+    @Test
211
+    public void testRaiseOneToAnIntegerPower() {
212
+        Rational expected = new Rational(1, 1);
213
+        Rational actual = new Rational(1, 1).pow(4);
214
+        assertEquals(expected, actual);
215
+    }
216
+
217
+    @Ignore("Remove to run test")
218
+    @Test
219
+    public void testRaiseAPositiveRationalNumberToThePowerOfZero() {
220
+        Rational expected = new Rational(1, 1);
221
+        Rational actual = new Rational(-1, 2).pow(0);
222
+        assertEquals(expected, actual);
223
+    }
224
+
225
+    @Ignore("Remove to run test")
226
+    @Test
227
+    public void testRaiseARealNumberToAPositiveRationalNumber() {
228
+        double expected = 16.0;
229
+        double actual = new Rational(4, 3).exp(8.0);
230
+        assertDoublesEqual(expected, actual);
231
+    }
232
+
233
+    @Ignore("Remove to run test")
234
+    @Test
235
+    public void testRaiseARealNumberToANegativeRationalNumber() {
236
+        double expected = 1.0 / 3;
237
+        double actual = new Rational(-1, 2).exp(9);
238
+        assertDoublesEqual(expected, actual);
239
+    }
240
+
241
+    @Ignore("Remove to run test")
242
+    @Test
243
+    public void testReduceAPositiveRationalNumberToLowestTerms() {
244
+        Rational expected = new Rational(1, 2);
245
+        Rational actual = new Rational(2, 4);
246
+        assertEquals(expected, actual);
247
+    }
248
+
249
+    @Ignore("Remove to run test")
250
+    @Test
251
+    public void testReduceANegativeRationalNumberToLowestTerms() {
252
+        Rational expected = new Rational(-2, 3);
253
+        Rational actual = new Rational(-4, 6);
254
+        assertEquals(expected, actual);
255
+    }
256
+
257
+    @Ignore("Remove to run test")
258
+    @Test
259
+    public void testReduceARationalNumberWithANegativeDenominatorToLowestTerms() {
260
+        Rational expected = new Rational(-1, 3);
261
+        Rational actual = new Rational(3, -9);
262
+        assertEquals(expected, actual);
263
+    }
264
+
265
+    @Ignore("Remove to run test")
266
+    @Test
267
+    public void testReduceZeroToLowestTerms() {
268
+        Rational expected = new Rational(0, 1);
269
+        Rational actual = new Rational(0, 6);
270
+        assertEquals(expected, actual);
271
+    }
272
+
273
+    @Ignore("Remove to run test")
274
+    @Test
275
+    public void testReduceAnIntegerToLowestTerms() {
276
+        Rational expected = new Rational(-2, 1);
277
+        Rational actual = new Rational(-14, 7);
278
+        assertEquals(expected, actual);
279
+    }
280
+
281
+    @Ignore("Remove to run test")
282
+    @Test
283
+    public void testReduceOneToLowestTerms() {
284
+        Rational expected = new Rational(1, 1);
285
+        Rational actual = new Rational(13, 13);
286
+        assertEquals(expected, actual);
287
+    }
288
+}

+ 1
- 0
exercises/settings.gradle 查看文件

@@ -64,6 +64,7 @@ include 'pythagorean-triplet'
64 64
 include 'queen-attack'
65 65
 include 'rail-fence-cipher'
66 66
 include 'raindrops'
67
+include 'rational-numbers'
67 68
 include 'rectangles'
68 69
 include 'reverse-string'
69 70
 include 'rna-transcription'