1
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
package edu.caltech.cs2.datastructures;
import edu.caltech.cs2.helpers.*;
import edu.caltech.cs2.interfaces.ICollection;
import edu.caltech.cs2.interfaces.IDictionary;
import edu.caltech.cs2.interfaces.IStyleTests;
import edu.caltech.cs2.textgenerator.NGram;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import static edu.caltech.cs2.project05.Project05TestOrdering.classSpecificTestLevel;
import static edu.caltech.cs2.project05.Project05TestOrdering.specialTestLevel;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.jupiter.api.Assertions.*;
@TestClassOrder(ClassOrderer.OrderAnnotation.class)
@Order(0)
@Tag("B")
@ExtendWith(TestExtension.class)
public class MoveToFrontDictionaryTests extends IDictionaryNGramTests {
private static String DICTIONARY_SOURCE = "src/edu/caltech/cs2/datastructures/MoveToFrontDictionary.java";
public int CONSTANT_TIMEOUT_MS = 50; // was 15, have heard reports of ~40.
public int SINGLE_OP_TIMEOUT_MS() {
return 100;
}
public int CONTAINS_VALUE_TIMEOUT_MS() {
return 100;
}
@Override
public RuntimeInstrumentation.ComplexityType getAndPutComplexity() {
return RuntimeInstrumentation.ComplexityType.LINEAR;
}
@Override
public RuntimeInstrumentation.ComplexityType getAndPutComplexityWorst() {
return RuntimeInstrumentation.ComplexityType.LINEAR;
}
@Override
public RuntimeInstrumentation.ComplexityType getAndPutComplexityBest() {
return RuntimeInstrumentation.ComplexityType.CONSTANT;
}
@DisplayName("Style")
@Nested
@Order(100)
@Tag("C")
class StyleTests implements IStyleTests {
@Override
public String getSource() {
return DICTIONARY_SOURCE;
}
@Override
public Class<?> getClazz() {
return MoveToFrontDictionary.class;
}
@Override
public List<String> getPublicInterface() {
return List.of("containsKey", "containsValue", "get", "iterator", "keys", "put", "remove", "size", "values", "toString");
}
@Override
public int getMaxFields() {
return 3;
}
@Override
public List<String> methodsToBanSelf() {
return List.of("put", "get", "remove", "keys", "values");
}
}
@DisplayName("Runtime Complexity (best case, int keys)")
@Nested
class RuntimeTestsBest {
@Order(specialTestLevel)
@DisplayName("Test get() best case complexity with int keys")
@DependsOn({"put", "get"})
@Test
@Timeout(value = 20, unit = SECONDS)
public void testBestCase() {
Function<Integer, IDictionary<Object, Object>> provide = (Integer numElements) -> {
IDictionary<Object, Object> t = newIDictionary();
for (int i = 0; i < numElements - 4; i++) {
t.put(i, 0);
}
return t;
};
BiConsumer<Integer, IDictionary<Object, Object>> get = (Integer size, IDictionary<Object, Object> t) -> {
t.get(size - 5);
};
RuntimeInstrumentation.assertAtMost("get", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, get, 8);
}
@Order(specialTestLevel)
@DisplayName("Test put() best case complexity with int keys")
@DependsOn({"put"})
@Test
@Timeout(value = 20, unit = SECONDS)
public void testBestCasePut() {
Function<Integer, IDictionary<Object, Object>> provide = (Integer numElements) -> {
IDictionary<Object, Object> t = newIDictionary();
for (int i = 0; i < numElements - 4; i++) {
t.put(i, 0);
}
return t;
};
BiConsumer<Integer, IDictionary<Object, Object>> put = (Integer size, IDictionary<Object, Object> t) -> {
t.put(size - 5, 0);
};
RuntimeInstrumentation.assertAtMost("put", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, put, 8);
}
}
// No restrictions on key type for MoveToFront
@Override
public IDictionary<Object, Object> newIDictionary() {
return new MoveToFrontDictionary<>();
}
@DisplayName("Implementation")
@Order(0)
@Nested
class ImplementationTests {
@Order(classSpecificTestLevel)
@DisplayName("Check MoveToFrontDictionary class is properly implemented")
@TestDescription("This test makes sure that the implementation of the MoveToFront dictionary, i.e. like having the right fields")
@TestHint("MoveToFrontDictionary should only have a head node and an int field to store size. It should also implement a Node class.")
@DependsOn({"fields", "node class"})
@Test
public void checkMTF() {
MoveToFrontChecker.checkClass(MoveToFrontDictionary.class);
}
@Order(classSpecificTestLevel)
@DisplayName("Check for excessive node allocation in put")
@TestDescription("This test checks that no extra nodes are allocated in the put() method")
@DependsOn({"put"})
@Test
public void testForExcessiveNodeAllocationPut() {
NewNode.MoveToFrontDictionary_NUM_CALLS = 0;
MoveToFrontDictionary<Integer, Integer> impl = new MoveToFrontDictionary<>();
for (int j = 0; j < 100; j++) {
int before = NewNode.MoveToFrontDictionary_NUM_CALLS;
impl.put(j, j);
int after = NewNode.MoveToFrontDictionary_NUM_CALLS;
assertTrue(before + 1 >= after, "Each put() should create at most one new node");
}
}
@Order(classSpecificTestLevel)
@DisplayName("Check for excessive node allocation in get")
@TestDescription("This test checks that no extra nodes are allocated in the get() method")
@DependsOn({"put", "get"})
@TestHint("get should not be affecting the dictionary in any way; it should simply find and return a value.")
@Test
public void testForExcessiveNodeAllocationGet() {
NewNode.MoveToFrontDictionary_NUM_CALLS = 0;
MoveToFrontDictionary<Integer, Integer> impl = new MoveToFrontDictionary<>();
for (int j = 0; j < 100; j++) {
impl.put(j, j);
}
for (int j = 0; j < 100; j++) {
int before = NewNode.MoveToFrontDictionary_NUM_CALLS;
impl.get(j);
int after = NewNode.MoveToFrontDictionary_NUM_CALLS;
assertEquals(before, after, "get() should not allocate any new node");
}
}
@Order(classSpecificTestLevel)
@DisplayName("Check for excessive Node allocation in remove")
@TestDescription("This test checks that no extra nodes are allocated in the remove() method")
@DependsOn({"put", "remove"})
@Test
public void testForExcessiveNodeAllocationRemove() {
NewObjectArray.NUM_CALLS = 0;
MoveToFrontDictionary<Integer, Integer> impl = new MoveToFrontDictionary<>();
for (int j = 0; j < 100; j++) {
impl.put(j, j);
}
for (int j = 0; j < 100; j++) {
int before = NewNode.MoveToFrontDictionary_NUM_CALLS;
impl.remove(j);
int after = NewNode.MoveToFrontDictionary_NUM_CALLS;
assertEquals(before, after, "remove() should not allocate any new node");
}
}
@Test
@DisplayName("Sanity check that accessing keys in various locations in the dictionary works")
@TestDescription("This test tries to obtain and remove data from various parts of the dictionary (front, back) to make sure its in the data structure")
@DependsOn({"put", "get", "remove"})
@Order(classSpecificTestLevel)
public void testDataLocations() {
MoveToFrontDictionary<Integer, Integer> impl = new MoveToFrontDictionary<>();
HashMap<Integer, Integer> ref = new HashMap<>();
for (int i = 0; i < 10; i++) {
impl.put(i, i);
ref.put(i, i);
}
// Check access of element at front
assertEquals(ref.get(9), impl.get(9), "Getting an element from the front failed.");
// Check accessing whatever element is at the back
for (int i = 0; i < 10; i++) {
assertEquals(ref.get(i), impl.get(i), "Getting an element from the back returns incorrect result.");
assertEquals(ref.get(i), impl.get(i), "Key that was just gotten is now missing.");
}
// Check removing element at the front
for (int i = 9; i >= 0; i--) {
assertEquals(ref.remove(i), impl.remove(i), "Removing an element from the front failed.");
}
// Repopulate to make sure that emptying it didn't bork it
for (int i = 0; i < 10; i++) {
impl.put(i, i);
ref.put(i, i);
}
// And repeat.
assertEquals(ref.get(9), impl.get(9));
for (int i = 0; i < 10; i++) {
assertEquals(ref.get(i), impl.get(i), "Getting an element from the back failed.");
}
}
@Test
@DisplayName("Test that referencing a key moves it to the front")
@TestDescription("This test is checking the fundamental principle of the MoveToFrontDictionary - that when you access a key, it does get moved to the front")
@TestHint("Make sure to move to front in both containsKey and get")
@DependsOn({"put", "containsKey", "get"})
@Order(specialTestLevel)
public void testMoveToFrontProperty() {
MoveToFrontDictionary<Integer, Integer> impl = new MoveToFrontDictionary<>();
final int DICT_SIZE = 30000;
for (int i = 0; i <= DICT_SIZE; i++) {
impl.put(i, i);
}
double totalTimeRetrieveFront = 0;
long startTime, endTime;
for (int i = 0; i <= DICT_SIZE; i++) {
// Get key from back to move to front
impl.containsKey(i);
// Clock retrieval of key from front.
startTime = System.nanoTime();
impl.get(i);
endTime = System.nanoTime();
totalTimeRetrieveFront += (endTime - startTime);
}
assertTrue(CONSTANT_TIMEOUT_MS > totalTimeRetrieveFront / 1000000,
"get(key) after calling containsKey(key) takes too long.");
totalTimeRetrieveFront = 0;
for (int i = 0; i <= DICT_SIZE; i++) {
// Get key from back to move to front
impl.get(i);
// Clock retrieval of key from front.
startTime = System.nanoTime();
impl.get(i);
endTime = System.nanoTime();
totalTimeRetrieveFront += (endTime - startTime);
}
assertTrue(CONSTANT_TIMEOUT_MS > totalTimeRetrieveFront / 1000000,
"get(key) after calling get(key) takes too long.");
}
@Order(specialTestLevel)
@DisplayName("Test removing from the front has the desired behavior")
@TestDescription("This test makes sure that removing from the front updates the head and the rest of the linked list correctly")
@TestHint("Make sure you update your head field correctly in removal, and that if a key is removed, the MoveToFrontDictionary is able to recognize and handle that")
@DependsOn({"put", "get", "remove", "values"})
@Test
public void testFrontRemove() {
MoveToFrontDictionary<Integer, Integer> impl = new MoveToFrontDictionary<>();
final int DICT_SIZE = 1000;
for (int i = 0; i <= DICT_SIZE; i++) {
impl.put(i, i);
}
Class nodeClass = MoveToFrontChecker.getNodeClass(MoveToFrontDictionary.class);
Field head = null;
for (Field field : MoveToFrontDictionary.class.getDeclaredFields()) {
if (field.getType().equals(nodeClass)) {
field.setAccessible(true);
head = field;
break;
}
}
assertNotNull(head, "There is no head field in MoveToFrontDictionary");
for (int i = DICT_SIZE; i >= 0; i--) {
assertEquals(i, impl.get(i), "Getting a key does not return the correct value");
assertEquals(i, impl.remove(i), "Removing a key does not return the correct value");
try {
if (i != 0) {
assertNotNull(head.get(impl), "Removing from front leaves a null head pointer");
} else {
assertNull(head.get(impl), "Removing last key does not set head pointer to null");
}
ICollection<Integer> values = impl.values();
assertNull(impl.get(i), "Getting a removed key does not return null");
assertIterableEquals(values, impl.values(), "Getting a removed key changes the value list");
if (i != 0) {
assertNotNull(head.get(impl), "Getting a removed key leaves a null head pointer");
} else {
assertNull(head.get(impl), "Getting the last key does leave a null head pointer");
}
} catch (IllegalAccessException ex) {
fail("There is no head field in MoveToFrontDictionary");
}
}
}
}
}