| 6 | |
| 7 | {{{ |
| 8 | #!cobra |
| 9 | |
| 10 | class MruList<of T> implements ICollection<of T> |
| 11 | """ |
| 12 | Store a "most recently used" list of items. |
| 13 | Calling .add puts the item at the front of the list. |
| 14 | Also, .add will not grow the list when it already contains a given item. |
| 15 | An MruList is enumerable, so you can use for loops on it, .toList, .numbered, .reversed, etc. |
| 16 | |
| 17 | Because an MruList uses a dictionary internally for some caching, the item T should implement |
| 18 | both .equals and .getHashCode. |
| 19 | |
| 20 | to-do: support a max capacity |
| 21 | """ |
| 22 | |
| 23 | var _list = LinkedList<of T>() |
| 24 | var _dict = Dictionary<of T, LinkedListNode<of T>>() |
| 25 | |
| 26 | def add(item as T) |
| 27 | if _list.count <= 4 # to-do: test what the real switchover should be |
| 28 | _list.remove(item) |
| 29 | else |
| 30 | lln as LinkedListNode<of T>? |
| 31 | if _dict.tryGetValue(item, out lln), _list.remove(lln) |
| 32 | _dict[item] = _list.addFirst(item) to ! |
| 33 | assert _list.count == _dict.count |
| 34 | |
| 35 | def clear |
| 36 | _list.clear |
| 37 | _dict.clear |
| 38 | assert _list.count == _dict.count |
| 39 | |
| 40 | def contains(item as T) as bool |
| 41 | return _list.contains(item) |
| 42 | |
| 43 | def copyTo(array as T[], arrayIndex as int) |
| 44 | _list.copyTo(array, arrayIndex) |
| 45 | |
| 46 | get count as int |
| 47 | return _list.count |
| 48 | |
| 49 | def getEnumerator as IEnumerator<of T> |
| 50 | return _list.getEnumerator |
| 51 | |
| 52 | def getEnumerator as System.Collections.IEnumerator |
| 53 | implements System.Collections.IEnumerable |
| 54 | return .getEnumerator |
| 55 | |
| 56 | get isReadOnly as bool |
| 57 | return false |
| 58 | |
| 59 | def remove(item as T) as bool |
| 60 | _dict.remove(item) |
| 61 | r = _list.remove(item) |
| 62 | assert _list.count == _dict.count |
| 63 | return r |
| 64 | |
| 65 | def set(items as IList<of T>) |
| 66 | """ |
| 67 | Set all the items in the MRU list. |
| 68 | There is no reversal of the items like if you called .add for each item. |
| 69 | Repeated items are forbidden and will cause an exception. |
| 70 | """ |
| 71 | .clear |
| 72 | for item in items, _dict[item] = _list.addLast(item) to ! |
| 73 | trace _dict.count, _list.count |
| 74 | if _dict.count <> _list.count, throw InvalidOperationException('repeated items in set list') |
| 75 | |
| 76 | |
| 77 | class TestMruList |
| 78 | |
| 79 | test |
| 80 | ml = MruList<of String>() |
| 81 | assert ml.count == 0 |
| 82 | ml.add('foo') |
| 83 | assert ml.count == 1 |
| 84 | assert ml.contains('foo') |
| 85 | assert not ml.contains('bar') |
| 86 | assert ml.toList == ['foo'] |
| 87 | ml.clear |
| 88 | assert ml.count == 0 |
| 89 | |
| 90 | ml = MruList<of String>() |
| 91 | ml.add('foo') |
| 92 | ml.add('bar') |
| 93 | assert ml.contains('foo') |
| 94 | assert ml.contains('bar') |
| 95 | assert ml.toList == ['bar', 'foo'] # note the mru order |
| 96 | ml.add('bar') |
| 97 | assert ml.toList == ['bar', 'foo'] |
| 98 | ml.add('bar') |
| 99 | assert ml.toList == ['bar', 'foo'] |
| 100 | ml.add('foo') |
| 101 | assert ml.toList == ['foo', 'bar'] |
| 102 | ml.add('baz') |
| 103 | assert ml.toList == ['baz', 'foo', 'bar'] |
| 104 | |
| 105 | ml = MruList<of String>() |
| 106 | ml.set(['foo', 'bar', 'baz']) |
| 107 | assert ml.toList == ['foo', 'bar', 'baz'] |
| 108 | ml.add('buzz') |
| 109 | assert ml.toList == ['buzz', 'foo', 'bar', 'baz'] |
| 110 | ml.set(['foo', 'bar', 'baz']) |
| 111 | assert ml.toList == ['foo', 'bar', 'baz'] |
| 112 | |
| 113 | }}} |