Skip to content

Commit 0694ed4

Browse files
committed
Merge branch 'master' into branch-coverage-#6
2 parents 2ce5658 + 5919a43 commit 0694ed4

File tree

4 files changed

+372
-5
lines changed

4 files changed

+372
-5
lines changed

Diff for: report.md

+230-1
Original file line numberDiff line numberDiff line change
@@ -144,14 +144,65 @@ It is stated that `replacementNode` can be null and that `nodeToRemoved` can't b
144144
Could be stated in the documentation that if the root of the BST is null the function simply returns an empty array. Shouldn't have to browse through the code to find this fact. Although it is stated that the input `order` determines how the array is sorted, this could be explained in more detail.
145145

146146

147+
#### @Kubha99
148+
| Function | nLOC | `lizard` CCN | Manual CCN |
149+
| -------- | ---: | ---------: | ---------: |
150+
| `RedBlackTree.balanceAfterDelete` | 72 | 22 | 21 |
151+
| `RedBlackTree.balanceAfterInsert` | 46 | 18 | 15 |
147152

153+
##### 1.
148154

155+
Like @psalqvist, I set CCN=2 and for each `if`, `for`, `while`, `else if`, `&&` and `||` (not for `else`) i added 1 and for each return i subtracted with 1. There was a slight difference between the results computed from manual count and the counting from lizard tool.
149156

157+
##### 2.
158+
There does exist a similarity between the length of the function and the complexity for it.
150159

151-
#### @Kubha99
160+
##### 3.
161+
###### balanceAfterInsert
162+
Has input of `RedBlackNode` as input and balances the given tree, this function is called once we insert a node in the tree
163+
164+
###### balanceAfterDelete
165+
Similar to `balanceAfterInsert` this function takes a `RedBlackNode` and balances the tree after a delete
166+
167+
##### 4.
168+
In `balanceAfterDelete` there exists a case where an execption needs to be handled.
169+
170+
##### 5.
171+
Both of the functions are well documented and there exists line comments explaining what operations are done and sometimes why.
172+
There also exists documentation for input as well as output.
152173

153174
#### @ekorre1001
154175

176+
| Function | nLOC | `lizard` CCN | Manual CCN |
177+
| -------- | ---: | ---------: | ---------: |
178+
| `BTree.validateNode` | 59 | 22 | 6 |
179+
| `Multiplication.multiplyUsingFFT` | 68 | 21 | 18 |
180+
181+
##### 1.
182+
183+
Like @psalqvist, we start with CCN = 2 then add 1 for `if`, `for`, `while`, `else if`, `&&` and `||` (not for `else`), and subtract 1 to CCN when reaching a `return` statement. The result shows a lower manual CCN. It is because that in both functions there are more than 1 return point, especially in the `BTree.validateNode`, almost every `if` statement is followed by a `return` code.
184+
185+
##### 2.
186+
Looking at the CCN result from lizard it seems like NLOC correlates with CCN. The manual count for `Multiplication.multiplyUsingLogs` also indicates that.
187+
188+
##### 3.
189+
###### validateNode
190+
Takes a `node` object as input and validates the node according to the B-Tree invariants. Returns `True` if valid, else `false`.
191+
192+
###### multiplyUsingFFT
193+
Takes two `string` objects as input and extract the numbers, then multiply the two numbers using Fast Fourier transform method. Returns the result as a `string`.
194+
195+
##### 4.
196+
There are no exceptions in either of the functions.
197+
198+
##### 5.
199+
###### validateNode
200+
This function is well documented since there are many comments that explain what some part of the function does. It is also clear what the input and output are.
201+
202+
###### multiplyUsingFFT
203+
There are no comments in the code which makes it harder for the reader to know what each part does. However, if you are familiar with the FFT method it can be intuitive.
204+
205+
155206

156207

157208
## Refactoring
@@ -164,6 +215,121 @@ Carried out refactoring (optional, P+):
164215

165216
git diff ...
166217

