2 Copyright (c) 2003-2004 Niels Kokholm <kokholm@itu.dk> and Peter Sestoft <sestoft@dina.kvl.dk>
\r
3 Permission is hereby granted, free of charge, to any person obtaining a copy
\r
4 of this software and associated documentation files (the "Software"), to deal
\r
5 in the Software without restriction, including without limitation the rights
\r
6 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
\r
7 copies of the Software, and to permit persons to whom the Software is
\r
8 furnished to do so, subject to the following conditions:
\r
10 The above copyright notice and this permission notice shall be included in
\r
11 all copies or substantial portions of the Software.
\r
13 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
\r
14 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
\r
15 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
\r
16 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
\r
17 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
\r
18 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
\r
22 #define LISTORDERnot
\r
23 #define EXTLISTORDER
\r
25 using System.Diagnostics;
\r
26 using MSG=System.Collections.Generic;
\r
31 /// A list collection based on a doubly linked list data structure with
\r
32 /// a hash index mapping item to node.
\r
34 public class HashedLinkedList<T>: LinkedList<T>, IList<T>
\r
38 HashDictionary<T,Node> dict;
\r
40 //Invariant: base.underlying == basehashedlist
\r
41 HashedLinkedList<T> hashedunderlying;
\r
45 #region Constructors
\r
49 #if LISTORDER || EXTLISTORDER
\r
50 maintaintags = true;
\r
52 dict = new HashDictionary<T,Node>(itemhasher);
\r
57 /// Create a hashed linked list with an external item hasher.
\r
59 /// <param name="itemhasher">The external hasher</param>
\r
60 public HashedLinkedList(IHasher<T> itemhasher) : base(itemhasher) { init(); }
\r
64 /// Create a hashed linked list with the natural item hasher.
\r
66 public HashedLinkedList() : base() { init(); }
\r
72 bool contains(T item, out Node node)
\r
74 if (!dict.Find(item,out node))
\r
77 return insideview(node);
\r
81 void insert(Node succ, T item)
\r
83 Node newnode = new Node(item);
\r
85 if (dict.FindOrAdd(item, ref newnode))
\r
86 throw new ArgumentException("Item already in indexed list");
\r
88 insertNode(succ, newnode);
\r
92 private bool dictremove(T item, out Node node)
\r
94 if (hashedunderlying == null)
\r
96 if (!dict.Remove(item, out node))
\r
101 //We cannot avoid calling dict twice - have to intersperse the listorder test!
\r
102 if (!contains(item, out node))
\r
112 bool insideview(Node node)
\r
114 if (underlying == null)
\r
119 return (startsentinel.tag < node.tag && (endsentinel.tag == 0 || node.tag < endsentinel.tag));
\r
123 return (startsentinel.precedes(node) && node.precedes(endsentinel));
\r
126 if (2 * size < hashedunderlying.size)
\r
128 Node cursor = startsentinel.next;
\r
130 while (cursor != endsentinel)
\r
132 if (cursor == node)
\r
135 cursor = cursor.next;
\r
142 Node cursor = hashedunderlying.startsentinel.next;
\r
144 while (cursor != startsentinel.next)
\r
146 if (cursor == node)
\r
149 cursor = cursor.next;
\r
152 cursor = endsentinel;
\r
153 while (cursor != hashedunderlying.endsentinel)
\r
155 if (cursor == node)
\r
158 cursor = cursor.next;
\r
168 #region IList<T> Members
\r
171 /// On this list, this indexer is read/write.
\r
172 /// <exception cref="IndexOutOfRangeException"/> if i is negative or
\r
173 /// >= the size of the collection.
\r
175 /// <value>The i'th item of this list.</value>
\r
176 /// <param name="i">The index of the item to fetch or store.</param>
\r
178 public override T this[int i]
\r
193 if (itemhasher.Equals(value, n.item))
\r
196 dict.Update(value, n);
\r
198 else if (!dict.FindOrAdd(value, ref n))
\r
200 dict.Remove(n.item);
\r
204 throw new ArgumentException("Item already in indexed list");
\r
210 /// Insert an item at a specific index location in this list.
\r
211 /// <exception cref="IndexOutOfRangeException"/> if i is negative or
\r
212 /// > the size of the collection.</summary>
\r
213 /// <exception cref="InvalidOperationException"/> if the item is
\r
214 /// already in the list.
\r
215 /// <param name="i">The index at which to insert.</param>
\r
216 /// <param name="item">The item to insert.</param>
\r
218 public override void Insert(int i, T item)
\r
221 insert(i == size ? endsentinel : get(i), item);
\r
226 /// Insert into this list all items from an enumerable collection starting
\r
227 /// at a particular index.
\r
228 /// <exception cref="IndexOutOfRangeException"/> if i is negative or
\r
229 /// > the size of the collection.
\r
230 /// <exception cref="InvalidOperationException"/> if one of the items to insert is
\r
231 /// already in the list.
\r
233 /// <param name="i">Index to start inserting at</param>
\r
234 /// <param name="items">Items to insert</param>
\r
236 public override void InsertAll(int i, MSG.IEnumerable<T> items)
\r
243 succ = i == size ? endsentinel : get(i);
\r
247 int taglimit = i == size ? int.MaxValue : succ.tag - 1, thetag = node.tag;
\r
249 TagGroup taggroup = null;
\r
250 int taglimit = 0, thetag = 0;
\r
253 taggroup = gettaggroup(node, succ, out thetag, out taglimit);
\r
255 foreach (T item in items)
\r
257 Node tmp = new Node(item, node, null);
\r
259 if (!dict.FindOrAdd(item, ref tmp))
\r
263 tmp.tag = thetag < taglimit ? ++thetag : thetag;
\r
267 tmp.tag = thetag < taglimit ? ++thetag : thetag;
\r
268 tmp.taggroup = taggroup;
\r
276 throw new ArgumentException("Item already in indexed list");
\r
282 taggroup.count += count;
\r
283 taggroup.first = succ.prev;
\r
284 taggroup.last = node;
\r
290 if (hashedunderlying != null)
\r
291 hashedunderlying.size += count;
\r
293 if (maintaintags && node.tag == node.prev.tag)
\r
298 if (node.tag == node.prev.tag)
\r
299 splittaggroup(taggroup);
\r
306 /// Insert an item right before the first occurrence of some target item.
\r
307 /// <exception cref="ArgumentException"/> if target is not found
\r
308 /// <exception cref="InvalidOperationException"/> if the item is
\r
309 /// already in the list.
\r
311 /// <param name="item">The item to insert.</param>
\r
312 /// <param name="target">The target before which to insert.</param>
\r
314 public override void InsertBefore(T item, T target)
\r
320 if (!contains(target, out node))
\r
321 throw new ArgumentException("Target item not found");
\r
323 insert(node, item);
\r
328 /// Insert an item right after the last(???) occurrence of some target item.
\r
329 /// <exception cref="ArgumentException"/> if target is not found
\r
330 /// <exception cref="InvalidOperationException"/> if the item is
\r
331 /// already in the list.
\r
333 /// <param name="item">The item to insert.</param>
\r
334 /// <param name="target">The target after which to insert.</param>
\r
336 public override void InsertAfter(T item, T target)
\r
342 if (!contains(target, out node))
\r
343 throw new ArgumentException("Target item not found");
\r
345 insert(node.next, item);
\r
351 /// Insert an item at the front of this list.
\r
352 /// <exception cref="InvalidOperationException"/> if the item is
\r
353 /// already in the list.
\r
355 /// <param name="item">The item to insert.</param>
\r
357 public override void InsertFirst(T item)
\r
360 insert(startsentinel.next, item);
\r
364 /// Insert an item at the back of this list.
\r
365 /// <exception cref="InvalidOperationException"/> if the item is
\r
366 /// already in the list.
\r
368 /// <param name="item">The item to insert.</param>
\r
370 public override void InsertLast(T item)
\r
373 insert(endsentinel, item);
\r
378 /// Remove one item from the list: from the front if <code>FIFO</code>
\r
379 /// is true, else from the back.
\r
380 /// <exception cref="InvalidOperationException"/> if this list is empty.
\r
382 /// <returns>The removed item.</returns>
\r
384 public override T Remove()
\r
386 T retval = base.Remove();
\r
388 dict.Remove(retval);
\r
394 /// Remove one item from the front of the list.
\r
395 /// <exception cref="InvalidOperationException"/> if this list is empty.
\r
397 /// <returns>The removed item.</returns>
\r
399 public override T RemoveFirst()
\r
401 T retval = base.RemoveFirst();
\r
403 dict.Remove(retval);
\r
409 /// Remove one item from the back of the list.
\r
410 /// <exception cref="InvalidOperationException"/> if this list is empty.
\r
412 /// <returns>The removed item.</returns>
\r
414 public override T RemoveLast()
\r
416 T retval = base.RemoveLast();
\r
418 dict.Remove(retval);
\r
424 /// Create a list view on this list.
\r
425 /// <exception cref="ArgumentOutOfRangeException"/> if the view would not fit into
\r
428 /// <param name="start">The index in this list of the start of the view.</param>
\r
429 /// <param name="count">The size of the view.</param>
\r
430 /// <returns>The new list view.</returns>
\r
432 public override IList<T> View(int start, int count)
\r
434 checkRange(start, count);
\r
437 HashedLinkedList<T> retval = (HashedLinkedList<T>)MemberwiseClone();
\r
439 retval.underlying = retval.hashedunderlying = hashedunderlying != null ? hashedunderlying : this;
\r
440 retval.offset = start + offset;
\r
441 retval.startsentinel = start == 0 ? startsentinel : get(start - 1);
\r
442 retval.endsentinel = start + count == size ? endsentinel : get(start + count);
\r
443 retval.size = count;
\r
448 /// Reverst part of the list so the items are in the opposite sequence order.
\r
449 /// <exception cref="ArgumentException"/> if the count is negative.
\r
450 /// <exception cref="ArgumentOutOfRangeException"/> if the part does not fit
\r
453 /// <param name="start">The index of the start of the part to reverse.</param>
\r
454 /// <param name="count">The size of the part to reverse.</param>
\r
456 public override void Reverse(int start, int count)
\r
458 //Duplicating linkedlist<T> code to minimize cache misses
\r
460 checkRange(start, count);
\r
464 Node a = get(start), b = get(start + count - 1);
\r
466 for (int i = 0; i < count / 2; i++)
\r
468 T swap = a.item;a.item = b.item;b.item = swap;
\r
469 dict[a.item] = a;dict[b.item] = b;
\r
470 a = a.next;b = b.prev;
\r
476 /// Shuffle the items of this list according to a specific random source.
\r
478 /// <param name="rnd">The random source.</param>
\r
479 public override void Shuffle(Random rnd)
\r
483 ArrayList<T> a = new ArrayList<T>();
\r
488 Node cursor = startsentinel.next;
\r
491 while (cursor != endsentinel)
\r
493 dict[cursor.item = a[j++]] = cursor;
\r
494 cursor = cursor.next;
\r
500 #region IIndexed<T> Members
\r
503 /// Searches for an item in the list going forwrds from the start.
\r
505 /// <param name="item">Item to search for.</param>
\r
506 /// <returns>Index of item from start.</returns>
\r
508 public override int IndexOf(T item)
\r
513 if (!dict.Find(item, out node))
\r
516 if (maintaintags && !insideview(node))
\r
519 if (maintaintags && !insideview(node))
\r
522 return base.IndexOf(item);
\r
527 /// Searches for an item in the list going backwords from the end.
\r
529 /// <param name="item">Item to search for.</param>
\r
530 /// <returns>Index of of item from the end.</returns>
\r
532 public override int LastIndexOf(T item) { return IndexOf(item); }
\r
536 /// Remove the item at a specific position of the list.
\r
537 /// <exception cref="IndexOutOfRangeException"/> if i is negative or
\r
538 /// >= the size of the collection.
\r
540 /// <param name="i">The index of the item to remove.</param>
\r
541 /// <returns>The removed item.</returns>
\r
543 public override T RemoveAt(int i)
\r
545 T retval = base.RemoveAt(i);
\r
547 dict.Remove(retval);
\r
553 /// Remove all items in an index interval.
\r
554 /// <exception cref="IndexOutOfRangeException"/>???.
\r
556 /// <param name="start">The index of the first item to remove.</param>
\r
557 /// <param name="count">The number of items to remove.</param>
\r
559 public override void RemoveInterval(int start, int count)
\r
562 checkRange(start, count);
\r
568 if (start <= size - start - count)
\r
570 b = a = get(start);
\r
574 for (int i = 0; i < count; i++)
\r
576 dict.Remove(b.item);
\r
582 removefromtaggroup(b);
\r
593 a = b = get(start + count - 1);
\r
597 for (int i = 0; i < count; i++)
\r
599 dict.Remove(a.item);
\r
605 removefromtaggroup(a);
\r
616 if (hashedunderlying != null)
\r
617 hashedunderlying.size -= count;
\r
622 #region ISequenced<T> Members
\r
624 int ISequenced<T>.GetHashCode()
\r
625 { modifycheck(); return sequencedhashcode(); }
\r
628 bool ISequenced<T>.Equals(ISequenced<T> that)
\r
629 { modifycheck(); return sequencedequals(that); }
\r
633 #region IEditableCollection<T> Members
\r
637 /// The value is symbolic indicating the type of asymptotic complexity
\r
638 /// in terms of the size of this collection (worst-case or amortized as
\r
641 /// <value>Speed.Constant</value>
\r
643 public override Speed ContainsSpeed
\r
648 #if LISTORDER || EXTLISTORDER
\r
649 return hashedunderlying == null || maintaintags ? Speed.Constant : Speed.Linear;
\r
651 return basehashedlist == null ? Speed.Constant : Speed.Linear;
\r
657 int ICollection<T>.GetHashCode()
\r
658 { modifycheck(); return unsequencedhashcode(); }
\r
661 bool ICollection<T>.Equals(ICollection<T> that)
\r
662 { modifycheck(); return unsequencedequals(that); }
\r
666 /// Check if this collection contains (an item equivalent to according to the
\r
667 /// itemhasher) a particular value.
\r
669 /// <param name="item">The value to check for.</param>
\r
670 /// <returns>True if the items is in this collection.</returns>
\r
672 public override bool Contains(T item)
\r
677 return contains(item, out node);
\r
682 /// Check if this collection contains an item equivalent according to the
\r
683 /// itemhasher to a particular value. If so, return in the ref argument (a
\r
684 /// binary copy of) the actual value found.
\r
686 /// <param name="item">The value to look for.</param>
\r
687 /// <returns>True if the items is in this collection.</returns>
\r
689 public override bool Find(ref T item)
\r
694 if (contains(item, out node)) { item = node.item; return true; }
\r
701 /// Check if this collection contains an item equivalent according to the
\r
702 /// itemhasher to a particular value. If so, update the item in the collection
\r
703 /// to with a binary copy of the supplied value.
\r
705 /// <param name="item">Value to update.</param>
\r
706 /// <returns>True if the item was found and hence updated.</returns>
\r
708 public override bool Update(T item)
\r
713 if (contains(item, out node)) { node.item = item; return true; }
\r
720 /// Check if this collection contains an item equivalent according to the
\r
721 /// itemhasher to a particular value. If so, return in the ref argument (a
\r
722 /// binary copy of) the actual value found. Else, add the item to the collection.
\r
724 /// <param name="item">The value to look for.</param>
\r
725 /// <returns>True if the item was found (hence not added).</returns>
\r
727 public override bool FindOrAdd(ref T item)
\r
731 //This is an extended myinsert:
\r
732 Node node = new Node(item);
\r
734 if (!dict.FindOrAdd(item, ref node))
\r
736 insertNode(endsentinel, node);
\r
740 if (!insideview(node))
\r
741 throw new ArgumentException("Item alredy in indexed list but outside view");
\r
749 /// Check if this collection contains an item equivalent according to the
\r
750 /// itemhasher to a particular value. If so, update the item in the collection
\r
751 /// to with a binary copy of the supplied value; else add the value to the collection.
\r
753 /// <param name="item">Value to add or update.</param>
\r
754 /// <returns>True if the item was found and updated (hence not added).</returns>
\r
756 public override bool UpdateOrAdd(T item)
\r
760 Node node = new Node(item);
\r
762 /*if (basehashedlist == null)
\r
764 if (!dict.UpdateOrAdd(item, node))
\r
769 //NOTE: it is hard to do this without double access to the dictionary
\r
770 //in the update case
\r
771 if (dict.FindOrAdd(item, ref node))
\r
773 if (!insideview(node))
\r
774 throw new ArgumentException("Item in indexed list but outside view");
\r
776 //dict.Update(item, node);
\r
782 insertNode(endsentinel, node);
\r
788 /// Remove a particular item from this collection.
\r
790 /// <param name="item">The value to remove.</param>
\r
791 /// <returns>True if the item was found (and removed).</returns>
\r
793 public override bool Remove(T item)
\r
799 if (!dictremove(item, out node))
\r
808 /// Remove a particular item from this collection if found.
\r
809 /// If an item was removed, report a binary copy of the actual item removed in
\r
812 /// <param name="item">The value to remove on input.</param>
\r
813 /// <returns>True if the item was found (and removed).</returns>
\r
815 public override bool RemoveWithReturn(ref T item)
\r
820 if (!dictremove(item, out node))
\r
830 /// Remove all items in another collection from this one.
\r
832 /// <param name="items">The items to remove.</param>
\r
834 public override void RemoveAll(MSG.IEnumerable<T> items)
\r
839 foreach (T item in items)
\r
840 if (dictremove(item, out node))
\r
846 /// Remove all items from this collection.
\r
849 public override void Clear()
\r
852 if (hashedunderlying == null)
\r
855 foreach (T item in this)
\r
863 /// Remove all items not in some other collection from this one.
\r
865 /// <param name="items">The items to retain.</param>
\r
867 public override void RetainAll(MSG.IEnumerable<T> items)
\r
870 if (hashedunderlying == null)
\r
872 HashDictionary<T,Node> newdict = new HashDictionary<T,Node>(itemhasher);
\r
874 foreach (T item in items)
\r
878 if (dict.Remove(item, out node))
\r
879 newdict.Add(item, node);
\r
882 foreach (KeyValuePair<T,Node> pair in dict)
\r
884 Node n = pair.value, p = n.prev, s = n.next; s.prev = p; p.next = s;
\r
887 removefromtaggroup(n);
\r
893 //For a small number of items to retain it might be faster to
\r
894 //iterate through the list and splice out the chunks not needed
\r
898 HashSet<T> newdict = new HashSet<T>(itemhasher);
\r
900 foreach (T item in this)
\r
903 foreach (T item in items)
\r
904 newdict.Remove(item);
\r
906 Node n = startsentinel.next;
\r
908 while (n != endsentinel)
\r
910 if (newdict.Contains(n.item))
\r
912 dict.Remove(n.item);
\r
922 /// Check if this collection contains all the values in another collection
\r
924 /// are not taken into account.
\r
926 /// <param name="items">The </param>
\r
927 /// <returns>True if all values in <code>items</code>is in this collection.</returns>
\r
929 public override bool ContainsAll(MSG.IEnumerable<T> items)
\r
934 foreach (T item in items)
\r
935 if (!contains(item, out node))
\r
943 /// Count the number of items of the collection equal to a particular value.
\r
944 /// Returns 0 if and only if the value is not in the collection.
\r
946 /// <param name="item">The value to count.</param>
\r
947 /// <returns>The number of copies found.</returns>
\r
949 public override int ContainsCount(T item) { return Contains(item) ? 1 : 0; }
\r
953 /// Remove all items equivalent to a given value.
\r
955 /// <param name="item">The value to remove.</param>
\r
957 public override void RemoveAllCopies(T item) { Remove(item); }
\r
961 #region ISink<T> Members
\r
966 /// <value>False since this collection has set semantics.</value>
\r
968 public override bool AllowsDuplicates { [Tested]get { return false; } }
\r
971 //This is *not* the same as AddLast!!
\r
973 /// Add an item to this collection if possible. Since this collection has set
\r
974 /// semantics, the item will be added if not already in the collection.
\r
976 /// <param name="item">The item to add.</param>
\r
977 /// <returns>True if item was added.</returns>
\r
979 public override bool Add(T item)
\r
983 Node node = new Node(item);
\r
985 if (!dict.FindOrAdd(item, ref node))
\r
987 insertNode(endsentinel, node);
\r
995 //Note: this is *not* equivalent to InsertRange int this Set situation!!!
\r
997 /// Add the elements from another collection to this collection.
\r
998 /// Only items not already in the collection
\r
1001 /// <param name="items">The items to add.</param>
\r
1003 public override void AddAll(MSG.IEnumerable<T> items)
\r
1006 foreach (T item in items)
\r
1008 Node node = new Node(item);
\r
1010 if (!dict.FindOrAdd(item, ref node))
\r
1011 insertNode(endsentinel, node);
\r
1016 /// Add the elements from another collection with a more specialized item type
\r
1017 /// to this collection.
\r
1018 /// Only items not already in the collection
\r
1019 /// will be added.
\r
1021 /// <typeparam name="U">The type of items to add</typeparam>
\r
1022 /// <param name="items">The items to add</param>
\r
1023 public override void AddAll<U>(MSG.IEnumerable<U> items) //where U:T
\r
1026 foreach (T item in items)
\r
1028 Node node = new Node(item);
\r
1030 if (!dict.FindOrAdd(item, ref node))
\r
1031 insertNode(endsentinel, node);
\r
1037 #region IDirectedEnumerable<T> Members
\r
1039 IDirectedEnumerable<T> IDirectedEnumerable<T>.Backwards() { return Backwards(); }
\r
1044 #region Diagnostics
\r
1046 /// Check the integrity of the internal data structures of this collection.
\r
1047 /// Only avaliable in DEBUG builds???
\r
1049 /// <returns>True if check does not fail.</returns>
\r
1051 public override bool Check()
\r
1053 if (!base.Check())
\r
1056 bool retval = true;
\r
1058 if (hashedunderlying == null)
\r
1060 if (size != dict.Count)
\r
1062 Console.WriteLine("list.size ({0}) != dict.Count ({1})", size, dict.Count);
\r
1066 Node n = startsentinel.next, n2;
\r
1068 while (n != endsentinel)
\r
1070 if (!dict.Find(n.item, out n2))
\r
1072 Console.WriteLine("Item in list but not dict: {0}", n.item);
\r
1077 Console.WriteLine("Wrong node in dict for item: {0}", n.item);
\r