3 Copyright (c) 2003-2004 Niels Kokholm <kokholm@itu.dk> and Peter Sestoft <sestoft@dina.kvl.dk>
\r
4 Permission is hereby granted, free of charge, to any person obtaining a copy
\r
5 of this software and associated documentation files (the "Software"), to deal
\r
6 in the Software without restriction, including without limitation the rights
\r
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
\r
8 copies of the Software, and to permit persons to whom the Software is
\r
9 furnished to do so, subject to the following conditions:
\r
11 The above copyright notice and this permission notice shall be included in
\r
12 all copies or substantial portions of the Software.
\r
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
\r
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
\r
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
\r
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
\r
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
\r
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
\r
23 #define LISTORDERnot
\r
24 #define EXTLISTORDER
\r
26 using System.Diagnostics;
\r
27 using MSG=System.Collections.Generic;
\r
32 /// A list collection based on a doubly linked list data structure with
\r
33 /// a hash index mapping item to node.
\r
35 public class HashedLinkedList<T>: LinkedList<T>, IList<T>
\r
39 HashDictionary<T,Node> dict;
\r
41 //Invariant: base.underlying == basehashedlist
\r
42 HashedLinkedList<T> hashedunderlying;
\r
46 #region Constructors
\r
50 #if LISTORDER || EXTLISTORDER
\r
51 maintaintags = true;
\r
53 dict = new HashDictionary<T,Node>(itemhasher);
\r
58 /// Create a hashed linked list with an external item hasher.
\r
60 /// <param name="itemhasher">The external hasher</param>
\r
61 public HashedLinkedList(IHasher<T> itemhasher) : base(itemhasher) { init(); }
\r
65 /// Create a hashed linked list with the natural item hasher.
\r
67 public HashedLinkedList() : base() { init(); }
\r
73 bool contains(T item, out Node node)
\r
75 if (!dict.Find(item,out node))
\r
78 return insideview(node);
\r
82 void insert(Node succ, T item)
\r
84 Node newnode = new Node(item);
\r
86 if (dict.FindOrAdd(item, ref newnode))
\r
87 throw new ArgumentException("Item already in indexed list");
\r
89 insertNode(succ, newnode);
\r
93 private bool dictremove(T item, out Node node)
\r
95 if (hashedunderlying == null)
\r
97 if (!dict.Remove(item, out node))
\r
102 //We cannot avoid calling dict twice - have to intersperse the listorder test!
\r
103 if (!contains(item, out node))
\r
113 bool insideview(Node node)
\r
115 if (underlying == null)
\r
120 return (startsentinel.tag < node.tag && (endsentinel.tag == 0 || node.tag < endsentinel.tag));
\r
124 return (startsentinel.precedes(node) && node.precedes(endsentinel));
\r
127 if (2 * size < hashedunderlying.size)
\r
129 Node cursor = startsentinel.next;
\r
131 while (cursor != endsentinel)
\r
133 if (cursor == node)
\r
136 cursor = cursor.next;
\r
143 Node cursor = hashedunderlying.startsentinel.next;
\r
145 while (cursor != startsentinel.next)
\r
147 if (cursor == node)
\r
150 cursor = cursor.next;
\r
153 cursor = endsentinel;
\r
154 while (cursor != hashedunderlying.endsentinel)
\r
156 if (cursor == node)
\r
159 cursor = cursor.next;
\r
169 #region IList<T> Members
\r
172 /// On this list, this indexer is read/write.
\r
173 /// <exception cref="IndexOutOfRangeException"/> if i is negative or
\r
174 /// >= the size of the collection.
\r
176 /// <value>The i'th item of this list.</value>
\r
177 /// <param name="i">The index of the item to fetch or store.</param>
\r
179 public override T this[int i]
\r
194 if (itemhasher.Equals(value, n.item))
\r
197 dict.Update(value, n);
\r
199 else if (!dict.FindOrAdd(value, ref n))
\r
201 dict.Remove(n.item);
\r
205 throw new ArgumentException("Item already in indexed list");
\r
211 /// Insert an item at a specific index location in this list.
\r
212 /// <exception cref="IndexOutOfRangeException"/> if i is negative or
\r
213 /// > the size of the collection.</summary>
\r
214 /// <exception cref="InvalidOperationException"/> if the item is
\r
215 /// already in the list.
\r
216 /// <param name="i">The index at which to insert.</param>
\r
217 /// <param name="item">The item to insert.</param>
\r
219 public override void Insert(int i, T item)
\r
222 insert(i == size ? endsentinel : get(i), item);
\r
227 /// Insert into this list all items from an enumerable collection starting
\r
228 /// at a particular index.
\r
229 /// <exception cref="IndexOutOfRangeException"/> if i is negative or
\r
230 /// > the size of the collection.
\r
231 /// <exception cref="InvalidOperationException"/> if one of the items to insert is
\r
232 /// already in the list.
\r
234 /// <param name="i">Index to start inserting at</param>
\r
235 /// <param name="items">Items to insert</param>
\r
237 public override void InsertAll(int i, MSG.IEnumerable<T> items)
\r
244 succ = i == size ? endsentinel : get(i);
\r
248 int taglimit = i == size ? int.MaxValue : succ.tag - 1, thetag = node.tag;
\r
250 TagGroup taggroup = null;
\r
251 int taglimit = 0, thetag = 0;
\r
254 taggroup = gettaggroup(node, succ, out thetag, out taglimit);
\r
256 foreach (T item in items)
\r
258 Node tmp = new Node(item, node, null);
\r
260 if (!dict.FindOrAdd(item, ref tmp))
\r
264 tmp.tag = thetag < taglimit ? ++thetag : thetag;
\r
268 tmp.tag = thetag < taglimit ? ++thetag : thetag;
\r
269 tmp.taggroup = taggroup;
\r
277 throw new ArgumentException("Item already in indexed list");
\r
283 taggroup.count += count;
\r
284 taggroup.first = succ.prev;
\r
285 taggroup.last = node;
\r
291 if (hashedunderlying != null)
\r
292 hashedunderlying.size += count;
\r
294 if (maintaintags && node.tag == node.prev.tag)
\r
299 if (node.tag == node.prev.tag)
\r
300 splittaggroup(taggroup);
\r
307 /// Insert an item right before the first occurrence of some target item.
\r
308 /// <exception cref="ArgumentException"/> if target is not found
\r
309 /// <exception cref="InvalidOperationException"/> if the item is
\r
310 /// already in the list.
\r
312 /// <param name="item">The item to insert.</param>
\r
313 /// <param name="target">The target before which to insert.</param>
\r
315 public override void InsertBefore(T item, T target)
\r
321 if (!contains(target, out node))
\r
322 throw new ArgumentException("Target item not found");
\r
324 insert(node, item);
\r
329 /// Insert an item right after the last(???) occurrence of some target item.
\r
330 /// <exception cref="ArgumentException"/> if target is not found
\r
331 /// <exception cref="InvalidOperationException"/> if the item is
\r
332 /// already in the list.
\r
334 /// <param name="item">The item to insert.</param>
\r
335 /// <param name="target">The target after which to insert.</param>
\r
337 public override void InsertAfter(T item, T target)
\r
343 if (!contains(target, out node))
\r
344 throw new ArgumentException("Target item not found");
\r
346 insert(node.next, item);
\r
352 /// Insert an item at the front of this list.
\r
353 /// <exception cref="InvalidOperationException"/> if the item is
\r
354 /// already in the list.
\r
356 /// <param name="item">The item to insert.</param>
\r
358 public override void InsertFirst(T item)
\r
361 insert(startsentinel.next, item);
\r
365 /// Insert an item at the back of this list.
\r
366 /// <exception cref="InvalidOperationException"/> if the item is
\r
367 /// already in the list.
\r
369 /// <param name="item">The item to insert.</param>
\r
371 public override void InsertLast(T item)
\r
374 insert(endsentinel, item);
\r
379 /// Remove one item from the list: from the front if <code>FIFO</code>
\r
380 /// is true, else from the back.
\r
381 /// <exception cref="InvalidOperationException"/> if this list is empty.
\r
383 /// <returns>The removed item.</returns>
\r
385 public override T Remove()
\r
387 T retval = base.Remove();
\r
389 dict.Remove(retval);
\r
395 /// Remove one item from the front of the list.
\r
396 /// <exception cref="InvalidOperationException"/> if this list is empty.
\r
398 /// <returns>The removed item.</returns>
\r
400 public override T RemoveFirst()
\r
402 T retval = base.RemoveFirst();
\r
404 dict.Remove(retval);
\r
410 /// Remove one item from the back of the list.
\r
411 /// <exception cref="InvalidOperationException"/> if this list is empty.
\r
413 /// <returns>The removed item.</returns>
\r
415 public override T RemoveLast()
\r
417 T retval = base.RemoveLast();
\r
419 dict.Remove(retval);
\r
425 /// Create a list view on this list.
\r
426 /// <exception cref="ArgumentOutOfRangeException"/> if the view would not fit into
\r
429 /// <param name="start">The index in this list of the start of the view.</param>
\r
430 /// <param name="count">The size of the view.</param>
\r
431 /// <returns>The new list view.</returns>
\r
433 public override IList<T> View(int start, int count)
\r
435 checkRange(start, count);
\r
438 HashedLinkedList<T> retval = (HashedLinkedList<T>)MemberwiseClone();
\r
440 retval.underlying = retval.hashedunderlying = hashedunderlying != null ? hashedunderlying : this;
\r
441 retval.offset = start + offset;
\r
442 retval.startsentinel = start == 0 ? startsentinel : get(start - 1);
\r
443 retval.endsentinel = start + count == size ? endsentinel : get(start + count);
\r
444 retval.size = count;
\r
449 /// Reverst part of the list so the items are in the opposite sequence order.
\r
450 /// <exception cref="ArgumentException"/> if the count is negative.
\r
451 /// <exception cref="ArgumentOutOfRangeException"/> if the part does not fit
\r
454 /// <param name="start">The index of the start of the part to reverse.</param>
\r
455 /// <param name="count">The size of the part to reverse.</param>
\r
457 public override void Reverse(int start, int count)
\r
459 //Duplicating linkedlist<T> code to minimize cache misses
\r
461 checkRange(start, count);
\r
465 Node a = get(start), b = get(start + count - 1);
\r
467 for (int i = 0; i < count / 2; i++)
\r
469 T swap = a.item;a.item = b.item;b.item = swap;
\r
470 dict[a.item] = a;dict[b.item] = b;
\r
471 a = a.next;b = b.prev;
\r
477 /// Shuffle the items of this list according to a specific random source.
\r
479 /// <param name="rnd">The random source.</param>
\r
480 public override void Shuffle(Random rnd)
\r
484 ArrayList<T> a = new ArrayList<T>();
\r
489 Node cursor = startsentinel.next;
\r
492 while (cursor != endsentinel)
\r
494 dict[cursor.item = a[j++]] = cursor;
\r
495 cursor = cursor.next;
\r
501 #region IIndexed<T> Members
\r
504 /// Searches for an item in the list going forwrds from the start.
\r
506 /// <param name="item">Item to search for.</param>
\r
507 /// <returns>Index of item from start.</returns>
\r
509 public override int IndexOf(T item)
\r
514 if (!dict.Find(item, out node))
\r
517 if (maintaintags && !insideview(node))
\r
520 if (maintaintags && !insideview(node))
\r
523 return base.IndexOf(item);
\r
528 /// Searches for an item in the list going backwords from the end.
\r
530 /// <param name="item">Item to search for.</param>
\r
531 /// <returns>Index of of item from the end.</returns>
\r
533 public override int LastIndexOf(T item) { return IndexOf(item); }
\r
537 /// Remove the item at a specific position of the list.
\r
538 /// <exception cref="IndexOutOfRangeException"/> if i is negative or
\r
539 /// >= the size of the collection.
\r
541 /// <param name="i">The index of the item to remove.</param>
\r
542 /// <returns>The removed item.</returns>
\r
544 public override T RemoveAt(int i)
\r
546 T retval = base.RemoveAt(i);
\r
548 dict.Remove(retval);
\r
554 /// Remove all items in an index interval.
\r
555 /// <exception cref="IndexOutOfRangeException"/>???.
\r
557 /// <param name="start">The index of the first item to remove.</param>
\r
558 /// <param name="count">The number of items to remove.</param>
\r
560 public override void RemoveInterval(int start, int count)
\r
563 checkRange(start, count);
\r
569 if (start <= size - start - count)
\r
571 b = a = get(start);
\r
575 for (int i = 0; i < count; i++)
\r
577 dict.Remove(b.item);
\r
583 removefromtaggroup(b);
\r
594 a = b = get(start + count - 1);
\r
598 for (int i = 0; i < count; i++)
\r
600 dict.Remove(a.item);
\r
606 removefromtaggroup(a);
\r
617 if (hashedunderlying != null)
\r
618 hashedunderlying.size -= count;
\r
623 #region ISequenced<T> Members
\r
625 int ISequenced<T>.GetHashCode()
\r
626 { modifycheck(); return sequencedhashcode(); }
\r
629 bool ISequenced<T>.Equals(ISequenced<T> that)
\r
630 { modifycheck(); return sequencedequals(that); }
\r
634 #region IEditableCollection<T> Members
\r
638 /// The value is symbolic indicating the type of asymptotic complexity
\r
639 /// in terms of the size of this collection (worst-case or amortized as
\r
642 /// <value>Speed.Constant</value>
\r
644 public override Speed ContainsSpeed
\r
649 #if LISTORDER || EXTLISTORDER
\r
650 return hashedunderlying == null || maintaintags ? Speed.Constant : Speed.Linear;
\r
652 return basehashedlist == null ? Speed.Constant : Speed.Linear;
\r
658 int ICollection<T>.GetHashCode()
\r
659 { modifycheck(); return unsequencedhashcode(); }
\r
662 bool ICollection<T>.Equals(ICollection<T> that)
\r
663 { modifycheck(); return unsequencedequals(that); }
\r
667 /// Check if this collection contains (an item equivalent to according to the
\r
668 /// itemhasher) a particular value.
\r
670 /// <param name="item">The value to check for.</param>
\r
671 /// <returns>True if the items is in this collection.</returns>
\r
673 public override bool Contains(T item)
\r
678 return contains(item, out node);
\r
683 /// Check if this collection contains an item equivalent according to the
\r
684 /// itemhasher to a particular value. If so, return in the ref argument (a
\r
685 /// binary copy of) the actual value found.
\r
687 /// <param name="item">The value to look for.</param>
\r
688 /// <returns>True if the items is in this collection.</returns>
\r
690 public override bool Find(ref T item)
\r
695 if (contains(item, out node)) { item = node.item; return true; }
\r
702 /// Check if this collection contains an item equivalent according to the
\r
703 /// itemhasher to a particular value. If so, update the item in the collection
\r
704 /// to with a binary copy of the supplied value.
\r
706 /// <param name="item">Value to update.</param>
\r
707 /// <returns>True if the item was found and hence updated.</returns>
\r
709 public override bool Update(T item)
\r
714 if (contains(item, out node)) { node.item = item; return true; }
\r
721 /// Check if this collection contains an item equivalent according to the
\r
722 /// itemhasher to a particular value. If so, return in the ref argument (a
\r
723 /// binary copy of) the actual value found. Else, add the item to the collection.
\r
725 /// <param name="item">The value to look for.</param>
\r
726 /// <returns>True if the item was found (hence not added).</returns>
\r
728 public override bool FindOrAdd(ref T item)
\r
732 //This is an extended myinsert:
\r
733 Node node = new Node(item);
\r
735 if (!dict.FindOrAdd(item, ref node))
\r
737 insertNode(endsentinel, node);
\r
741 if (!insideview(node))
\r
742 throw new ArgumentException("Item alredy in indexed list but outside view");
\r
750 /// Check if this collection contains an item equivalent according to the
\r
751 /// itemhasher to a particular value. If so, update the item in the collection
\r
752 /// to with a binary copy of the supplied value; else add the value to the collection.
\r
754 /// <param name="item">Value to add or update.</param>
\r
755 /// <returns>True if the item was found and updated (hence not added).</returns>
\r
757 public override bool UpdateOrAdd(T item)
\r
761 Node node = new Node(item);
\r
763 /*if (basehashedlist == null)
\r
765 if (!dict.UpdateOrAdd(item, node))
\r
770 //NOTE: it is hard to do this without double access to the dictionary
\r
771 //in the update case
\r
772 if (dict.FindOrAdd(item, ref node))
\r
774 if (!insideview(node))
\r
775 throw new ArgumentException("Item in indexed list but outside view");
\r
777 //dict.Update(item, node);
\r
783 insertNode(endsentinel, node);
\r
789 /// Remove a particular item from this collection.
\r
791 /// <param name="item">The value to remove.</param>
\r
792 /// <returns>True if the item was found (and removed).</returns>
\r
794 public override bool Remove(T item)
\r
800 if (!dictremove(item, out node))
\r
809 /// Remove a particular item from this collection if found.
\r
810 /// If an item was removed, report a binary copy of the actual item removed in
\r
813 /// <param name="item">The value to remove on input.</param>
\r
814 /// <returns>True if the item was found (and removed).</returns>
\r
816 public override bool RemoveWithReturn(ref T item)
\r
821 if (!dictremove(item, out node))
\r
831 /// Remove all items in another collection from this one.
\r
833 /// <param name="items">The items to remove.</param>
\r
835 public override void RemoveAll(MSG.IEnumerable<T> items)
\r
840 foreach (T item in items)
\r
841 if (dictremove(item, out node))
\r
847 /// Remove all items from this collection.
\r
850 public override void Clear()
\r
853 if (hashedunderlying == null)
\r
856 foreach (T item in this)
\r
864 /// Remove all items not in some other collection from this one.
\r
866 /// <param name="items">The items to retain.</param>
\r
868 public override void RetainAll(MSG.IEnumerable<T> items)
\r
871 if (hashedunderlying == null)
\r
873 HashDictionary<T,Node> newdict = new HashDictionary<T,Node>(itemhasher);
\r
875 foreach (T item in items)
\r
879 if (dict.Remove(item, out node))
\r
880 newdict.Add(item, node);
\r
883 foreach (KeyValuePair<T,Node> pair in dict)
\r
885 Node n = pair.value, p = n.prev, s = n.next; s.prev = p; p.next = s;
\r
888 removefromtaggroup(n);
\r
894 //For a small number of items to retain it might be faster to
\r
895 //iterate through the list and splice out the chunks not needed
\r
899 HashSet<T> newdict = new HashSet<T>(itemhasher);
\r
901 foreach (T item in this)
\r
904 foreach (T item in items)
\r
905 newdict.Remove(item);
\r
907 Node n = startsentinel.next;
\r
909 while (n != endsentinel)
\r
911 if (newdict.Contains(n.item))
\r
913 dict.Remove(n.item);
\r
923 /// Check if this collection contains all the values in another collection
\r
925 /// are not taken into account.
\r
927 /// <param name="items">The </param>
\r
928 /// <returns>True if all values in <code>items</code>is in this collection.</returns>
\r
930 public override bool ContainsAll(MSG.IEnumerable<T> items)
\r
935 foreach (T item in items)
\r
936 if (!contains(item, out node))
\r
944 /// Count the number of items of the collection equal to a particular value.
\r
945 /// Returns 0 if and only if the value is not in the collection.
\r
947 /// <param name="item">The value to count.</param>
\r
948 /// <returns>The number of copies found.</returns>
\r
950 public override int ContainsCount(T item) { return Contains(item) ? 1 : 0; }
\r
954 /// Remove all items equivalent to a given value.
\r
956 /// <param name="item">The value to remove.</param>
\r
958 public override void RemoveAllCopies(T item) { Remove(item); }
\r
962 #region ISink<T> Members
\r
967 /// <value>False since this collection has set semantics.</value>
\r
969 public override bool AllowsDuplicates { [Tested]get { return false; } }
\r
972 //This is *not* the same as AddLast!!
\r
974 /// Add an item to this collection if possible. Since this collection has set
\r
975 /// semantics, the item will be added if not already in the collection.
\r
977 /// <param name="item">The item to add.</param>
\r
978 /// <returns>True if item was added.</returns>
\r
980 public override bool Add(T item)
\r
984 Node node = new Node(item);
\r
986 if (!dict.FindOrAdd(item, ref node))
\r
988 insertNode(endsentinel, node);
\r
996 //Note: this is *not* equivalent to InsertRange int this Set situation!!!
\r
998 /// Add the elements from another collection to this collection.
\r
999 /// Only items not already in the collection
\r
1000 /// will be added.
\r
1002 /// <param name="items">The items to add.</param>
\r
1004 public override void AddAll(MSG.IEnumerable<T> items)
\r
1007 foreach (T item in items)
\r
1009 Node node = new Node(item);
\r
1011 if (!dict.FindOrAdd(item, ref node))
\r
1012 insertNode(endsentinel, node);
\r
1017 /// Add the elements from another collection with a more specialized item type
\r
1018 /// to this collection.
\r
1019 /// Only items not already in the collection
\r
1020 /// will be added.
\r
1022 /// <typeparam name="U">The type of items to add</typeparam>
\r
1023 /// <param name="items">The items to add</param>
\r
1024 public override void AddAll<U>(MSG.IEnumerable<U> items) //where U:T
\r
1027 foreach (T item in items)
\r
1029 Node node = new Node(item);
\r
1031 if (!dict.FindOrAdd(item, ref node))
\r
1032 insertNode(endsentinel, node);
\r
1038 #region IDirectedEnumerable<T> Members
\r
1040 IDirectedEnumerable<T> IDirectedEnumerable<T>.Backwards() { return Backwards(); }
\r
1045 #region Diagnostics
\r
1047 /// Check the integrity of the internal data structures of this collection.
\r
1048 /// Only avaliable in DEBUG builds???
\r
1050 /// <returns>True if check does not fail.</returns>
\r
1052 public override bool Check()
\r
1054 if (!base.Check())
\r
1057 bool retval = true;
\r
1059 if (hashedunderlying == null)
\r
1061 if (size != dict.Count)
\r
1063 Console.WriteLine("list.size ({0}) != dict.Count ({1})", size, dict.Count);
\r
1067 Node n = startsentinel.next, n2;
\r
1069 while (n != endsentinel)
\r
1071 if (!dict.Find(n.item, out n2))
\r
1073 Console.WriteLine("Item in list but not dict: {0}", n.item);
\r
1078 Console.WriteLine("Wrong node in dict for item: {0}", n.item);
\r