218+
219+
### @nolanderc: `BinaryHeapArray.heapDown`
220+
221+
The main goal of refactoring should be to reduce the amount of duplication in the `if` statements. For example, there is one that looks like this:
222+
```java
223+
if ((type == Type.MIN && left != null && right != null && value.compareTo(left) > 0 && value.compareTo(right) > 0)
224+
|| (type == Type.MAX && left != null && right != null && value.compareTo(left) < 0 && value.compareTo(right) < 0) {
225+
...
226+
}
227+
```
228+
Here the checks for `left != null` and `right != null` are duplicated twice each. Also the comparisons against `value` are repeated twice, but with different operators (`<` and `>`). This pattern is repeated an additional 2-4 times, depending on how you count.
229+
230+
We can reduce the cyclomatic complexity of this code by restructuring the code so that each check for null only happens once, and which comparison to do against the value is determined by the `type` only a single time. Applying these changes results in something the following:
231+
232+
```java
233+
// determine the order we want nodes in once
234+
int desiredOrder = (type == Type.MIN) ? -1 : 1;
235+
int undesiredOrder = -desiredOrder;
236+
237+
// Do checks against null and perform comparisons against the parent value.
238+
// If their order does not match the desired, we need to swap them.
239+
boolean leftShouldSwap = left != null && Integer.signum(value.compareTo(left)) == undesiredOrder;
240+
boolean rightShouldSwap = right != null && Integer.signum(value.compareTo(right)) == undesiredOrder;
241+
242+
// handle different cases depending on which child needs to swap with the parent
243+
if (leftShouldSwap & rightShouldSwap) {
244+
// if both need to swap, make sure that swapping preserves the desired order
245+
return (Integer.signum(left.compareTo(right)) == desiredOrder) ? leftIndex : rightIndex;
246+
} else if (leftShouldSwap) {
247+
return leftIndex;
248+
} else if (rightShouldSwap) {
249+
return rightIndex;
250+
} else {
251+
return -1;
252+
}
253+
```
254+
255+
This is essentially everything the old 46 nLOC function did, but with multiple levels of nested `if` statements and convuluted logic. In the end, the new version uses two functions with a cyclomatic complexity of 2 and 10, respectively. Compare this to the old version which had a cyclomatic complexity of 41.
256+
257+
For a full diff, run the following:
258+
```sh
259+
git show 9b4599289de7c734e4cd7364ce8535fc7d32be90
260+
```
261+
262+
### @ekorre1001: `Multiplication.multiplyUsingFFT`
263+
264+
To improve the cyclomatic complexity we want to either remove or reduce the use of `if`, `for`, `while`, `else if`, `&&` and `||`. In the multiplyUsingFFT we can find a lot of if statements used to investigate both of the input numbers since they are strings. For instance the following is used to check whether the product is negative.
265+
266+
```java
267+
if ((a.charAt(0) == '-' && b.charAt(0) != '-') || (a.charAt(0) != '-' && b.charAt(0) == '-')) {
268+
negative = true;
269+
}
270+
```
271+
This can be improved by converting both of the strings to integers and check if the product is negative or not.
272+
273+
```java
274+
int x = Integer.parseInt(a);
275+
int y = Integer.parseInt(b);
276+
if(x*y < 0) negative = true;
277+
```
278+
By doing this we have reduced the cyclomatic complexity but the drawback is that we initiate new variables and we are dependent on another library.
279+
280+
Lastly, we can remove the use of some commonly used operations by promoting them to a function. For instance, since both strings are edited to remove the minus symbol (the following code), we can reduce the complexity by adding a helper function that gets called for each string. The drawback is of course those additional functions.
281+
```java
282+
if (a.charAt(0) == '-') {
283+
a = a.substring(1);
284+
}
285+
if (b.charAt(0) == '-') {
286+
b = b.substring(1);
287+
}
288+
```
289+
290+
In the end, we managed to reduce the CCN from 21 to 13.
291+
292+
Git diff: Check the refactor section [here](https://docs.google.com/document/d/1qRhKoisnicSaKS3oRQEs6EaFpCoqO1QLV4kNYcLeAFo/edit?usp=sharing).
293+
294+
### `BTree.validateNode`
295+
296+
As with the previous function we aim to reduce the use of `if`, `for`, `while`, `else if`, `&&` and `||`. In this case a lot of if statements were used to check a specific condition and directly followed by a `return`. For example the following:
297+
298+
```java
299+
if (keySize < minKeySize) {
300+
return false;
301+
} else if (keySize > maxKeySize) {
302+
return false;
303+
} else if (childrenSize == 0) {
304+
return true;
305+
} else if (keySize != (childrenSize - 1)) {
306+
return false;
307+
} else if (childrenSize < minChildrenSize) {
308+
return false;
309+
} else if (childrenSize > maxChildrenSize) {
310+
return false;
311+
}
312+
```
313+
314+
This can be reduced by creating a helper function that does the same thing and then we just need to check the value returned by the helper function. The code above can be replaced with the following:
315+
316+
```java
317+
// make the check in another function and save the result
318+
int checkNonRoot = validateNonRootHelper(keySize, childrenSize);
319+
// return the corresponding boolean
320+
if (checkNonRoot == 0) {
321+
return false;
322+
} else if (checkNonRoot == 1) {
323+
return true;
324+
}
325+
```
326+
The drawback is that we need to add a few additional functions and variables.
327+
328+
In the end, we managed to reduce the CCN from 22 to 14.
329+
330+
Git diff: Check the refactor section [here](https://docs.google.com/document/d/1qRhKoisnicSaKS3oRQEs6EaFpCoqO1QLV4kNYcLeAFo/edit?usp=sharing).
331+
332+
167333
## Coverage
168334

169335
### Tools
@@ -173,6 +339,12 @@ Document your experience in using a "new"/different coverage tool.
173339
How well was the tool documented? Was it possible/easy/difficult to
174340
integrate it with your build environment?
175341

342+
### NOTES
343+
344+
#### @ekorre1001
345+
346+
The code coverage tool I am using is OpenClover which works with the Ant build tool. I followed the provided quick start guide and managed to integrate it with Ant. It was quite easy to do the setup since only a few steps were required, although a few things did not work out initially.
347+
176348
### Your own coverage tool
177349

178350
Show a patch (or link to a branch) that shows the instrumented code to
@@ -186,6 +358,20 @@ git diff ...
186358
What kinds of constructs does your tool support, and how accurate is
187359
its output?
188360

361+
#### @nolanderc
362+
363+
See `git show 9ef482092816b3367f4d4b45214821ee11019fe3`
364+
365+
It supports `if` statements, `while` loops and `for` loops. The output should
366+
show exactly how many times a code block was executed, and also notify if a code
367+
block has not been executed. Example:
368+
369+
```
370+
BranchCoverageTest.branchCoverageWithNotReached:
371+
- entry point: 1
372+
- not reached: 0 <-- NOT REACHED
373+
```
374+
189375
### Evaluation
190376

191377
1. How detailed is your coverage measurement?
@@ -208,6 +394,49 @@ git diff ...
208394

209395
Number of test cases added: two per team member (P) or at least four (P+).
210396

397+
### NOTES
398+
399+
400+
#### @ekorre1001
401+
402+
Changed to another function because:
403+
404+
BTree::validateNode: Barely any test for the data structure and no test for this specific function which makes it harder to understand.
405+
406+
Multiplication::multiplyUsingFFT: Above 98% code coverage
407+
408+
The requirements + report can be found [here](https://docs.google.com/document/d/1qRhKoisnicSaKS3oRQEs6EaFpCoqO1QLV4kNYcLeAFo/edit?usp=sharing)
409+
410+
5 Test cases added and can be found in the PrimeTest.java
411+
412+
413+
#### @nolanderc
414+
415+
`BinaryHeapArray.heapDown` before: 94.1%, after: 100.0%.
416+
417+
Branch coverage report: https://docs.google.com/document/d/1IVLQTIkl8IiemVskQ_JFw1sNe5EBXXEWpTxYIQ4-NV0/edit?usp=sharing
418+
419+
For new test cases, see:
420+
421+
```sh
422+
git show 395749c08a93e8b8a063e87849e44543fe1189ac
423+
```
424+
425+
Since the `heapDown` function already had such high branch coverage I also wrote
426+
tests for `BinaryHeapArray.validateNode`:
427+
428+
`BinaryHeapArray.validateNode` before: 57.7%, after: 100.0%.
429+
430+
```sh
431+
git show 8e6d8c7d8d36417133fe66f2c8d80a153d23b3f9
432+
```
433+
434+
While writing these tests, I also found a bug in the source code. The fix can be seen here:
435+
436+
```sh
437+
git show 12d42662a86bc770915c15f5d2d24d877b107c06
438+
```
439+
211440
## Self-assessment: Way of working
212441

213442
Current state according to the Essence standard: ...

Diff for: src/com/jwetherell/algorithms/data_structures/BinaryHeap.java

+16-4
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,16 @@ public BinaryHeapArray(Type type) {
106106
this.type = type;
107107
}
108108

109+
/**
110+
* Create a heap from an already "heapified" array.
111+
* If this is not the case (ie. the array is in arbitrary order, behaviour is undefined).
112+
* @param items The heapified array
113+
*/
114+
public void setArrayUnsafe(T[] items) {
115+
this.size = items.length;
116+
this.array = Arrays.copyOf(items, Integer.max(items.length, MINIMUM_SIZE));
117+
}
118+
109119
/**
110120
* {@inheritDoc}
111121
*/
@@ -305,17 +315,19 @@ private boolean validateNode(int index) {
305315
T left = this.array[leftIndex];
306316
if ((type == Type.MIN && value.compareTo(left) < 0)
307317
|| (type == Type.MAX && value.compareTo(left) > 0)) {
308-
return validateNode(leftIndex);
318+
if (!validateNode(leftIndex)) return false;
319+
} else {
320+
return false;
309321
}
310-
return false;
311322
}
312323
if (rightIndex != Integer.MIN_VALUE && rightIndex < size) {
313324
T right = this.array[rightIndex];
314325
if ((type == Type.MIN && value.compareTo(right) < 0)
315326
|| (type == Type.MAX && value.compareTo(right) > 0)) {
316-
return validateNode(rightIndex);
327+
if (!validateNode(rightIndex)) return false;
328+
} else {
329+
return false;
317330
}
318-
return false;
319331
}
320332

321333
return true;

Diff for: test/com/jwetherell/algorithms/data_structures/test/BinaryHeapTests.java

+60
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.jwetherell.algorithms.data_structures.test;
22

33
import static org.junit.Assert.assertTrue;
4+
import static org.junit.Assert.assertEquals;
5+
import static org.junit.Assert.assertFalse;
46
import static org.junit.Assert.assertNull;
57

68
import java.util.Collection;
@@ -81,4 +83,62 @@ public void testMaxHeap() {
8183
tHeapNull.clear();
8284
assertNull(tHeapNull.getHeadValue()); // we expect null here
8385
}
86+
87+
// Exercices case in `BinaryHeapArray.heapDown` when the parent is less than
88+
// both its children, but the children are equal
89+
@Test
90+
public void testHeapDownChildrenLessButEqualMax() {
91+
BinaryHeap.BinaryHeapArray<Integer> aMaxHeap = new BinaryHeap.BinaryHeapArray<Integer>(BinaryHeap.Type.MAX);
92+
aMaxHeap.add(7);
93+
aMaxHeap.add(3);
94+
aMaxHeap.add(3);
95+
aMaxHeap.add(2);
96+
assertEquals(7, (int)aMaxHeap.removeHead());
97+
assertEquals(3, (int)aMaxHeap.removeHead());
98+
assertEquals(3, (int)aMaxHeap.removeHead());
99+
assertEquals(2, (int)aMaxHeap.removeHead());
100+
assertNull(aMaxHeap.removeHead());
101+
}
102+
103+
// Exercices case in `BinaryHeapArray.heapDown` when the parent is greater than
104+
// both its children, but the children are equal
105+
@Test
106+
public void testHeapDownChildrenLessButEqualMin() {
107+
BinaryHeap.BinaryHeapArray<Integer> aMinHeap = new BinaryHeap.BinaryHeapArray<Integer>(BinaryHeap.Type.MIN);
108+
aMinHeap.add(2);
109+
aMinHeap.add(3);
110+
aMinHeap.add(3);
111+
aMinHeap.add(7);
112+
assertEquals(2, (int)aMinHeap.removeHead());
113+
assertEquals(3, (int)aMinHeap.removeHead());
114+
assertEquals(3, (int)aMinHeap.removeHead());
115+
assertEquals(7, (int)aMinHeap.removeHead());
116+
assertNull(aMinHeap.removeHead());
117+
}
118+
119+
@Test
120+
public void testValidateNodeValid() {
121+
BinaryHeap.BinaryHeapArray<Integer> heap = new BinaryHeap.BinaryHeapArray<Integer>(BinaryHeap.Type.MAX);
122+
heap.setArrayUnsafe(new Integer[]{ 3, 2, 1 });
123+
assertTrue(heap.validate());
124+
}
125+
126+
// Improves branch coverage in `validateNode` from 57.7% to 65.4%
127+
@Test
128+
public void testValidateNodeMaxInvalidLeft() {
129+
BinaryHeap.BinaryHeapArray<Integer> heap = new BinaryHeap.BinaryHeapArray<Integer>(BinaryHeap.Type.MAX);
130+
131+
// An invalid heap (wrong ordering) should be flagged as invalid
132+
heap.setArrayUnsafe(new Integer[]{ 3, 4, 1 });
133+
assertFalse(heap.validate());
134+
}
135+
136+
// Improves branch coverage in `validateNode` from 65.4% to 81.2%
137+
@Test
138+
public void testValidateNodeMaxInvalidRight() {
139+
BinaryHeap.BinaryHeapArray<Integer> heap = new BinaryHeap.BinaryHeapArray<Integer>(BinaryHeap.Type.MAX);
140+
// Check that invalid order is checked for right child as well
141+
heap.setArrayUnsafe(new Integer[]{ 3, 2, 4 });
142+
assertFalse(heap.validate());
143+
}
84144
}

0 commit comments

Comments
 (0)