1
|
/*
|
2
|
Copyright (c) 2003-2006 Niels Kokholm and Peter Sestoft
|
3
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
of this software and associated documentation files (the "Software"), to deal
|
5
|
in the Software without restriction, including without limitation the rights
|
6
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
copies of the Software, and to permit persons to whom the Software is
|
8
|
furnished to do so, subject to the following conditions:
|
9
|
|
10
|
The above copyright notice and this permission notice shall be included in
|
11
|
all copies or substantial portions of the Software.
|
12
|
|
13
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
SOFTWARE.
|
20
|
*/
|
21
|
|
22
|
#define HASHINDEXnot
|
23
|
|
24
|
using System;
|
25
|
using System.Diagnostics;
|
26
|
using SCG = System.Collections.Generic;
|
27
|
|
28
|
namespace C5
|
29
|
{
|
30
|
/// <summary>
|
31
|
/// A list collection class based on a doubly linked list data structure.
|
32
|
/// </summary>
|
33
|
[Serializable]
|
34
|
public class LinkedList<T> : SequencedBase<T>, IList<T>, SCG.IList<T>
|
35
|
#if HASHINDEX
|
36
|
#else
|
37
|
, IStack<T>, IQueue<T>
|
38
|
#endif
|
39
|
{
|
40
|
#region Fields
|
41
|
/// <summary>
|
42
|
/// IExtensible.Add(T) always does AddLast(T), fIFO determines
|
43
|
/// if T Remove() does RemoveFirst() or RemoveLast()
|
44
|
/// </summary>
|
45
|
bool fIFO = true;
|
46
|
|
47
|
#region Events
|
48
|
|
49
|
/// <summary>
|
50
|
///
|
51
|
/// </summary>
|
52
|
/// <value></value>
|
53
|
public override EventTypeEnum ListenableEvents { get { return underlying == null ? EventTypeEnum.All : EventTypeEnum.None; } }
|
54
|
|
55
|
#endregion
|
56
|
|
57
|
//Invariant: startsentinel != null && endsentinel != null
|
58
|
//If size==0: startsentinel.next == endsentinel && endsentinel.prev == startsentinel
|
59
|
//Else: startsentinel.next == First && endsentinel.prev == Last)
|
60
|
/// <summary>
|
61
|
/// Node to the left of first node
|
62
|
/// </summary>
|
63
|
Node startsentinel;
|
64
|
/// <summary>
|
65
|
/// Node to the right of last node
|
66
|
/// </summary>
|
67
|
Node endsentinel;
|
68
|
/// <summary>
|
69
|
/// Offset of this view in underlying list
|
70
|
/// </summary>
|
71
|
#if HASHINDEX
|
72
|
int? offset;
|
73
|
#else
|
74
|
int offset;
|
75
|
#endif
|
76
|
|
77
|
/// <summary>
|
78
|
/// underlying list of this view (or null for the underlying list)
|
79
|
/// </summary>
|
80
|
LinkedList<T> underlying;
|
81
|
|
82
|
//Note: all views will have the same views list since all view objects are created by MemberwiseClone()
|
83
|
WeakViewList<LinkedList<T>> views;
|
84
|
WeakViewList<LinkedList<T>>.Node myWeakReference;
|
85
|
|
86
|
/// <summary>
|
87
|
/// Has this list or view not been invalidated by some operation (by someone calling Dispose())
|
88
|
/// </summary>
|
89
|
bool isValid = true;
|
90
|
|
91
|
|
92
|
#if HASHINDEX
|
93
|
HashDictionary<T, Node> dict;
|
94
|
/// <summary>
|
95
|
/// Number of taggroups
|
96
|
/// </summary>
|
97
|
int taggroups;
|
98
|
/// <summary>
|
99
|
///
|
100
|
/// </summary>
|
101
|
/// <value></value>
|
102
|
int Taggroups
|
103
|
{
|
104
|
get { return underlying == null ? taggroups : underlying.taggroups; }
|
105
|
set { if (underlying == null) taggroups = value; else underlying.taggroups = value; }
|
106
|
}
|
107
|
#endif
|
108
|
|
109
|
#endregion
|
110
|
|
111
|
#region Util
|
112
|
|
113
|
bool equals(T i1, T i2) { return itemequalityComparer.Equals(i1, i2); }
|
114
|
|
115
|
#region Check utilities
|
116
|
/// <summary>
|
117
|
/// Check if it is valid to perform updates and increment stamp of
|
118
|
/// underlying if this is a view.
|
119
|
/// <para>This method should be called in every public modifying
|
120
|
/// methods before any modifications are performed.
|
121
|
/// </para>
|
122
|
/// </summary>
|
123
|
/// <exception cref="InvalidOperationException"> if check fails.</exception>
|
124
|
protected override void updatecheck()
|
125
|
{
|
126
|
validitycheck();
|
127
|
base.updatecheck();
|
128
|
if (underlying != null)
|
129
|
underlying.stamp++;
|
130
|
}
|
131
|
|
132
|
/// <summary>
|
133
|
/// Check if we are a view that the underlyinglist has only been updated through us.
|
134
|
/// <br/>
|
135
|
/// This method should be called from enumerators etc to guard against
|
136
|
/// modification of the base collection.
|
137
|
/// </summary>
|
138
|
/// <exception cref="InvalidOperationException"> if check fails.</exception>
|
139
|
void validitycheck()
|
140
|
{
|
141
|
if (!isValid)
|
142
|
throw new ViewDisposedException();
|
143
|
}
|
144
|
|
145
|
/// <summary>
|
146
|
/// Check that the list has not been updated since a particular time.
|
147
|
/// </summary>
|
148
|
/// <param name="stamp">The stamp indicating the time.</param>
|
149
|
/// <exception cref="CollectionModifiedException"> if check fails.</exception>
|
150
|
protected override void modifycheck(int stamp)
|
151
|
{
|
152
|
validitycheck();
|
153
|
if ((underlying != null ? underlying.stamp : this.stamp) != stamp)
|
154
|
throw new CollectionModifiedException();
|
155
|
}
|
156
|
#endregion
|
157
|
|
158
|
#region Searching
|
159
|
bool contains(T item, out Node node)
|
160
|
{
|
161
|
#if HASHINDEX
|
162
|
if (dict.Find(item, out node))
|
163
|
return insideview(node);
|
164
|
#else
|
165
|
//TODO: search from both ends? Or search from the end selected by FIFO?
|
166
|
node = startsentinel.next;
|
167
|
while (node != endsentinel)
|
168
|
{
|
169
|
if (equals(item, node.item))
|
170
|
return true;
|
171
|
node = node.next;
|
172
|
}
|
173
|
#endif
|
174
|
return false;
|
175
|
}
|
176
|
|
177
|
/// <summary>
|
178
|
/// Search forwards from a node for a node with a particular item.
|
179
|
/// </summary>
|
180
|
/// <param name="item">The item to look for</param>
|
181
|
/// <param name="node">On input, the node to start at. If item was found, the node found on output.</param>
|
182
|
/// <param name="index">If node was found, the value will be the number of links followed higher than
|
183
|
/// the value on input. If item was not found, the value on output is undefined.</param>
|
184
|
/// <returns>True if node was found.</returns>
|
185
|
bool find(T item, ref Node node, ref int index)
|
186
|
{
|
187
|
while (node != endsentinel)
|
188
|
{
|
189
|
//if (item.Equals(node.item))
|
190
|
if (itemequalityComparer.Equals(item, node.item))
|
191
|
return true;
|
192
|
|
193
|
index++;
|
194
|
node = node.next;
|
195
|
}
|
196
|
|
197
|
return false;
|
198
|
}
|
199
|
|
200
|
bool dnif(T item, ref Node node, ref int index)
|
201
|
{
|
202
|
while (node != startsentinel)
|
203
|
{
|
204
|
//if (item.Equals(node.item))
|
205
|
if (itemequalityComparer.Equals(item, node.item))
|
206
|
return true;
|
207
|
|
208
|
index--;
|
209
|
node = node.prev;
|
210
|
}
|
211
|
|
212
|
return false;
|
213
|
}
|
214
|
|
215
|
#if HASHINDEX
|
216
|
bool insideview(Node node)
|
217
|
{
|
218
|
if (underlying == null)
|
219
|
return true;
|
220
|
return (startsentinel.precedes(node) && node.precedes(endsentinel));
|
221
|
}
|
222
|
#endif
|
223
|
|
224
|
#endregion
|
225
|
|
226
|
#region Indexing
|
227
|
/// <summary>
|
228
|
/// Return the node at position pos
|
229
|
/// </summary>
|
230
|
/// <param name="pos"></param>
|
231
|
/// <returns></returns>
|
232
|
Node get(int pos)
|
233
|
{
|
234
|
if (pos < 0 || pos >= size)
|
235
|
throw new IndexOutOfRangeException();
|
236
|
else if (pos < size / 2)
|
237
|
{ // Closer to front
|
238
|
Node node = startsentinel;
|
239
|
|
240
|
for (int i = 0; i <= pos; i++)
|
241
|
node = node.next;
|
242
|
|
243
|
return node;
|
244
|
}
|
245
|
else
|
246
|
{ // Closer to end
|
247
|
Node node = endsentinel;
|
248
|
|
249
|
for (int i = size; i > pos; i--)
|
250
|
node = node.prev;
|
251
|
|
252
|
return node;
|
253
|
}
|
254
|
}
|
255
|
|
256
|
/// <summary>
|
257
|
/// Find the distance from pos to the set given by positions. Return the
|
258
|
/// signed distance as return value and as an out parameter, the
|
259
|
/// array index of the nearest position. This is used for up to length 5 of
|
260
|
/// positions, and we do not assume it is sorted.
|
261
|
/// </summary>
|
262
|
/// <param name="pos"></param>
|
263
|
/// <param name="positions"></param>
|
264
|
/// <param name="nearest"></param>
|
265
|
/// <returns></returns>
|
266
|
int dist(int pos, out int nearest, int[] positions)
|
267
|
{
|
268
|
nearest = -1;
|
269
|
int bestdist = int.MaxValue;
|
270
|
int signeddist = bestdist;
|
271
|
for (int i = 0; i < positions.Length; i++)
|
272
|
{
|
273
|
int thisdist = positions[i] - pos;
|
274
|
if (thisdist >= 0 && thisdist < bestdist) { nearest = i; bestdist = thisdist; signeddist = thisdist; }
|
275
|
if (thisdist < 0 && -thisdist < bestdist) { nearest = i; bestdist = -thisdist; signeddist = thisdist; }
|
276
|
}
|
277
|
return signeddist;
|
278
|
}
|
279
|
|
280
|
/// <summary>
|
281
|
/// Find the node at position pos, given known positions of several nodes.
|
282
|
/// </summary>
|
283
|
/// <param name="pos"></param>
|
284
|
/// <param name="positions"></param>
|
285
|
/// <param name="nodes"></param>
|
286
|
/// <returns></returns>
|
287
|
Node get(int pos, int[] positions, Node[] nodes)
|
288
|
{
|
289
|
int nearest;
|
290
|
int delta = dist(pos, out nearest, positions);
|
291
|
Node node = nodes[nearest];
|
292
|
if (delta > 0)
|
293
|
for (int i = 0; i < delta; i++)
|
294
|
node = node.prev;
|
295
|
else
|
296
|
for (int i = 0; i > delta; i--)
|
297
|
node = node.next;
|
298
|
return node;
|
299
|
}
|
300
|
|
301
|
/// <summary>
|
302
|
/// Get nodes at positions p1 and p2, given nodes at several positions.
|
303
|
/// </summary>
|
304
|
/// <param name="p1"></param>
|
305
|
/// <param name="p2"></param>
|
306
|
/// <param name="n1"></param>
|
307
|
/// <param name="n2"></param>
|
308
|
/// <param name="positions"></param>
|
309
|
/// <param name="nodes"></param>
|
310
|
void getPair(int p1, int p2, out Node n1, out Node n2, int[] positions, Node[] nodes)
|
311
|
{
|
312
|
int nearest1, nearest2;
|
313
|
int delta1 = dist(p1, out nearest1, positions), d1 = delta1 < 0 ? -delta1 : delta1;
|
314
|
int delta2 = dist(p2, out nearest2, positions), d2 = delta2 < 0 ? -delta2 : delta2;
|
315
|
|
316
|
if (d1 < d2)
|
317
|
{
|
318
|
n1 = get(p1, positions, nodes);
|
319
|
n2 = get(p2, new int[] { positions[nearest2], p1 }, new Node[] { nodes[nearest2], n1 });
|
320
|
}
|
321
|
else
|
322
|
{
|
323
|
n2 = get(p2, positions, nodes);
|
324
|
n1 = get(p1, new int[] { positions[nearest1], p2 }, new Node[] { nodes[nearest1], n2 });
|
325
|
}
|
326
|
}
|
327
|
#endregion
|
328
|
|
329
|
#region Insertion
|
330
|
#if HASHINDEX
|
331
|
void insert(int index, Node succ, T item)
|
332
|
{
|
333
|
Node newnode = new Node(item);
|
334
|
if (dict.FindOrAdd(item, ref newnode))
|
335
|
throw new DuplicateNotAllowedException("Item already in indexed list");
|
336
|
insertNode(true, succ, newnode);
|
337
|
}
|
338
|
|
339
|
/// <summary>
|
340
|
/// Insert a Node before another one. Unchecked version.
|
341
|
/// </summary>
|
342
|
/// <param name="succ">The successor to be</param>
|
343
|
/// <param name="newnode">Node to insert</param>
|
344
|
/// <param name="updateViews">update overlapping view in this call</param>
|
345
|
void insertNode(bool updateViews, Node succ, Node newnode)
|
346
|
{
|
347
|
newnode.next = succ;
|
348
|
Node pred = newnode.prev = succ.prev;
|
349
|
succ.prev.next = newnode;
|
350
|
succ.prev = newnode;
|
351
|
size++;
|
352
|
if (underlying != null)
|
353
|
underlying.size++;
|
354
|
settag(newnode);
|
355
|
if (updateViews)
|
356
|
fixViewsAfterInsert(succ, pred, 1, 0);
|
357
|
}
|
358
|
#else
|
359
|
/// <summary>
|
360
|
///
|
361
|
/// </summary>
|
362
|
/// <param name="index">The index in this view</param>
|
363
|
/// <param name="succ"></param>
|
364
|
/// <param name="item"></param>
|
365
|
/// <returns></returns>
|
366
|
Node insert(int index, Node succ, T item)
|
367
|
{
|
368
|
Node newnode = new Node(item, succ.prev, succ);
|
369
|
succ.prev.next = newnode;
|
370
|
succ.prev = newnode;
|
371
|
size++;
|
372
|
if (underlying != null)
|
373
|
underlying.size++;
|
374
|
fixViewsAfterInsert(succ, newnode.prev, 1, Offset + index);
|
375
|
return newnode;
|
376
|
}
|
377
|
#endif
|
378
|
#endregion
|
379
|
|
380
|
#region Removal
|
381
|
T remove(Node node, int index)
|
382
|
{
|
383
|
fixViewsBeforeSingleRemove(node, Offset + index);
|
384
|
node.prev.next = node.next;
|
385
|
node.next.prev = node.prev;
|
386
|
size--;
|
387
|
if (underlying != null)
|
388
|
underlying.size--;
|
389
|
#if HASHINDEX
|
390
|
removefromtaggroup(node);
|
391
|
#endif
|
392
|
return node.item;
|
393
|
}
|
394
|
|
395
|
#if HASHINDEX
|
396
|
private bool dictremove(T item, out Node node)
|
397
|
{
|
398
|
if (underlying == null)
|
399
|
{
|
400
|
if (!dict.Remove(item, out node))
|
401
|
return false;
|
402
|
}
|
403
|
else
|
404
|
{
|
405
|
//We cannot avoid calling dict twice - have to intersperse the listorder test!
|
406
|
if (!contains(item, out node))
|
407
|
return false;
|
408
|
dict.Remove(item);
|
409
|
}
|
410
|
return true;
|
411
|
}
|
412
|
#endif
|
413
|
#endregion
|
414
|
|
415
|
#region fixView utilities
|
416
|
/// <summary>
|
417
|
///
|
418
|
/// </summary>
|
419
|
/// <param name="added">The actual number of inserted nodes</param>
|
420
|
/// <param name="pred">The predecessor of the inserted nodes</param>
|
421
|
/// <param name="succ">The successor of the added nodes</param>
|
422
|
/// <param name="realInsertionIndex"></param>
|
423
|
void fixViewsAfterInsert(Node succ, Node pred, int added, int realInsertionIndex)
|
424
|
{
|
425
|
if (views != null)
|
426
|
foreach (LinkedList<T> view in views)
|
427
|
{
|
428
|
if (view != this)
|
429
|
{
|
430
|
#if HASHINDEX
|
431
|
if (pred.precedes(view.startsentinel) || (view.startsentinel == pred && view.size > 0))
|
432
|
view.offset += added;
|
433
|
if (view.startsentinel.precedes(pred) && succ.precedes(view.endsentinel))
|
434
|
view.size += added;
|
435
|
if (view.startsentinel == pred && view.size > 0)
|
436
|
view.startsentinel = succ.prev;
|
437
|
if (view.endsentinel == succ)
|
438
|
view.endsentinel = pred.next;
|
439
|
#else
|
440
|
if (view.Offset == realInsertionIndex && view.size > 0)
|
441
|
view.startsentinel = succ.prev;
|
442
|
if (view.Offset + view.size == realInsertionIndex)
|
443
|
view.endsentinel = pred.next;
|
444
|
if (view.Offset < realInsertionIndex && view.Offset + view.size > realInsertionIndex)
|
445
|
view.size += added;
|
446
|
if (view.Offset > realInsertionIndex || (view.Offset == realInsertionIndex && view.size > 0))
|
447
|
view.offset += added;
|
448
|
#endif
|
449
|
}
|
450
|
}
|
451
|
}
|
452
|
|
453
|
void fixViewsBeforeSingleRemove(Node node, int realRemovalIndex)
|
454
|
{
|
455
|
if (views != null)
|
456
|
foreach (LinkedList<T> view in views)
|
457
|
{
|
458
|
if (view != this)
|
459
|
{
|
460
|
#if HASHINDEX
|
461
|
if (view.startsentinel.precedes(node) && node.precedes(view.endsentinel))
|
462
|
view.size--;
|
463
|
if (!view.startsentinel.precedes(node))
|
464
|
view.offset--;
|
465
|
if (view.startsentinel == node)
|
466
|
view.startsentinel = node.prev;
|
467
|
if (view.endsentinel == node)
|
468
|
view.endsentinel = node.next;
|
469
|
#else
|
470
|
if (view.offset - 1 == realRemovalIndex)
|
471
|
view.startsentinel = node.prev;
|
472
|
if (view.offset + view.size == realRemovalIndex)
|
473
|
view.endsentinel = node.next;
|
474
|
if (view.offset <= realRemovalIndex && view.offset + view.size > realRemovalIndex)
|
475
|
view.size--;
|
476
|
if (view.offset > realRemovalIndex)
|
477
|
view.offset--;
|
478
|
#endif
|
479
|
}
|
480
|
}
|
481
|
}
|
482
|
|
483
|
#if HASHINDEX
|
484
|
#else
|
485
|
void fixViewsBeforeRemove(int start, int count, Node first, Node last)
|
486
|
{
|
487
|
int clearend = start + count - 1;
|
488
|
if (views != null)
|
489
|
foreach (LinkedList<T> view in views)
|
490
|
{
|
491
|
if (view == this)
|
492
|
continue;
|
493
|
int viewoffset = view.Offset, viewend = viewoffset + view.size - 1;
|
494
|
//sentinels
|
495
|
if (start < viewoffset && viewoffset - 1 <= clearend)
|
496
|
view.startsentinel = first.prev;
|
497
|
if (start <= viewend + 1 && viewend < clearend)
|
498
|
view.endsentinel = last.next;
|
499
|
//offsets and sizes
|
500
|
if (start < viewoffset)
|
501
|
{
|
502
|
if (clearend < viewoffset)
|
503
|
view.offset = viewoffset - count;
|
504
|
else
|
505
|
{
|
506
|
view.offset = start;
|
507
|
view.size = clearend < viewend ? viewend - clearend : 0;
|
508
|
}
|
509
|
}
|
510
|
else if (start <= viewend)
|
511
|
view.size = clearend <= viewend ? view.size - count : start - viewoffset;
|
512
|
}
|
513
|
}
|
514
|
#endif
|
515
|
|
516
|
/// <summary>
|
517
|
///
|
518
|
/// </summary>
|
519
|
/// <param name="otherView"></param>
|
520
|
/// <returns>The position of View(otherOffset, otherSize) wrt. this view</returns>
|
521
|
MutualViewPosition viewPosition(LinkedList<T> otherView)
|
522
|
{
|
523
|
#if HASHINDEX
|
524
|
Node otherstartsentinel = otherView.startsentinel, otherendsentinel = otherView.endsentinel,
|
525
|
first = startsentinel.next, last = endsentinel.prev,
|
526
|
otherfirst = otherstartsentinel.next, otherlast = otherendsentinel.prev;
|
527
|
if (last.precedes(otherfirst) || otherlast.precedes(first))
|
528
|
return MutualViewPosition.NonOverlapping;
|
529
|
if (size == 0 || (otherstartsentinel.precedes(first) && last.precedes(otherendsentinel)))
|
530
|
return MutualViewPosition.Contains;
|
531
|
if (otherView.size == 0 || (startsentinel.precedes(otherfirst) && otherlast.precedes(endsentinel)))
|
532
|
return MutualViewPosition.ContainedIn;
|
533
|
return MutualViewPosition.Overlapping;
|
534
|
#else
|
535
|
int end = offset + size, otherOffset = otherView.offset, otherSize = otherView.size, otherEnd = otherOffset + otherSize;
|
536
|
if (otherOffset >= end || otherEnd <= offset)
|
537
|
return MutualViewPosition.NonOverlapping;
|
538
|
if (size == 0 || (otherOffset <= offset && end <= otherEnd))
|
539
|
return MutualViewPosition.Contains;
|
540
|
if (otherSize == 0 || (offset <= otherOffset && otherEnd <= end))
|
541
|
return MutualViewPosition.ContainedIn;
|
542
|
return MutualViewPosition.Overlapping;
|
543
|
#endif
|
544
|
}
|
545
|
|
546
|
void disposeOverlappingViews(bool reverse)
|
547
|
{
|
548
|
if (views != null)
|
549
|
{
|
550
|
foreach (LinkedList<T> view in views)
|
551
|
{
|
552
|
if (view != this)
|
553
|
{
|
554
|
switch (viewPosition(view))
|
555
|
{
|
556
|
case MutualViewPosition.ContainedIn:
|
557
|
if (reverse)
|
558
|
{ }
|
559
|
else
|
560
|
view.Dispose();
|
561
|
break;
|
562
|
case MutualViewPosition.Overlapping:
|
563
|
view.Dispose();
|
564
|
break;
|
565
|
case MutualViewPosition.Contains:
|
566
|
case MutualViewPosition.NonOverlapping:
|
567
|
break;
|
568
|
}
|
569
|
}
|
570
|
}
|
571
|
}
|
572
|
}
|
573
|
|
574
|
#endregion
|
575
|
|
576
|
#endregion
|
577
|
|
578
|
#region Constructors
|
579
|
|
580
|
/// <summary>
|
581
|
/// Create a linked list with en external item equalityComparer
|
582
|
/// </summary>
|
583
|
/// <param name="itemequalityComparer">The external equalityComparer</param>
|
584
|
public LinkedList(SCG.IEqualityComparer<T> itemequalityComparer)
|
585
|
: base(itemequalityComparer)
|
586
|
{
|
587
|
offset = 0;
|
588
|
size = stamp = 0;
|
589
|
startsentinel = new Node(default(T));
|
590
|
endsentinel = new Node(default(T));
|
591
|
startsentinel.next = endsentinel;
|
592
|
endsentinel.prev = startsentinel;
|
593
|
#if HASHINDEX
|
594
|
//It is important that the sentinels are different:
|
595
|
startsentinel.taggroup = new TagGroup();
|
596
|
startsentinel.taggroup.tag = int.MinValue;
|
597
|
startsentinel.taggroup.count = 0;
|
598
|
endsentinel.taggroup = new TagGroup();
|
599
|
endsentinel.taggroup.tag = int.MaxValue;
|
600
|
endsentinel.taggroup.count = 0;
|
601
|
dict = new HashDictionary<T, Node>(itemequalityComparer);
|
602
|
#endif
|
603
|
}
|
604
|
|
605
|
/// <summary>
|
606
|
/// Create a linked list with the natural item equalityComparer
|
607
|
/// </summary>
|
608
|
public LinkedList() : this(EqualityComparer<T>.Default) { }
|
609
|
|
610
|
#endregion
|
611
|
|
612
|
#region Node nested class
|
613
|
|
614
|
/// <summary>
|
615
|
/// An individual cell in the linked list
|
616
|
/// </summary>
|
617
|
[Serializable]
|
618
|
class Node
|
619
|
{
|
620
|
public Node prev;
|
621
|
|
622
|
public Node next;
|
623
|
|
624
|
public T item;
|
625
|
|
626
|
#region Tag support
|
627
|
#if HASHINDEX
|
628
|
internal int tag;
|
629
|
|
630
|
internal TagGroup taggroup;
|
631
|
|
632
|
internal bool precedes(Node that)
|
633
|
{
|
634
|
//Debug.Assert(taggroup != null, "taggroup field null");
|
635
|
//Debug.Assert(that.taggroup != null, "that.taggroup field null");
|
636
|
int t1 = taggroup.tag;
|
637
|
int t2 = that.taggroup.tag;
|
638
|
|
639
|
return t1 < t2 ? true : t1 > t2 ? false : tag < that.tag;
|
640
|
}
|
641
|
#endif
|
642
|
#endregion
|
643
|
|
644
|
[Tested]
|
645
|
internal Node(T item) { this.item = item; }
|
646
|
|
647
|
[Tested]
|
648
|
internal Node(T item, Node prev, Node next)
|
649
|
{
|
650
|
this.item = item; this.prev = prev; this.next = next;
|
651
|
}
|
652
|
|
653
|
public override string ToString()
|
654
|
{
|
655
|
#if HASHINDEX
|
656
|
return String.Format("Node: (item={0}, tag={1})", item, tag);
|
657
|
#else
|
658
|
return String.Format("Node(item={0})", item);
|
659
|
#endif
|
660
|
}
|
661
|
}
|
662
|
|
663
|
#endregion
|
664
|
|
665
|
#region Taggroup nested class and tag maintenance utilities
|
666
|
#if HASHINDEX
|
667
|
/// <summary>
|
668
|
/// A group of nodes with the same high tag. Purpose is to be
|
669
|
/// able to tell the sequence order of two nodes without having to scan through
|
670
|
/// the list.
|
671
|
/// </summary>
|
672
|
[Serializable]
|
673
|
class TagGroup
|
674
|
{
|
675
|
internal int tag, count;
|
676
|
|
677
|
internal Node first, last;
|
678
|
|
679
|
/// <summary>
|
680
|
/// Pretty print a tag group
|
681
|
/// </summary>
|
682
|
/// <returns>Formatted tag group</returns>
|
683
|
public override string ToString()
|
684
|
{ return String.Format("TagGroup(tag={0}, cnt={1}, fst={2}, lst={3})", tag, count, first, last); }
|
685
|
}
|
686
|
|
687
|
//Constants for tag maintenance
|
688
|
const int wordsize = 32;
|
689
|
|
690
|
const int lobits = 3;
|
691
|
|
692
|
const int hibits = lobits + 1;
|
693
|
|
694
|
const int losize = 1 << lobits;
|
695
|
|
696
|
const int hisize = 1 << hibits;
|
697
|
|
698
|
const int logwordsize = 5;
|
699
|
|
700
|
TagGroup gettaggroup(Node pred, Node succ, out int lowbound, out int highbound)
|
701
|
{
|
702
|
TagGroup predgroup = pred.taggroup, succgroup = succ.taggroup;
|
703
|
|
704
|
if (predgroup == succgroup)
|
705
|
{
|
706
|
lowbound = pred.tag + 1;
|
707
|
highbound = succ.tag - 1;
|
708
|
return predgroup;
|
709
|
}
|
710
|
else if (predgroup.first != null)
|
711
|
{
|
712
|
lowbound = pred.tag + 1;
|
713
|
highbound = int.MaxValue;
|
714
|
return predgroup;
|
715
|
}
|
716
|
else if (succgroup.first != null)
|
717
|
{
|
718
|
lowbound = int.MinValue;
|
719
|
highbound = succ.tag - 1;
|
720
|
return succgroup;
|
721
|
}
|
722
|
else
|
723
|
{
|
724
|
lowbound = int.MinValue;
|
725
|
highbound = int.MaxValue;
|
726
|
return new TagGroup();
|
727
|
}
|
728
|
}
|
729
|
|
730
|
|
731
|
/// <summary>
|
732
|
/// Put a tag on a node (already inserted in the list). Split taggroups and renumber as
|
733
|
/// necessary.
|
734
|
/// </summary>
|
735
|
/// <param name="node">The node to tag</param>
|
736
|
void settag(Node node)
|
737
|
{
|
738
|
Node pred = node.prev, succ = node.next;
|
739
|
TagGroup predgroup = pred.taggroup, succgroup = succ.taggroup;
|
740
|
|
741
|
if (predgroup == succgroup)
|
742
|
{
|
743
|
node.taggroup = predgroup;
|
744
|
predgroup.count++;
|
745
|
if (pred.tag + 1 == succ.tag)
|
746
|
splittaggroup(predgroup);
|
747
|
else
|
748
|
node.tag = (pred.tag + 1) / 2 + (succ.tag - 1) / 2;
|
749
|
}
|
750
|
else if (predgroup.first != null)
|
751
|
{
|
752
|
node.taggroup = predgroup;
|
753
|
predgroup.last = node;
|
754
|
predgroup.count++;
|
755
|
if (pred.tag == int.MaxValue)
|
756
|
splittaggroup(predgroup);
|
757
|
else
|
758
|
node.tag = pred.tag / 2 + int.MaxValue / 2 + 1;
|
759
|
}
|
760
|
else if (succgroup.first != null)
|
761
|
{
|
762
|
node.taggroup = succgroup;
|
763
|
succgroup.first = node;
|
764
|
succgroup.count++;
|
765
|
if (succ.tag == int.MinValue)
|
766
|
splittaggroup(node.taggroup);
|
767
|
else
|
768
|
node.tag = int.MinValue / 2 + (succ.tag - 1) / 2;
|
769
|
}
|
770
|
else
|
771
|
{
|
772
|
Debug.Assert(Taggroups == 0);
|
773
|
|
774
|
TagGroup newgroup = new TagGroup();
|
775
|
|
776
|
Taggroups = 1;
|
777
|
node.taggroup = newgroup;
|
778
|
newgroup.first = newgroup.last = node;
|
779
|
newgroup.count = 1;
|
780
|
return;
|
781
|
}
|
782
|
}
|
783
|
|
784
|
|
785
|
/// <summary>
|
786
|
/// Remove a node from its taggroup.
|
787
|
/// <br/> When this is called, node must already have been removed from the underlying list
|
788
|
/// </summary>
|
789
|
/// <param name="node">The node to remove</param>
|
790
|
void removefromtaggroup(Node node)
|
791
|
{
|
792
|
|
793
|
TagGroup taggroup = node.taggroup;
|
794
|
|
795
|
if (--taggroup.count == 0)
|
796
|
{
|
797
|
Taggroups--;
|
798
|
return;
|
799
|
}
|
800
|
|
801
|
if (node == taggroup.first)
|
802
|
taggroup.first = node.next;
|
803
|
|
804
|
if (node == taggroup.last)
|
805
|
taggroup.last = node.prev;
|
806
|
|
807
|
//node.taggroup = null;
|
808
|
if (taggroup.count != losize || Taggroups == 1)
|
809
|
return;
|
810
|
|
811
|
TagGroup otg;
|
812
|
// bug20070911:
|
813
|
Node neighbor;
|
814
|
if ((neighbor = taggroup.first.prev) != startsentinel
|
815
|
&& (otg = neighbor.taggroup).count <= losize)
|
816
|
taggroup.first = otg.first;
|
817
|
else if ((neighbor = taggroup.last.next) != endsentinel
|
818
|
&& (otg = neighbor.taggroup).count <= losize)
|
819
|
taggroup.last = otg.last;
|
820
|
else
|
821
|
return;
|
822
|
|
823
|
Node n = otg.first;
|
824
|
|
825
|
for (int i = 0, length = otg.count; i < length; i++)
|
826
|
{
|
827
|
n.taggroup = taggroup;
|
828
|
n = n.next;
|
829
|
}
|
830
|
|
831
|
taggroup.count += otg.count;
|
832
|
Taggroups--;
|
833
|
n = taggroup.first;
|
834
|
|
835
|
const int ofs = wordsize - hibits;
|
836
|
|
837
|
for (int i = 0, count = taggroup.count; i < count; i++)
|
838
|
{
|
839
|
n.tag = (i - losize) << ofs; //(i-8)<<28
|
840
|
n = n.next;
|
841
|
}
|
842
|
}
|
843
|
|
844
|
|
845
|
/// <summary>
|
846
|
/// Split a tag group to make rom for more tags.
|
847
|
/// </summary>
|
848
|
/// <param name="taggroup">The tag group</param>
|
849
|
void splittaggroup(TagGroup taggroup)
|
850
|
{
|
851
|
Node n = taggroup.first;
|
852
|
int ptgt = taggroup.first.prev.taggroup.tag;
|
853
|
int ntgt = taggroup.last.next.taggroup.tag;
|
854
|
|
855
|
Debug.Assert(ptgt + 1 <= ntgt - 1);
|
856
|
|
857
|
int ofs = wordsize - hibits;
|
858
|
int newtgs = (taggroup.count - 1) / hisize;
|
859
|
int tgtdelta = (int)((ntgt + 0.0 - ptgt) / (newtgs + 2)), tgtag = ptgt;
|
860
|
|
861
|
tgtdelta = tgtdelta == 0 ? 1 : tgtdelta;
|
862
|
for (int j = 0; j < newtgs; j++)
|
863
|
{
|
864
|
TagGroup newtaggroup = new TagGroup();
|
865
|
|
866
|
newtaggroup.tag = (tgtag = tgtag >= ntgt - tgtdelta ? ntgt : tgtag + tgtdelta);
|
867
|
newtaggroup.first = n;
|
868
|
newtaggroup.count = hisize;
|
869
|
for (int i = 0; i < hisize; i++)
|
870
|
{
|
871
|
n.taggroup = newtaggroup;
|
872
|
n.tag = (i - losize) << ofs; //(i-8)<<28
|
873
|
n = n.next;
|
874
|
}
|
875
|
|
876
|
newtaggroup.last = n.prev;
|
877
|
}
|
878
|
|
879
|
int rest = taggroup.count - hisize * newtgs;
|
880
|
|
881
|
taggroup.first = n;
|
882
|
taggroup.count = rest;
|
883
|
taggroup.tag = (tgtag = tgtag >= ntgt - tgtdelta ? ntgt : tgtag + tgtdelta); ofs--;
|
884
|
for (int i = 0; i < rest; i++)
|
885
|
{
|
886
|
n.tag = (i - hisize) << ofs; //(i-16)<<27
|
887
|
n = n.next;
|
888
|
}
|
889
|
|
890
|
taggroup.last = n.prev;
|
891
|
Taggroups += newtgs;
|
892
|
if (tgtag == ntgt)
|
893
|
redistributetaggroups(taggroup);
|
894
|
}
|
895
|
|
896
|
|
897
|
private void redistributetaggroups(TagGroup taggroup)
|
898
|
{
|
899
|
TagGroup pred = taggroup, succ = taggroup, tmp;
|
900
|
double limit = 1, bigt = Math.Pow(Taggroups, 1.0 / 30);//?????
|
901
|
int bits = 1, count = 1, lowmask = 0, himask = 0, target = 0;
|
902
|
|
903
|
do
|
904
|
{
|
905
|
bits++;
|
906
|
lowmask = (1 << bits) - 1;
|
907
|
himask = ~lowmask;
|
908
|
target = taggroup.tag & himask;
|
909
|
while ((tmp = pred.first.prev.taggroup).first != null && (tmp.tag & himask) == target)
|
910
|
{ count++; pred = tmp; }
|
911
|
|
912
|
while ((tmp = succ.last.next.taggroup).last != null && (tmp.tag & himask) == target)
|
913
|
{ count++; succ = tmp; }
|
914
|
|
915
|
limit *= bigt;
|
916
|
} while (count > limit);
|
917
|
|
918
|
//redistibute tags
|
919
|
int lob = pred.first.prev.taggroup.tag, upb = succ.last.next.taggroup.tag;
|
920
|
int delta = upb / (count + 1) - lob / (count + 1);
|
921
|
|
922
|
Debug.Assert(delta > 0);
|
923
|
for (int i = 0; i < count; i++)
|
924
|
{
|
925
|
pred.tag = lob + (i + 1) * delta;
|
926
|
pred = pred.last.next.taggroup;
|
927
|
}
|
928
|
}
|
929
|
#endif
|
930
|
|
931
|
#endregion
|
932
|
|
933
|
#region Position, PositionComparer and ViewHandler nested types
|
934
|
class PositionComparer : SCG.IComparer<Position>
|
935
|
{
|
936
|
static PositionComparer _default;
|
937
|
PositionComparer() { }
|
938
|
public static PositionComparer Default { get { return _default ?? (_default = new PositionComparer()); } }
|
939
|
public int Compare(Position a, Position b)
|
940
|
{
|
941
|
#if HASHINDEX
|
942
|
return a.Endpoint == b.Endpoint ? 0 : a.Endpoint.precedes(b.Endpoint) ? -1 : 1;
|
943
|
#else
|
944
|
return a.Index.CompareTo(b.Index);
|
945
|
#endif
|
946
|
}
|
947
|
}
|
948
|
/// <summary>
|
949
|
/// During RemoveAll, we need to cache the original endpoint indices of views
|
950
|
/// </summary>
|
951
|
struct Position
|
952
|
{
|
953
|
public readonly LinkedList<T> View;
|
954
|
public bool Left;
|
955
|
#if HASHINDEX
|
956
|
public readonly Node Endpoint;
|
957
|
#else
|
958
|
public readonly int Index;
|
959
|
#endif
|
960
|
public Position(LinkedList<T> view, bool left)
|
961
|
{
|
962
|
View = view;
|
963
|
Left = left;
|
964
|
#if HASHINDEX
|
965
|
Endpoint = left ? view.startsentinel.next : view.endsentinel.prev;
|
966
|
#else
|
967
|
Index = left ? view.Offset : view.Offset + view.size - 1;
|
968
|
#endif
|
969
|
}
|
970
|
#if HASHINDEX
|
971
|
public Position(Node node, int foo) { this.Endpoint = node; View = null; Left = false; }
|
972
|
#else
|
973
|
public Position(int index) { this.Index = index; View = null; Left = false; }
|
974
|
#endif
|
975
|
}
|
976
|
|
977
|
//TODO: merge the two implementations using Position values as arguments
|
978
|
/// <summary>
|
979
|
/// Handle the update of (other) views during a multi-remove operation.
|
980
|
/// </summary>
|
981
|
struct ViewHandler
|
982
|
{
|
983
|
ArrayList<Position> leftEnds;
|
984
|
ArrayList<Position> rightEnds;
|
985
|
int leftEndIndex, rightEndIndex, leftEndIndex2, rightEndIndex2;
|
986
|
internal readonly int viewCount;
|
987
|
internal ViewHandler(LinkedList<T> list)
|
988
|
{
|
989
|
leftEndIndex = rightEndIndex = leftEndIndex2 = rightEndIndex2 = viewCount = 0;
|
990
|
leftEnds = rightEnds = null;
|
991
|
if (list.views != null)
|
992
|
foreach (LinkedList<T> v in list.views)
|
993
|
if (v != list)
|
994
|
{
|
995
|
if (leftEnds == null)
|
996
|
{
|
997
|
leftEnds = new ArrayList<Position>();
|
998
|
rightEnds = new ArrayList<Position>();
|
999
|
}
|
1000
|
leftEnds.Add(new Position(v, true));
|
1001
|
rightEnds.Add(new Position(v, false));
|
1002
|
}
|
1003
|
if (leftEnds == null)
|
1004
|
return;
|
1005
|
viewCount = leftEnds.Count;
|
1006
|
leftEnds.Sort(PositionComparer.Default);
|
1007
|
rightEnds.Sort(PositionComparer.Default);
|
1008
|
}
|
1009
|
#if HASHINDEX
|
1010
|
internal void skipEndpoints(int removed, Node n)
|
1011
|
{
|
1012
|
if (viewCount > 0)
|
1013
|
{
|
1014
|
Position endpoint;
|
1015
|
while (leftEndIndex < viewCount && ((endpoint = leftEnds[leftEndIndex]).Endpoint.prev.precedes(n)))
|
1016
|
{
|
1017
|
LinkedList<T> view = endpoint.View;
|
1018
|
view.offset = view.offset - removed;//TODO: extract offset.Value?
|
1019
|
view.size += removed;
|
1020
|
leftEndIndex++;
|
1021
|
}
|
1022
|
while (rightEndIndex < viewCount && (endpoint = rightEnds[rightEndIndex]).Endpoint.precedes(n))
|
1023
|
{
|
1024
|
LinkedList<T> view = endpoint.View;
|
1025
|
view.size -= removed;
|
1026
|
rightEndIndex++;
|
1027
|
}
|
1028
|
}
|
1029
|
if (viewCount > 0)
|
1030
|
{
|
1031
|
Position endpoint;
|
1032
|
while (leftEndIndex2 < viewCount && (endpoint = leftEnds[leftEndIndex2]).Endpoint.prev.precedes(n))
|
1033
|
leftEndIndex2++;
|
1034
|
while (rightEndIndex2 < viewCount && (endpoint = rightEnds[rightEndIndex2]).Endpoint.next.precedes(n))
|
1035
|
rightEndIndex2++;
|
1036
|
}
|
1037
|
}
|
1038
|
/// <summary>
|
1039
|
/// To be called with n pointing to the right of each node to be removed in a stretch.
|
1040
|
/// And at the endsentinel.
|
1041
|
///
|
1042
|
/// Update offset of a view whose left endpoint (has not already been handled and) is n or precedes n.
|
1043
|
/// I.e. startsentinel precedes n.
|
1044
|
/// Also update the size as a prelude to handling the right endpoint.
|
1045
|
///
|
1046
|
/// Update size of a view not already handled and whose right endpoint precedes n.
|
1047
|
/// </summary>
|
1048
|
/// <param name="removed">The number of nodes left of n to be removed</param>
|
1049
|
/// <param name="n"></param>
|
1050
|
internal void updateViewSizesAndCounts(int removed, Node n)
|
1051
|
{
|
1052
|
if (viewCount > 0)
|
1053
|
{
|
1054
|
Position endpoint;
|
1055
|
while (leftEndIndex < viewCount && ((endpoint = leftEnds[leftEndIndex]).Endpoint.prev.precedes(n)))
|
1056
|
{
|
1057
|
LinkedList<T> view = endpoint.View;
|
1058
|
view.offset = view.offset - removed; //TODO: fix use of offset
|
1059
|
view.size += removed;
|
1060
|
leftEndIndex++;
|
1061
|
}
|
1062
|
while (rightEndIndex < viewCount && (endpoint = rightEnds[rightEndIndex]).Endpoint.precedes(n))
|
1063
|
{
|
1064
|
LinkedList<T> view = endpoint.View;
|
1065
|
view.size -= removed;
|
1066
|
rightEndIndex++;
|
1067
|
}
|
1068
|
}
|
1069
|
}
|
1070
|
/// <summary>
|
1071
|
/// To be called with n being the first not-to-be-removed node after a (stretch of) node(s) to be removed.
|
1072
|
///
|
1073
|
/// It will update the startsentinel of views (that have not been handled before and)
|
1074
|
/// whose startsentinel precedes n, i.e. is to be deleted.
|
1075
|
///
|
1076
|
/// It will update the endsentinel of views (...) whose endsentinel precedes n, i.e. is to be deleted.
|
1077
|
///
|
1078
|
/// PROBLEM: DOESNT WORK AS ORIGINALLY ADVERTISED. WE MUST DO THIS BEFORE WE ACTUALLY REMOVE THE NODES. WHEN THE
|
1079
|
/// NODES HAVE BEEN REMOVED, THE precedes METHOD WILL NOT WORK!
|
1080
|
/// </summary>
|
1081
|
/// <param name="n"></param>
|
1082
|
/// <param name="newstart"></param>
|
1083
|
/// <param name="newend"></param>
|
1084
|
internal void updateSentinels(Node n, Node newstart, Node newend)
|
1085
|
{
|
1086
|
if (viewCount > 0)
|
1087
|
{
|
1088
|
Position endpoint;
|
1089
|
while (leftEndIndex2 < viewCount && (endpoint = leftEnds[leftEndIndex2]).Endpoint.prev.precedes(n))
|
1090
|
{
|
1091
|
LinkedList<T> view = endpoint.View;
|
1092
|
view.startsentinel = newstart;
|
1093
|
leftEndIndex2++;
|
1094
|
}
|
1095
|
while (rightEndIndex2 < viewCount && (endpoint = rightEnds[rightEndIndex2]).Endpoint.next.precedes(n))
|
1096
|
{
|
1097
|
LinkedList<T> view = endpoint.View;
|
1098
|
view.endsentinel = newend;
|
1099
|
rightEndIndex2++;
|
1100
|
}
|
1101
|
}
|
1102
|
}
|
1103
|
#else
|
1104
|
/// <summary>
|
1105
|
/// This is to be called with realindex pointing to the first node to be removed after a (stretch of) node that was not removed
|
1106
|
/// </summary>
|
1107
|
/// <param name="removed"></param>
|
1108
|
/// <param name="realindex"></param>
|
1109
|
internal void skipEndpoints(int removed, int realindex)
|
1110
|
{
|
1111
|
if (viewCount > 0)
|
1112
|
{
|
1113
|
Position endpoint;
|
1114
|
while (leftEndIndex < viewCount && (endpoint = leftEnds[leftEndIndex]).Index <= realindex)
|
1115
|
{
|
1116
|
LinkedList<T> view = endpoint.View;
|
1117
|
view.offset = view.offset - removed;
|
1118
|
view.size += removed;
|
1119
|
leftEndIndex++;
|
1120
|
}
|
1121
|
while (rightEndIndex < viewCount && (endpoint = rightEnds[rightEndIndex]).Index < realindex)
|
1122
|
{
|
1123
|
LinkedList<T> view = endpoint.View;
|
1124
|
view.size -= removed;
|
1125
|
rightEndIndex++;
|
1126
|
}
|
1127
|
}
|
1128
|
if (viewCount > 0)
|
1129
|
{
|
1130
|
Position endpoint;
|
1131
|
while (leftEndIndex2 < viewCount && (endpoint = leftEnds[leftEndIndex2]).Index <= realindex)
|
1132
|
leftEndIndex2++;
|
1133
|
while (rightEndIndex2 < viewCount && (endpoint = rightEnds[rightEndIndex2]).Index < realindex - 1)
|
1134
|
rightEndIndex2++;
|
1135
|
}
|
1136
|
}
|
1137
|
internal void updateViewSizesAndCounts(int removed, int realindex)
|
1138
|
{
|
1139
|
if (viewCount > 0)
|
1140
|
{
|
1141
|
Position endpoint;
|
1142
|
while (leftEndIndex < viewCount && (endpoint = leftEnds[leftEndIndex]).Index <= realindex)
|
1143
|
{
|
1144
|
LinkedList<T> view = endpoint.View;
|
1145
|
view.offset = view.Offset - removed;
|
1146
|
view.size += removed;
|
1147
|
leftEndIndex++;
|
1148
|
}
|
1149
|
while (rightEndIndex < viewCount && (endpoint = rightEnds[rightEndIndex]).Index < realindex)
|
1150
|
{
|
1151
|
LinkedList<T> view = endpoint.View;
|
1152
|
view.size -= removed;
|
1153
|
rightEndIndex++;
|
1154
|
}
|
1155
|
}
|
1156
|
}
|
1157
|
internal void updateSentinels(int realindex, Node newstart, Node newend)
|
1158
|
{
|
1159
|
if (viewCount > 0)
|
1160
|
{
|
1161
|
Position endpoint;
|
1162
|
while (leftEndIndex2 < viewCount && (endpoint = leftEnds[leftEndIndex2]).Index <= realindex)
|
1163
|
{
|
1164
|
LinkedList<T> view = endpoint.View;
|
1165
|
view.startsentinel = newstart;
|
1166
|
leftEndIndex2++;
|
1167
|
}
|
1168
|
while (rightEndIndex2 < viewCount && (endpoint = rightEnds[rightEndIndex2]).Index < realindex - 1)
|
1169
|
{
|
1170
|
LinkedList<T> view = endpoint.View;
|
1171
|
view.endsentinel = newend;
|
1172
|
rightEndIndex2++;
|
1173
|
}
|
1174
|
}
|
1175
|
}
|
1176
|
#endif
|
1177
|
}
|
1178
|
#endregion
|
1179
|
|
1180
|
#region Range nested class
|
1181
|
|
1182
|
class Range : DirectedCollectionValueBase<T>, IDirectedCollectionValue<T>
|
1183
|
{
|
1184
|
int start, count, rangestamp;
|
1185
|
Node startnode, endnode;
|
1186
|
|
1187
|
LinkedList<T> list;
|
1188
|
|
1189
|
bool forwards;
|
1190
|
|
1191
|
|
1192
|
internal Range(LinkedList<T> list, int start, int count, bool forwards)
|
1193
|
{
|
1194
|
this.list = list; this.rangestamp = list.underlying != null ? list.underlying.stamp : list.stamp;
|
1195
|
this.start = start; this.count = count; this.forwards = forwards;
|
1196
|
if (count > 0)
|
1197
|
{
|
1198
|
startnode = list.get(start);
|
1199
|
endnode = list.get(start + count - 1);
|
1200
|
}
|
1201
|
}
|
1202
|
|
1203
|
public override bool IsEmpty { get { list.modifycheck(rangestamp); return count == 0; } }
|
1204
|
|
1205
|
[Tested]
|
1206
|
public override int Count { [Tested]get { list.modifycheck(rangestamp); return count; } }
|
1207
|
|
1208
|
|
1209
|
public override Speed CountSpeed { get { list.modifycheck(rangestamp); return Speed.Constant; } }
|
1210
|
|
1211
|
|
1212
|
public override T Choose()
|
1213
|
{
|
1214
|
list.modifycheck(rangestamp);
|
1215
|
if (count > 0) return startnode.item;
|
1216
|
throw new NoSuchItemException();
|
1217
|
}
|
1218
|
|
1219
|
|
1220
|
[Tested]
|
1221
|
public override SCG.IEnumerator<T> GetEnumerator()
|
1222
|
{
|
1223
|
int togo = count;
|
1224
|
|
1225
|
list.modifycheck(rangestamp);
|
1226
|
if (togo == 0)
|
1227
|
yield break;
|
1228
|
|
1229
|
Node cursor = forwards ? startnode : endnode;
|
1230
|
|
1231
|
yield return cursor.item;
|
1232
|
while (--togo > 0)
|
1233
|
{
|
1234
|
cursor = forwards ? cursor.next : cursor.prev;
|
1235
|
list.modifycheck(rangestamp);
|
1236
|
yield return cursor.item;
|
1237
|
}
|
1238
|
}
|
1239
|
|
1240
|
|
1241
|
[Tested]
|
1242
|
public override IDirectedCollectionValue<T> Backwards()
|
1243
|
{
|
1244
|
list.modifycheck(rangestamp);
|
1245
|
|
1246
|
Range b = (Range)MemberwiseClone();
|
1247
|
|
1248
|
b.forwards = !forwards;
|
1249
|
return b;
|
1250
|
}
|
1251
|
|
1252
|
|
1253
|
[Tested]
|
1254
|
IDirectedEnumerable<T> IDirectedEnumerable<T>.Backwards() { return Backwards(); }
|
1255
|
|
1256
|
|
1257
|
[Tested]
|
1258
|
public override EnumerationDirection Direction
|
1259
|
{
|
1260
|
[Tested]
|
1261
|
get
|
1262
|
{ return forwards ? EnumerationDirection.Forwards : EnumerationDirection.Backwards; }
|
1263
|
}
|
1264
|
}
|
1265
|
|
1266
|
|
1267
|
#endregion
|
1268
|
|
1269
|
#region IDisposable Members
|
1270
|
|
1271
|
/// <summary>
|
1272
|
/// Invalidate this list. If a view, just invalidate the view.
|
1273
|
/// If not a view, invalidate the list and all views on it.
|
1274
|
/// </summary>
|
1275
|
public virtual void Dispose()
|
1276
|
{
|
1277
|
Dispose(false);
|
1278
|
}
|
1279
|
|
1280
|
void Dispose(bool disposingUnderlying)
|
1281
|
{
|
1282
|
if (isValid)
|
1283
|
{
|
1284
|
if (underlying != null)
|
1285
|
{
|
1286
|
isValid = false;
|
1287
|
if (!disposingUnderlying && views != null)
|
1288
|
views.Remove(myWeakReference);
|
1289
|
endsentinel = null;
|
1290
|
startsentinel = null;
|
1291
|
underlying = null;
|
1292
|
views = null;
|
1293
|
myWeakReference = null;
|
1294
|
}
|
1295
|
else
|
1296
|
{
|
1297
|
//isValid = false;
|
1298
|
//endsentinel = null;
|
1299
|
//startsentinel = null;
|
1300
|
if (views != null)
|
1301
|
foreach (LinkedList<T> view in views)
|
1302
|
view.Dispose(true);
|
1303
|
//views = null;
|
1304
|
Clear();
|
1305
|
}
|
1306
|
}
|
1307
|
}
|
1308
|
|
1309
|
#endregion IDisposable stuff
|
1310
|
|
1311
|
#region IList<T> Members
|
1312
|
|
1313
|
/// <summary>
|
1314
|
/// </summary>
|
1315
|
/// <exception cref="NoSuchItemException"> if this list is empty.</exception>
|
1316
|
/// <value>The first item in this list.</value>
|
1317
|
[Tested]
|
1318
|
public virtual T First
|
1319
|
{
|
1320
|
[Tested]
|
1321
|
get
|
1322
|
{
|
1323
|
validitycheck();
|
1324
|
if (size == 0)
|
1325
|
throw new NoSuchItemException();
|
1326
|
return startsentinel.next.item;
|
1327
|
}
|
1328
|
}
|
1329
|
|
1330
|
|
1331
|
/// <summary>
|
1332
|
/// </summary>
|
1333
|
/// <exception cref="NoSuchItemException"> if this list is empty.</exception>
|
1334
|
/// <value>The last item in this list.</value>
|
1335
|
[Tested]
|
1336
|
public virtual T Last
|
1337
|
{
|
1338
|
[Tested]
|
1339
|
get
|
1340
|
{
|
1341
|
validitycheck();
|
1342
|
if (size == 0)
|
1343
|
throw new NoSuchItemException();
|
1344
|
return endsentinel.prev.item;
|
1345
|
}
|
1346
|
}
|
1347
|
|
1348
|
/// <summary>
|
1349
|
/// Since <code>Add(T item)</code> always add at the end of the list,
|
1350
|
/// this describes if list has FIFO or LIFO semantics.
|
1351
|
/// </summary>
|
1352
|
/// <value>True if the <code>Remove()</code> operation removes from the
|
1353
|
/// start of the list, false if it removes from the end. THe default for a new linked list is true.</value>
|
1354
|
[Tested]
|
1355
|
public virtual bool FIFO
|
1356
|
{
|
1357
|
[Tested]
|
1358
|
get { validitycheck(); return fIFO; }
|
1359
|
[Tested]
|
1360
|
set { updatecheck(); fIFO = value; }
|
1361
|
}
|
1362
|
|
1363
|
/// <summary>
|
1364
|
///
|
1365
|
/// </summary>
|
1366
|
public virtual bool IsFixedSize
|
1367
|
{
|
1368
|
get { validitycheck(); return false; }
|
1369
|
}
|
1370
|
|
1371
|
/// <summary>
|
1372
|
/// On this list, this indexer is read/write.
|
1373
|
/// <exception cref="IndexOutOfRangeException"/> if i is negative or
|
1374
|
/// >= the size of the collection.
|
1375
|
/// </summary>
|
1376
|
/// <value>The i'th item of this list.</value>
|
1377
|
/// <param name="index">The index of the item to fetch or store.</param>
|
1378
|
[Tested]
|
1379
|
public virtual T this[int index]
|
1380
|
{
|
1381
|
[Tested]
|
1382
|
get { validitycheck(); return get(index).item; }
|
1383
|
[Tested]
|
1384
|
set
|
1385
|
{
|
1386
|
updatecheck();
|
1387
|
Node n = get(index);
|
1388
|
//
|
1389
|
T item = n.item;
|
1390
|
#if HASHINDEX
|
1391
|
|
1392
|
if (itemequalityComparer.Equals(value, item))
|
1393
|
{
|
1394
|
n.item = value;
|
1395
|
dict.Update(value, n);
|
1396
|
}
|
1397
|
else if (!dict.FindOrAdd(value, ref n))
|
1398
|
{
|
1399
|
dict.Remove(item);
|
1400
|
n.item = value;
|
1401
|
}
|
1402
|
else
|
1403
|
throw new ArgumentException("Item already in indexed list");
|
1404
|
#else
|
1405
|
n.item = value;
|
1406
|
#endif
|
1407
|
(underlying ?? this).raiseForSetThis(index, value, item);
|
1408
|
}
|
1409
|
}
|
1410
|
|
1411
|
/// <summary>
|
1412
|
///
|
1413
|
/// </summary>
|
1414
|
/// <value></value>
|
1415
|
public virtual Speed IndexingSpeed { get { return Speed.Linear; } }
|
1416
|
|
1417
|
/// <summary>
|
1418
|
/// Insert an item at a specific index location in this list.
|
1419
|
/// <exception cref="IndexOutOfRangeException"/> if i is negative or
|
1420
|
/// > the size of the collection.</summary>
|
1421
|
/// <param name="i">The index at which to insert.</param>
|
1422
|
/// <param name="item">The item to insert.</param>
|
1423
|
[Tested]
|
1424
|
public virtual void Insert(int i, T item)
|
1425
|
{
|
1426
|
updatecheck();
|
1427
|
insert(i, i == size ? endsentinel : get(i), item);
|
1428
|
if (ActiveEvents != EventTypeEnum.None)
|
1429
|
(underlying ?? this).raiseForInsert(i + Offset, item);
|
1430
|
}
|
1431
|
|
1432
|
/// <summary>
|
1433
|
/// Insert an item at the end of a compatible view, used as a pointer.
|
1434
|
/// <para>The <code>pointer</code> must be a view on the same list as
|
1435
|
/// <code>this</code> and the endpoitn of <code>pointer</code> must be
|
1436
|
/// a valid insertion point of <code>this</code></para>
|
1437
|
/// </summary>
|
1438
|
/// <exception cref="IncompatibleViewException">If <code>pointer</code>
|
1439
|
/// is not a view on the same list as <code>this</code></exception>
|
1440
|
/// <exception cref="IndexOutOfRangeException"><b>??????</b> if the endpoint of
|
1441
|
/// <code>pointer</code> is not inside <code>this</code></exception>
|
1442
|
/// <exception cref="DuplicateNotAllowedException"> if the list has
|
1443
|
/// <code>AllowsDuplicates==false</code> and the item is
|
1444
|
/// already in the list.</exception>
|
1445
|
/// <param name="pointer"></param>
|
1446
|
/// <param name="item"></param>
|
1447
|
public void Insert(IList<T> pointer, T item)
|
1448
|
{
|
1449
|
updatecheck();
|
1450
|
if ((pointer == null) || ((pointer.Underlying ?? pointer) != (underlying ?? this)))
|
1451
|
throw new IncompatibleViewException();
|
1452
|
#warning INEFFICIENT
|
1453
|
//TODO: make this efficient (the whole point of the method:
|
1454
|
//Do NOT use Insert, but insert the node at pointer.endsentinel, checking
|
1455
|
//via the ordering that this is a valid insertion point
|
1456
|
Insert(pointer.Offset + pointer.Count - Offset, item);
|
1457
|
}
|
1458
|
|
1459
|
/// <summary>
|
1460
|
/// Insert into this list all items from an enumerable collection starting
|
1461
|
/// at a particular index.
|
1462
|
/// <exception cref="IndexOutOfRangeException"/> if i is negative or
|
1463
|
/// > the size of the collection.
|
1464
|
/// </summary>
|
1465
|
/// <param name="i">Index to start inserting at</param>
|
1466
|
/// <param name="items">Items to insert</param>
|
1467
|
/// <typeparam name="U"></typeparam>
|
1468
|
[Tested]
|
1469
|
public virtual void InsertAll<U>(int i, SCG.IEnumerable<U> items) where U : T
|
1470
|
{
|
1471
|
insertAll(i, items, true);
|
1472
|
}
|
1473
|
|
1474
|
void insertAll<U>(int i, SCG.IEnumerable<U> items, bool insertion) where U : T
|
1475
|
{
|
1476
|
updatecheck();
|
1477
|
Node succ, node, pred;
|
1478
|
int count = 0;
|
1479
|
succ = i == size ? endsentinel : get(i);
|
1480
|
pred = node = succ.prev;
|
1481
|
#if HASHINDEX
|
1482
|
TagGroup taggroup = null;
|
1483
|
int taglimit = 0, thetag = 0;
|
1484
|
taggroup = gettaggroup(node, succ, out thetag, out taglimit);
|
1485
|
try
|
1486
|
{
|
1487
|
foreach (T item in items)
|
1488
|
{
|
1489
|
Node tmp = new Node(item, node, null);
|
1490
|
if (!dict.FindOrAdd(item, ref tmp))
|
1491
|
{
|
1492
|
tmp.tag = thetag < taglimit ? ++thetag : thetag;
|
1493
|
tmp.taggroup = taggroup;
|
1494
|
node.next = tmp;
|
1495
|
count++;
|
1496
|
node = tmp;
|
1497
|
}
|
1498
|
else
|
1499
|
throw new DuplicateNotAllowedException("Item already in indexed list");
|
1500
|
}
|
1501
|
}
|
1502
|
finally
|
1503
|
{
|
1504
|
if (count != 0)
|
1505
|
{
|
1506
|
taggroup.count += count;
|
1507
|
if (taggroup != pred.taggroup)
|
1508
|
taggroup.first = pred.next;
|
1509
|
if (taggroup != succ.taggroup)
|
1510
|
taggroup.last = node;
|
1511
|
succ.prev = node;
|
1512
|
node.next = succ;
|
1513
|
if (node.tag == node.prev.tag)
|
1514
|
splittaggroup(taggroup);
|
1515
|
size += count;
|
1516
|
if (underlying != null)
|
1517
|
underlying.size += count;
|
1518
|
fixViewsAfterInsert(succ, pred, count, 0);
|
1519
|
raiseForInsertAll(pred, i, count, insertion);
|
1520
|
}
|
1521
|
}
|
1522
|
#else
|
1523
|
foreach (T item in items)
|
1524
|
{
|
1525
|
Node tmp = new Node(item, node, null);
|
1526
|
node.next = tmp;
|
1527
|
count++;
|
1528
|
node = tmp;
|
1529
|
}
|
1530
|
if (count == 0)
|
1531
|
return;
|
1532
|
succ.prev = node;
|
1533
|
node.next = succ;
|
1534
|
size += count;
|
1535
|
if (underlying != null)
|
1536
|
underlying.size += count;
|
1537
|
if (count > 0)
|
1538
|
{
|
1539
|
fixViewsAfterInsert(succ, pred, count, offset + i);
|
1540
|
raiseForInsertAll(pred, i, count, insertion);
|
1541
|
}
|
1542
|
#endif
|
1543
|
}
|
1544
|
|
1545
|
private void raiseForInsertAll(Node node, int i, int added, bool insertion)
|
1546
|
{
|
1547
|
if (ActiveEvents != 0)
|
1548
|
{
|
1549
|
int index = Offset + i;
|
1550
|
if ((ActiveEvents & (EventTypeEnum.Added | EventTypeEnum.Inserted)) != 0)
|
1551
|
for (int j = index; j < index + added; j++)
|
1552
|
{
|
1553
|
#warning must we check stamps here?
|
1554
|
node = node.next;
|
1555
|
T item = node.item;
|
1556
|
if (insertion) raiseItemInserted(item, j);
|
1557
|
raiseItemsAdded(item, 1);
|
1558
|
}
|
1559
|
raiseCollectionChanged();
|
1560
|
}
|
1561
|
}
|
1562
|
|
1563
|
/// <summary>
|
1564
|
/// Insert an item at the front of this list.
|
1565
|
/// </summary>
|
1566
|
/// <param name="item">The item to insert.</param>
|
1567
|
[Tested]
|
1568
|
public virtual void InsertFirst(T item)
|
1569
|
{
|
1570
|
updatecheck();
|
1571
|
insert(0, startsentinel.next, item);
|
1572
|
if (ActiveEvents != EventTypeEnum.None)
|
1573
|
(underlying ?? this).raiseForInsert(0 + Offset, item);
|
1574
|
}
|
1575
|
|
1576
|
/// <summary>
|
1577
|
/// Insert an item at the back of this list.
|
1578
|
/// </summary>
|
1579
|
/// <param name="item">The item to insert.</param>
|
1580
|
[Tested]
|
1581
|
public virtual void InsertLast(T item)
|
1582
|
{
|
1583
|
updatecheck();
|
1584
|
insert(size, endsentinel, item);
|
1585
|
if (ActiveEvents != EventTypeEnum.None)
|
1586
|
(underlying ?? this).raiseForInsert(size - 1 + Offset, item);
|
1587
|
}
|
1588
|
|
1589
|
/// <summary>
|
1590
|
/// Create a new list consisting of the results of mapping all items of this
|
1591
|
/// list.
|
1592
|
/// </summary>
|
1593
|
/// <param name="mapper">The delegate defining the map.</param>
|
1594
|
/// <returns>The new list.</returns>
|
1595
|
[Tested]
|
1596
|
public IList<V> Map<V>(Fun<T, V> mapper)
|
1597
|
{
|
1598
|
validitycheck();
|
1599
|
|
1600
|
LinkedList<V> retval = new LinkedList<V>();
|
1601
|
return map<V>(mapper, retval);
|
1602
|
}
|
1603
|
|
1604
|
/// <summary>
|
1605
|
/// Create a new list consisting of the results of mapping all items of this
|
1606
|
/// list. The new list will use a specified equalityComparer for the item type.
|
1607
|
/// </summary>
|
1608
|
/// <typeparam name="V">The type of items of the new list</typeparam>
|
1609
|
/// <param name="mapper">The delegate defining the map.</param>
|
1610
|
/// <param name="equalityComparer">The equalityComparer to use for the new list</param>
|
1611
|
/// <returns>The new list.</returns>
|
1612
|
public IList<V> Map<V>(Fun<T, V> mapper, SCG.IEqualityComparer<V> equalityComparer)
|
1613
|
{
|
1614
|
validitycheck();
|
1615
|
|
1616
|
LinkedList<V> retval = new LinkedList<V>(equalityComparer);
|
1617
|
return map<V>(mapper, retval);
|
1618
|
}
|
1619
|
|
1620
|
private IList<V> map<V>(Fun<T, V> mapper, LinkedList<V> retval)
|
1621
|
{
|
1622
|
if (size == 0)
|
1623
|
return retval;
|
1624
|
int stamp = this.stamp;
|
1625
|
Node cursor = startsentinel.next;
|
1626
|
LinkedList<V>.Node mcursor = retval.startsentinel;
|
1627
|
|
1628
|
#if HASHINDEX
|
1629
|
double tagdelta = int.MaxValue / (size + 1.0);
|
1630
|
int count = 1;
|
1631
|
LinkedList<V>.TagGroup taggroup = null;
|
1632
|
taggroup = new LinkedList<V>.TagGroup();
|
1633
|
retval.taggroups = 1;
|
1634
|
taggroup.count = size;
|
1635
|
#endif
|
1636
|
while (cursor != endsentinel)
|
1637
|
{
|
1638
|
V v = mapper(cursor.item);
|
1639
|
modifycheck(stamp);
|
1640
|
mcursor.next = new LinkedList<V>.Node(v, mcursor, null);
|
1641
|
cursor = cursor.next;
|
1642
|
mcursor = mcursor.next;
|
1643
|
#if HASHINDEX
|
1644
|
retval.dict.Add(v, mcursor);
|
1645
|
mcursor.taggroup = taggroup;
|
1646
|
mcursor.tag = (int)(tagdelta * count++);
|
1647
|
#endif
|
1648
|
}
|
1649
|
|
1650
|
#if HASHINDEX
|
1651
|
taggroup.first = retval.startsentinel.next;
|
1652
|
taggroup.last = mcursor;
|
1653
|
#endif
|
1654
|
retval.endsentinel.prev = mcursor;
|
1655
|
mcursor.next = retval.endsentinel;
|
1656
|
retval.size = size;
|
1657
|
return retval;
|
1658
|
}
|
1659
|
|
1660
|
/// <summary>
|
1661
|
/// Remove one item from the list: from the front if <code>FIFO</code>
|
1662
|
/// is true, else from the back.
|
1663
|
/// <exception cref="NoSuchItemException"/> if this list is empty.
|
1664
|
/// </summary>
|
1665
|
/// <returns>The removed item.</returns>
|
1666
|
[Tested]
|
1667
|
public virtual T Remove()
|
1668
|
{
|
1669
|
updatecheck();
|
1670
|
if (size == 0)
|
1671
|
throw new NoSuchItemException("List is empty");
|
1672
|
T item = fIFO ? remove(startsentinel.next, 0) : remove(endsentinel.prev, size - 1);
|
1673
|
#if HASHINDEX
|
1674
|
dict.Remove(item);
|
1675
|
#endif
|
1676
|
(underlying ?? this).raiseForRemove(item);
|
1677
|
return item;
|
1678
|
}
|
1679
|
|
1680
|
/// <summary>
|
1681
|
/// Remove one item from the front of the list.
|
1682
|
/// <exception cref="NoSuchItemException"/> if this list is empty.
|
1683
|
/// </summary>
|
1684
|
/// <returns>The removed item.</returns>
|
1685
|
[Tested]
|
1686
|
public virtual T RemoveFirst()
|
1687
|
{
|
1688
|
updatecheck();
|
1689
|
if (size == 0)
|
1690
|
throw new NoSuchItemException("List is empty");
|
1691
|
|
1692
|
T item = remove(startsentinel.next, 0);
|
1693
|
#if HASHINDEX
|
1694
|
dict.Remove(item);
|
1695
|
#endif
|
1696
|
if (ActiveEvents != EventTypeEnum.None)
|
1697
|
(underlying ?? this).raiseForRemoveAt(Offset, item);
|
1698
|
return item;
|
1699
|
}
|
1700
|
|
1701
|
/// <summary>
|
1702
|
/// Remove one item from the back of the list.
|
1703
|
/// <exception cref="NoSuchItemException"/> if this list is empty.
|
1704
|
/// </summary>
|
1705
|
/// <returns>The removed item.</returns>
|
1706
|
[Tested]
|
1707
|
public virtual T RemoveLast()
|
1708
|
{
|
1709
|
updatecheck();
|
1710
|
if (size == 0)
|
1711
|
throw new NoSuchItemException("List is empty");
|
1712
|
|
1713
|
T item = remove(endsentinel.prev, size - 1);
|
1714
|
#if HASHINDEX
|
1715
|
dict.Remove(item);
|
1716
|
#endif
|
1717
|
if (ActiveEvents != EventTypeEnum.None)
|
1718
|
(underlying ?? this).raiseForRemoveAt(size + Offset, item);
|
1719
|
return item;
|
1720
|
}
|
1721
|
|
1722
|
/// <summary>
|
1723
|
/// Create a list view on this list.
|
1724
|
/// </summary>
|
1725
|
/// <exception cref="ArgumentOutOfRangeException"> if the start or count is negative</exception>
|
1726
|
/// <exception cref="ArgumentException"> if the range does not fit within list.</exception>
|
1727
|
/// <param name="start">The index in this list of the start of the view.</param>
|
1728
|
/// <param name="count">The size of the view.</param>
|
1729
|
/// <returns>The new list view.</returns>
|
1730
|
[Tested]
|
1731
|
public virtual IList<T> View(int start, int count)
|
1732
|
{
|
1733
|
checkRange(start, count);
|
1734
|
validitycheck();
|
1735
|
if (views == null)
|
1736
|
views = new WeakViewList<LinkedList<T>>();
|
1737
|
LinkedList<T> retval = (LinkedList<T>)MemberwiseClone();
|
1738
|
retval.underlying = underlying != null ? underlying : this;
|
1739
|
retval.offset = offset + start;
|
1740
|
retval.size = count;
|
1741
|
getPair(start - 1, start + count, out retval.startsentinel, out retval.endsentinel,
|
1742
|
new int[] { -1, size }, new Node[] { startsentinel, endsentinel });
|
1743
|
//retval.startsentinel = start == 0 ? startsentinel : get(start - 1);
|
1744
|
//retval.endsentinel = start + count == size ? endsentinel : get(start + count);
|
1745
|
|
1746
|
//TODO: for the purpose of Dispose, we need to retain a ref to the node
|
1747
|
retval.myWeakReference = views.Add(retval);
|
1748
|
return retval;
|
1749
|
}
|
1750
|
|
1751
|
/// <summary>
|
1752
|
/// Create a list view on this list containing the (first) occurrence of a particular item.
|
1753
|
/// </summary>
|
1754
|
/// <exception cref="ArgumentException"> if the item is not in this list.</exception>
|
1755
|
/// <param name="item">The item to find.</param>
|
1756
|
/// <returns>The new list view.</returns>
|
1757
|
public virtual IList<T> ViewOf(T item)
|
1758
|
{
|
1759
|
#if HASHINDEX
|
1760
|
Node n;
|
1761
|
validitycheck();
|
1762
|
if (!contains(item, out n))
|
1763
|
return null;
|
1764
|
LinkedList<T> retval = (LinkedList<T>)MemberwiseClone();
|
1765
|
retval.underlying = underlying != null ? underlying : this;
|
1766
|
retval.offset = null;
|
1767
|
retval.startsentinel = n.prev;
|
1768
|
retval.endsentinel = n.next;
|
1769
|
retval.size = 1;
|
1770
|
return retval;
|
1771
|
#else
|
1772
|
int index = 0;
|
1773
|
Node n = startsentinel.next;
|
1774
|
if (!find(item, ref n, ref index))
|
1775
|
return null;
|
1776
|
//TODO: optimize with getpair!
|
1777
|
return View(index, 1);
|
1778
|
#endif
|
1779
|
}
|
1780
|
|
1781
|
/// <summary>
|
1782
|
/// Create a list view on this list containing the last occurrence of a particular item.
|
1783
|
/// <exception cref="ArgumentException"/> if the item is not in this list.
|
1784
|
/// </summary>
|
1785
|
/// <param name="item">The item to find.</param>
|
1786
|
/// <returns>The new list view.</returns>
|
1787
|
public virtual IList<T> LastViewOf(T item)
|
1788
|
{
|
1789
|
#if HASHINDEX
|
1790
|
return ViewOf(item);
|
1791
|
#else
|
1792
|
int index = size - 1;
|
1793
|
Node n = endsentinel.prev;
|
1794
|
if (!dnif(item, ref n, ref index))
|
1795
|
return null;
|
1796
|
return View(index, 1);
|
1797
|
#endif
|
1798
|
}
|
1799
|
|
1800
|
/// <summary>
|
1801
|
/// Null if this list is not a view.
|
1802
|
/// </summary>
|
1803
|
/// <value>Underlying list for view.</value>
|
1804
|
[Tested]
|
1805
|
public virtual IList<T> Underlying { [Tested]get { validitycheck(); return underlying; } }
|
1806
|
|
1807
|
/// <summary>
|
1808
|
///
|
1809
|
/// </summary>
|
1810
|
/// <value></value>
|
1811
|
public virtual bool IsValid { get { return isValid; } }
|
1812
|
|
1813
|
/// <summary>
|
1814
|
/// </summary>
|
1815
|
/// <value>Offset for this list view or 0 for a underlying list.</value>
|
1816
|
[Tested]
|
1817
|
public virtual int Offset
|
1818
|
{
|
1819
|
[Tested]
|
1820
|
get
|
1821
|
{
|
1822
|
validitycheck();
|
1823
|
#if HASHINDEX
|
1824
|
if (offset == null && underlying != null)
|
1825
|
{
|
1826
|
//TODO: search from both ends simultaneously!
|
1827
|
Node n = underlying.startsentinel;
|
1828
|
int i = 0;
|
1829
|
while (n != startsentinel) { n = n.next; i++; }
|
1830
|
offset = i;
|
1831
|
}
|
1832
|
#endif
|
1833
|
return (int)offset;
|
1834
|
}
|
1835
|
}
|
1836
|
|
1837
|
/// <summary>
|
1838
|
/// Slide this list view along the underlying list.
|
1839
|
/// </summary>
|
1840
|
/// <exception cref="NotAViewException"> if this list is not a view.</exception>
|
1841
|
/// <exception cref="ArgumentOutOfRangeException"> if the operation
|
1842
|
/// would bring either end of the view outside the underlying list.</exception>
|
1843
|
/// <param name="offset">The signed amount to slide: positive to slide
|
1844
|
/// towards the end.</param>
|
1845
|
[Tested]
|
1846
|
public IList<T> Slide(int offset)
|
1847
|
{
|
1848
|
if (!TrySlide(offset, size))
|
1849
|
throw new ArgumentOutOfRangeException();
|
1850
|
return this;
|
1851
|
}
|
1852
|
|
1853
|
//TODO: more test cases
|
1854
|
/// <summary>
|
1855
|
/// Slide this list view along the underlying list, perhaps changing its size.
|
1856
|
/// </summary>
|
1857
|
/// <exception cref="NotAViewException"> if this list is not a view.</exception>
|
1858
|
/// <exception cref="ArgumentOutOfRangeException"> if the operation
|
1859
|
/// would bring either end of the view outside the underlying list.</exception>
|
1860
|
/// <param name="offset">The signed amount to slide: positive to slide
|
1861
|
/// towards the end.</param>
|
1862
|
/// <param name="size">The new size of the view.</param>
|
1863
|
public IList<T> Slide(int offset, int size)
|
1864
|
{
|
1865
|
if (!TrySlide(offset, size))
|
1866
|
throw new ArgumentOutOfRangeException();
|
1867
|
return this;
|
1868
|
}
|
1869
|
|
1870
|
/// <summary>
|
1871
|
///
|
1872
|
/// </summary>
|
1873
|
/// <param name="offset"></param>
|
1874
|
/// <returns></returns>
|
1875
|
public virtual bool TrySlide(int offset) { return TrySlide(offset, size); }
|
1876
|
|
1877
|
/// <summary>
|
1878
|
///
|
1879
|
/// </summary>
|
1880
|
/// <param name="offset"></param>
|
1881
|
/// <param name="size"></param>
|
1882
|
/// <returns></returns>
|
1883
|
public virtual bool TrySlide(int offset, int size)
|
1884
|
{
|
1885
|
updatecheck();
|
1886
|
if (underlying == null)
|
1887
|
throw new NotAViewException("List not a view");
|
1888
|
|
1889
|
#pragma warning disable 472
|
1890
|
if (this.offset == null) //Note: only possible with HASHINDEX
|
1891
|
#pragma warning restore 472
|
1892
|
{
|
1893
|
#pragma warning disable 162
|
1894
|
try
|
1895
|
{
|
1896
|
getPair(offset - 1, offset + size, out startsentinel, out endsentinel,
|
1897
|
new int[] { -1, this.size }, new Node[] { startsentinel, endsentinel });
|
1898
|
//TODO: maybe-update offset field
|
1899
|
}
|
1900
|
catch (NullReferenceException)
|
1901
|
{
|
1902
|
return false;
|
1903
|
}
|
1904
|
#pragma warning restore 162
|
1905
|
}
|
1906
|
else
|
1907
|
{
|
1908
|
if (offset + this.offset < 0 || offset + this.offset + size > underlying.size)
|
1909
|
return false;
|
1910
|
int oldoffset = (int)(this.offset);
|
1911
|
getPair(offset - 1, offset + size, out startsentinel, out endsentinel,
|
1912
|
new int[] { -oldoffset - 1, -1, this.size, underlying.size - oldoffset },
|
1913
|
new Node[] { underlying.startsentinel, startsentinel, endsentinel, underlying.endsentinel });
|
1914
|
}
|
1915
|
this.size = size;
|
1916
|
this.offset += offset;
|
1917
|
return true;
|
1918
|
}
|
1919
|
|
1920
|
|
1921
|
//TODO: improve the complexity of the implementation
|
1922
|
/// <summary>
|
1923
|
///
|
1924
|
/// <para>Returns null if <code>otherView</code> is strictly to the left of this view</para>
|
1925
|
/// </summary>
|
1926
|
/// <param name="otherView"></param>
|
1927
|
/// <exception cref="IncompatibleViewException">If otherView does not have the same underlying list as this</exception>
|
1928
|
/// <returns></returns>
|
1929
|
public virtual IList<T> Span(IList<T> otherView)
|
1930
|
{
|
1931
|
if ((otherView == null) || ((otherView.Underlying ?? otherView) != (underlying ?? this)))
|
1932
|
throw new IncompatibleViewException();
|
1933
|
if (otherView.Offset + otherView.Count - Offset < 0)
|
1934
|
return null;
|
1935
|
return (underlying ?? this).View(Offset, otherView.Offset + otherView.Count - Offset);
|
1936
|
}
|
1937
|
|
1938
|
|
1939
|
//Question: should we swap items or move nodes around?
|
1940
|
//The first seems much more efficient unless the items are value types
|
1941
|
//with a large memory footprint.
|
1942
|
//(Swapping will do count*3/2 T assignments, linking around will do
|
1943
|
// 4*count ref assignments; note that ref assignments are more expensive
|
1944
|
//than copying non-ref bits)
|
1945
|
/// <summary>
|
1946
|
/// Reverse the list so the items are in the opposite sequence order.
|
1947
|
/// </summary>
|
1948
|
[Tested]
|
1949
|
public virtual void Reverse()
|
1950
|
{
|
1951
|
updatecheck();
|
1952
|
if (size == 0)
|
1953
|
return;
|
1954
|
|
1955
|
Position[] positions = null;
|
1956
|
int poslow = 0, poshigh = 0;
|
1957
|
if (views != null)
|
1958
|
{
|
1959
|
CircularQueue<Position> _positions = null;
|
1960
|
foreach (LinkedList<T> view in views)
|
1961
|
{
|
1962
|
if (view != this)
|
1963
|
{
|
1964
|
switch (viewPosition(view))
|
1965
|
{
|
1966
|
case MutualViewPosition.ContainedIn:
|
1967
|
(_positions ?? (_positions = new CircularQueue<Position>())).Enqueue(new Position(view, true));
|
1968
|
_positions.Enqueue(new Position(view, false));
|
1969
|
break;
|
1970
|
case MutualViewPosition.Overlapping:
|
1971
|
view.Dispose();
|
1972
|
break;
|
1973
|
case MutualViewPosition.Contains:
|
1974
|
case MutualViewPosition.NonOverlapping:
|
1975
|
break;
|
1976
|
}
|
1977
|
}
|
1978
|
}
|
1979
|
if (_positions != null)
|
1980
|
{
|
1981
|
positions = _positions.ToArray();
|
1982
|
Sorting.IntroSort<Position>(positions, 0, positions.Length, PositionComparer.Default);
|
1983
|
poshigh = positions.Length - 1;
|
1984
|
}
|
1985
|
}
|
1986
|
|
1987
|
Node a = get(0), b = get(size - 1);
|
1988
|
for (int i = 0; i < size / 2; i++)
|
1989
|
{
|
1990
|
T swap;
|
1991
|
swap = a.item; a.item = b.item; b.item = swap;
|
1992
|
#if HASHINDEX
|
1993
|
dict[a.item] = a; dict[b.item] = b;
|
1994
|
#endif
|
1995
|
if (positions != null)
|
1996
|
mirrorViewSentinelsForReverse(positions, ref poslow, ref poshigh, a, b, i);
|
1997
|
a = a.next; b = b.prev;
|
1998
|
}
|
1999
|
if (positions != null && size % 2 != 0)
|
2000
|
mirrorViewSentinelsForReverse(positions, ref poslow, ref poshigh, a, b, size / 2);
|
2001
|
(underlying ?? this).raiseCollectionChanged();
|
2002
|
}
|
2003
|
|
2004
|
private void mirrorViewSentinelsForReverse(Position[] positions, ref int poslow, ref int poshigh, Node a, Node b, int i)
|
2005
|
{
|
2006
|
#if HASHINDEX
|
2007
|
int? aindex = offset + i, bindex = offset + size - 1 - i;
|
2008
|
#else
|
2009
|
int aindex = offset + i, bindex = offset + size - 1 - i;
|
2010
|
#endif
|
2011
|
Position pos;
|
2012
|
#if HASHINDEX
|
2013
|
while (poslow <= poshigh && (pos = positions[poslow]).Endpoint == a)
|
2014
|
#else
|
2015
|
while (poslow <= poshigh && (pos = positions[poslow]).Index == aindex)
|
2016
|
#endif
|
2017
|
{
|
2018
|
//TODO: Note: in the case og hashed linked list, if this.offset == null, but pos.View.offset!=null
|
2019
|
//we may at this point compute this.offset and non-null values of aindex and bindex
|
2020
|
if (pos.Left)
|
2021
|
pos.View.endsentinel = b.next;
|
2022
|
else
|
2023
|
{
|
2024
|
pos.View.startsentinel = b.prev;
|
2025
|
pos.View.offset = bindex;
|
2026
|
}
|
2027
|
poslow++;
|
2028
|
}
|
2029
|
#if HASHINDEX
|
2030
|
while (poslow < poshigh && (pos = positions[poshigh]).Endpoint == b)
|
2031
|
#else
|
2032
|
while (poslow < poshigh && (pos = positions[poshigh]).Index == bindex)
|
2033
|
#endif
|
2034
|
{
|
2035
|
if (pos.Left)
|
2036
|
pos.View.endsentinel = a.next;
|
2037
|
else
|
2038
|
{
|
2039
|
pos.View.startsentinel = a.prev;
|
2040
|
pos.View.offset = aindex;
|
2041
|
}
|
2042
|
poshigh--;
|
2043
|
}
|
2044
|
}
|
2045
|
|
2046
|
/// <summary>
|
2047
|
/// Check if this list is sorted according to the default sorting order
|
2048
|
/// for the item type T, as defined by the <see cref="T:C5.Comparer`1"/> class
|
2049
|
/// </summary>
|
2050
|
/// <exception cref="NotComparableException">if T is not comparable</exception>
|
2051
|
/// <returns>True if the list is sorted, else false.</returns>
|
2052
|
public bool IsSorted() { return IsSorted(Comparer<T>.Default); }
|
2053
|
|
2054
|
/// <summary>
|
2055
|
/// Check if this list is sorted according to a specific sorting order.
|
2056
|
/// </summary>
|
2057
|
/// <param name="c">The comparer defining the sorting order.</param>
|
2058
|
/// <returns>True if the list is sorted, else false.</returns>
|
2059
|
[Tested]
|
2060
|
public virtual bool IsSorted(SCG.IComparer<T> c)
|
2061
|
{
|
2062
|
validitycheck();
|
2063
|
if (size <= 1)
|
2064
|
return true;
|
2065
|
|
2066
|
Node node = startsentinel.next;
|
2067
|
T prevItem = node.item;
|
2068
|
|
2069
|
node = node.next;
|
2070
|
while (node != endsentinel)
|
2071
|
{
|
2072
|
if (c.Compare(prevItem, node.item) > 0)
|
2073
|
return false;
|
2074
|
else
|
2075
|
{
|
2076
|
prevItem = node.item;
|
2077
|
node = node.next;
|
2078
|
}
|
2079
|
}
|
2080
|
|
2081
|
return true;
|
2082
|
}
|
2083
|
|
2084
|
/// <summary>
|
2085
|
/// Sort the items of the list according to the default sorting order
|
2086
|
/// for the item type T, as defined by the Comparer[T] class.
|
2087
|
/// (<see cref="T:C5.Comparer`1"/>).
|
2088
|
/// The sorting is stable.
|
2089
|
/// </summary>
|
2090
|
/// <exception cref="InvalidOperationException">if T is not comparable</exception>
|
2091
|
public virtual void Sort() { Sort(Comparer<T>.Default); }
|
2092
|
|
2093
|
// Sort the linked list using mergesort
|
2094
|
/// <summary>
|
2095
|
/// Sort the items of the list according to a specific sorting order.
|
2096
|
/// The sorting is stable.
|
2097
|
/// </summary>
|
2098
|
/// <param name="c">The comparer defining the sorting order.</param>
|
2099
|
[Tested]
|
2100
|
public virtual void Sort(SCG.IComparer<T> c)
|
2101
|
{
|
2102
|
updatecheck();
|
2103
|
if (size == 0)
|
2104
|
return;
|
2105
|
disposeOverlappingViews(false);
|
2106
|
#if HASHINDEX
|
2107
|
if (underlying != null)
|
2108
|
{
|
2109
|
Node cursor = startsentinel.next;
|
2110
|
while (cursor != endsentinel)
|
2111
|
{
|
2112
|
cursor.taggroup.count--;
|
2113
|
cursor = cursor.next;
|
2114
|
}
|
2115
|
}
|
2116
|
#endif
|
2117
|
// Build a linked list of non-empty runs.
|
2118
|
// The prev field in first node of a run points to next run's first node
|
2119
|
Node runTail = startsentinel.next;
|
2120
|
Node prevNode = startsentinel.next;
|
2121
|
|
2122
|
endsentinel.prev.next = null;
|
2123
|
while (prevNode != null)
|
2124
|
{
|
2125
|
Node node = prevNode.next;
|
2126
|
|
2127
|
while (node != null && c.Compare(prevNode.item, node.item) <= 0)
|
2128
|
{
|
2129
|
prevNode = node;
|
2130
|
node = prevNode.next;
|
2131
|
}
|
2132
|
|
2133
|
// Completed a run; prevNode is the last node of that run
|
2134
|
prevNode.next = null; // Finish the run
|
2135
|
runTail.prev = node; // Link it into the chain of runs
|
2136
|
runTail = node;
|
2137
|
if (c.Compare(endsentinel.prev.item, prevNode.item) <= 0)
|
2138
|
endsentinel.prev = prevNode; // Update last pointer to point to largest
|
2139
|
|
2140
|
prevNode = node; // Start a new run
|
2141
|
}
|
2142
|
|
2143
|
// Repeatedly merge runs two and two, until only one run remains
|
2144
|
while (startsentinel.next.prev != null)
|
2145
|
{
|
2146
|
Node run = startsentinel.next;
|
2147
|
Node newRunTail = null;
|
2148
|
|
2149
|
while (run != null && run.prev != null)
|
2150
|
{ // At least two runs, merge
|
2151
|
Node nextRun = run.prev.prev;
|
2152
|
Node newrun = mergeRuns(run, run.prev, c);
|
2153
|
|
2154
|
if (newRunTail != null)
|
2155
|
newRunTail.prev = newrun;
|
2156
|
else
|
2157
|
startsentinel.next = newrun;
|
2158
|
|
2159
|
newRunTail = newrun;
|
2160
|
run = nextRun;
|
2161
|
}
|
2162
|
|
2163
|
if (run != null) // Add the last run, if any
|
2164
|
newRunTail.prev = run;
|
2165
|
}
|
2166
|
|
2167
|
endsentinel.prev.next = endsentinel;
|
2168
|
startsentinel.next.prev = startsentinel;
|
2169
|
|
2170
|
//assert invariant();
|
2171
|
//assert isSorted();
|
2172
|
#if HASHINDEX
|
2173
|
{
|
2174
|
Node cursor = startsentinel.next, end = endsentinel;
|
2175
|
int tag, taglimit;
|
2176
|
TagGroup t = gettaggroup(startsentinel, endsentinel, out tag, out taglimit);
|
2177
|
int tagdelta = taglimit / (size + 1) - tag / (size + 1);
|
2178
|
tagdelta = tagdelta == 0 ? 1 : tagdelta;
|
2179
|
if (underlying == null)
|
2180
|
taggroups = 1;
|
2181
|
while (cursor != end)
|
2182
|
{
|
2183
|
tag = tag + tagdelta > taglimit ? taglimit : tag + tagdelta;
|
2184
|
cursor.tag = tag;
|
2185
|
t.count++;
|
2186
|
cursor.taggroup = t;
|
2187
|
cursor = cursor.next;
|
2188
|
}
|
2189
|
if (t != startsentinel.taggroup)
|
2190
|
t.first = startsentinel.next;
|
2191
|
if (t != endsentinel.taggroup)
|
2192
|
t.last = endsentinel.prev;
|
2193
|
if (tag == taglimit)
|
2194
|
splittaggroup(t);
|
2195
|
}
|
2196
|
#endif
|
2197
|
(underlying ?? this).raiseCollectionChanged();
|
2198
|
}
|
2199
|
|
2200
|
private static Node mergeRuns(Node run1, Node run2, SCG.IComparer<T> c)
|
2201
|
{
|
2202
|
//assert run1 != null && run2 != null;
|
2203
|
Node prev;
|
2204
|
bool prev1; // is prev from run1?
|
2205
|
|
2206
|
if (c.Compare(run1.item, run2.item) <= 0)
|
2207
|
{
|
2208
|
prev = run1;
|
2209
|
prev1 = true;
|
2210
|
run1 = run1.next;
|
2211
|
}
|
2212
|
else
|
2213
|
{
|
2214
|
prev = run2;
|
2215
|
prev1 = false;
|
2216
|
run2 = run2.next;
|
2217
|
}
|
2218
|
|
2219
|
Node start = prev;
|
2220
|
|
2221
|
//assert start != null;
|
2222
|
start.prev = null;
|
2223
|
while (run1 != null && run2 != null)
|
2224
|
{
|
2225
|
if (prev1)
|
2226
|
{
|
2227
|
//assert prev.next == run1;
|
2228
|
//Comparable run2item = (Comparable)run2.item;
|
2229
|
while (run1 != null && c.Compare(run2.item, run1.item) >= 0)
|
2230
|
{
|
2231
|
prev = run1;
|
2232
|
run1 = prev.next;
|
2233
|
}
|
2234
|
|
2235
|
if (run1 != null)
|
2236
|
{ // prev.item <= run2.item < run1.item; insert run2
|
2237
|
prev.next = run2;
|
2238
|
run2.prev = prev;
|
2239
|
prev = run2;
|
2240
|
run2 = prev.next;
|
2241
|
prev1 = false;
|
2242
|
}
|
2243
|
}
|
2244
|
else
|
2245
|
{
|
2246
|
//assert prev.next == run2;
|
2247
|
//Comparable run1item = (Comparable)run1.item;
|
2248
|
while (run2 != null && c.Compare(run1.item, run2.item) > 0)
|
2249
|
{
|
2250
|
prev = run2;
|
2251
|
run2 = prev.next;
|
2252
|
}
|
2253
|
|
2254
|
if (run2 != null)
|
2255
|
{ // prev.item < run1.item <= run2.item; insert run1
|
2256
|
prev.next = run1;
|
2257
|
run1.prev = prev;
|
2258
|
prev = run1;
|
2259
|
run1 = prev.next;
|
2260
|
prev1 = true;
|
2261
|
}
|
2262
|
}
|
2263
|
}
|
2264
|
|
2265
|
//assert !(run1 != null && prev1) && !(run2 != null && !prev1);
|
2266
|
if (run1 != null)
|
2267
|
{ // last run2 < all of run1; attach run1 at end
|
2268
|
prev.next = run1;
|
2269
|
run1.prev = prev;
|
2270
|
}
|
2271
|
else if (run2 != null)
|
2272
|
{ // last run1
|
2273
|
prev.next = run2;
|
2274
|
run2.prev = prev;
|
2275
|
}
|
2276
|
|
2277
|
return start;
|
2278
|
}
|
2279
|
|
2280
|
/// <summary>
|
2281
|
/// Randomly shuffle the items of this list.
|
2282
|
/// <para>Will invalidate overlapping views???</para>
|
2283
|
/// </summary>
|
2284
|
public virtual void Shuffle() { Shuffle(new C5Random()); }
|
2285
|
|
2286
|
|
2287
|
/// <summary>
|
2288
|
/// Shuffle the items of this list according to a specific random source.
|
2289
|
/// <para>Will invalidate overlapping views???</para>
|
2290
|
/// </summary>
|
2291
|
/// <param name="rnd">The random source.</param>
|
2292
|
public virtual void Shuffle(Random rnd)
|
2293
|
{
|
2294
|
updatecheck();
|
2295
|
if (size == 0)
|
2296
|
return;
|
2297
|
disposeOverlappingViews(false);
|
2298
|
ArrayList<T> a = new ArrayList<T>();
|
2299
|
a.AddAll(this);
|
2300
|
a.Shuffle(rnd);
|
2301
|
Node cursor = startsentinel.next;
|
2302
|
int j = 0;
|
2303
|
while (cursor != endsentinel)
|
2304
|
{
|
2305
|
cursor.item = a[j++];
|
2306
|
#if HASHINDEX
|
2307
|
dict[cursor.item] = cursor;
|
2308
|
#endif
|
2309
|
cursor = cursor.next;
|
2310
|
}
|
2311
|
(underlying ?? this).raiseCollectionChanged();
|
2312
|
}
|
2313
|
|
2314
|
#endregion
|
2315
|
|
2316
|
#region IIndexed<T> Members
|
2317
|
|
2318
|
/// <summary>
|
2319
|
/// <exception cref="IndexOutOfRangeException"/>.
|
2320
|
/// </summary>
|
2321
|
/// <value>The directed collection of items in a specific index interval.</value>
|
2322
|
/// <param name="start">The low index of the interval (inclusive).</param>
|
2323
|
/// <param name="count">The size of the range.</param>
|
2324
|
[Tested]
|
2325
|
public IDirectedCollectionValue<T> this[int start, int count]
|
2326
|
{
|
2327
|
[Tested]
|
2328
|
get
|
2329
|
{
|
2330
|
validitycheck();
|
2331
|
checkRange(start, count);
|
2332
|
return new Range(this, start, count, true);
|
2333
|
}
|
2334
|
}
|
2335
|
|
2336
|
/// <summary>
|
2337
|
/// Searches for an item in the list going forwrds from the start.
|
2338
|
/// </summary>
|
2339
|
/// <param name="item">Item to search for.</param>
|
2340
|
/// <returns>Index of item from start.</returns>
|
2341
|
[Tested]
|
2342
|
public virtual int IndexOf(T item)
|
2343
|
{
|
2344
|
validitycheck();
|
2345
|
Node node;
|
2346
|
#if HASHINDEX
|
2347
|
if (!dict.Find(item, out node) || !insideview(node))
|
2348
|
return ~size;
|
2349
|
#endif
|
2350
|
node = startsentinel.next;
|
2351
|
int index = 0;
|
2352
|
if (find(item, ref node, ref index))
|
2353
|
return index;
|
2354
|
else
|
2355
|
return ~size;
|
2356
|
}
|
2357
|
|
2358
|
/// <summary>
|
2359
|
/// Searches for an item in the list going backwords from the end.
|
2360
|
/// </summary>
|
2361
|
/// <param name="item">Item to search for.</param>
|
2362
|
/// <returns>Index of of item from the end.</returns>
|
2363
|
[Tested]
|
2364
|
public virtual int LastIndexOf(T item)
|
2365
|
{
|
2366
|
#if HASHINDEX
|
2367
|
return IndexOf(item);
|
2368
|
#else
|
2369
|
validitycheck();
|
2370
|
|
2371
|
Node node = endsentinel.prev;
|
2372
|
int index = size - 1;
|
2373
|
|
2374
|
if (dnif(item, ref node, ref index))
|
2375
|
return index;
|
2376
|
else
|
2377
|
return ~size;
|
2378
|
#endif
|
2379
|
}
|
2380
|
|
2381
|
/// <summary>
|
2382
|
/// Remove the item at a specific position of the list.
|
2383
|
/// <exception cref="IndexOutOfRangeException"/> if i is negative or
|
2384
|
/// >= the size of the collection.
|
2385
|
/// </summary>
|
2386
|
/// <param name="i">The index of the item to remove.</param>
|
2387
|
/// <returns>The removed item.</returns>
|
2388
|
[Tested]
|
2389
|
public virtual T RemoveAt(int i)
|
2390
|
{
|
2391
|
updatecheck();
|
2392
|
T retval = remove(get(i), i);
|
2393
|
#if HASHINDEX
|
2394
|
dict.Remove(retval);
|
2395
|
#endif
|
2396
|
if (ActiveEvents != EventTypeEnum.None)
|
2397
|
(underlying ?? this).raiseForRemoveAt(Offset + i, retval);
|
2398
|
return retval;
|
2399
|
}
|
2400
|
|
2401
|
/// <summary>
|
2402
|
/// Remove all items in an index interval.
|
2403
|
/// <exception cref="IndexOutOfRangeException"/>???.
|
2404
|
/// </summary>
|
2405
|
/// <param name="start">The index of the first item to remove.</param>
|
2406
|
/// <param name="count">The number of items to remove.</param>
|
2407
|
[Tested]
|
2408
|
public virtual void RemoveInterval(int start, int count)
|
2409
|
{
|
2410
|
#if HASHINDEX
|
2411
|
updatecheck();
|
2412
|
checkRange(start, count);
|
2413
|
if (count == 0)
|
2414
|
return;
|
2415
|
|
2416
|
View(start, count).Clear();
|
2417
|
#else
|
2418
|
//Note: this is really almost equaivalent to Clear on a view
|
2419
|
updatecheck();
|
2420
|
checkRange(start, count);
|
2421
|
if (count == 0)
|
2422
|
return;
|
2423
|
|
2424
|
//for small count: optimize
|
2425
|
//use an optimal get(int i, int j, ref Node ni, ref Node nj)?
|
2426
|
Node a = get(start), b = get(start + count - 1);
|
2427
|
fixViewsBeforeRemove(start, count, a, b);
|
2428
|
a.prev.next = b.next;
|
2429
|
b.next.prev = a.prev;
|
2430
|
if (underlying != null)
|
2431
|
underlying.size -= count;
|
2432
|
|
2433
|
size -= count;
|
2434
|
if (ActiveEvents != EventTypeEnum.None)
|
2435
|
(underlying ?? this).raiseForRemoveInterval(start + Offset, count);
|
2436
|
#endif
|
2437
|
}
|
2438
|
|
2439
|
void raiseForRemoveInterval(int start, int count)
|
2440
|
{
|
2441
|
if (ActiveEvents != 0)
|
2442
|
{
|
2443
|
raiseCollectionCleared(size == 0, count, start);
|
2444
|
raiseCollectionChanged();
|
2445
|
}
|
2446
|
}
|
2447
|
#endregion
|
2448
|
|
2449
|
#region ISequenced<T> Members
|
2450
|
|
2451
|
/// <summary>
|
2452
|
///
|
2453
|
/// </summary>
|
2454
|
/// <returns></returns>
|
2455
|
[Tested]
|
2456
|
public override int GetSequencedHashCode() { validitycheck(); return base.GetSequencedHashCode(); }
|
2457
|
|
2458
|
/// <summary>
|
2459
|
///
|
2460
|
/// </summary>
|
2461
|
/// <param name="that"></param>
|
2462
|
/// <returns></returns>
|
2463
|
[Tested]
|
2464
|
public override bool SequencedEquals(ISequenced<T> that) { validitycheck(); return base.SequencedEquals(that); }
|
2465
|
|
2466
|
#endregion
|
2467
|
|
2468
|
#region IDirectedCollection<T> Members
|
2469
|
|
2470
|
/// <summary>
|
2471
|
/// Create a collection containing the same items as this collection, but
|
2472
|
/// whose enumerator will enumerate the items backwards. The new collection
|
2473
|
/// will become invalid if the original is modified. Method typicaly used as in
|
2474
|
/// <code>foreach (T x in coll.Backwards()) {...}</code>
|
2475
|
/// </summary>
|
2476
|
/// <returns>The backwards collection.</returns>
|
2477
|
[Tested]
|
2478
|
public override IDirectedCollectionValue<T> Backwards()
|
2479
|
{ return this[0, size].Backwards(); }
|
2480
|
|
2481
|
#endregion
|
2482
|
|
2483
|
#region IDirectedEnumerable<T> Members
|
2484
|
|
2485
|
[Tested]
|
2486
|
IDirectedEnumerable<T> IDirectedEnumerable<T>.Backwards() { return Backwards(); }
|
2487
|
|
2488
|
#endregion
|
2489
|
|
2490
|
#region IEditableCollection<T> Members
|
2491
|
|
2492
|
/// <summary>
|
2493
|
/// The value is symbolic indicating the type of asymptotic complexity
|
2494
|
/// in terms of the size of this collection (worst-case or amortized as
|
2495
|
/// relevant).
|
2496
|
/// </summary>
|
2497
|
/// <value>Speed.Linear</value>
|
2498
|
[Tested]
|
2499
|
public virtual Speed ContainsSpeed
|
2500
|
{
|
2501
|
[Tested]
|
2502
|
get
|
2503
|
{
|
2504
|
#if HASHINDEX
|
2505
|
return Speed.Constant;
|
2506
|
#else
|
2507
|
return Speed.Linear;
|
2508
|
#endif
|
2509
|
}
|
2510
|
}
|
2511
|
|
2512
|
/// <summary>
|
2513
|
/// Performs a check for view validity before calling base.GetUnsequencedHashCode()
|
2514
|
/// </summary>
|
2515
|
/// <returns></returns>
|
2516
|
[Tested]
|
2517
|
public override int GetUnsequencedHashCode()
|
2518
|
{ validitycheck(); return base.GetUnsequencedHashCode(); }
|
2519
|
|
2520
|
/// <summary>
|
2521
|
///
|
2522
|
/// </summary>
|
2523
|
/// <param name="that"></param>
|
2524
|
/// <returns></returns>
|
2525
|
[Tested]
|
2526
|
public override bool UnsequencedEquals(ICollection<T> that)
|
2527
|
{ validitycheck(); return base.UnsequencedEquals(that); }
|
2528
|
|
2529
|
/// <summary>
|
2530
|
/// Check if this collection contains (an item equivalent to according to the
|
2531
|
/// itemequalityComparer) a particular value.
|
2532
|
/// </summary>
|
2533
|
/// <param name="item">The value to check for.</param>
|
2534
|
/// <returns>True if the items is in this collection.</returns>
|
2535
|
[Tested]
|
2536
|
public virtual bool Contains(T item)
|
2537
|
{
|
2538
|
validitycheck();
|
2539
|
Node node;
|
2540
|
return contains(item, out node);
|
2541
|
}
|
2542
|
|
2543
|
/// <summary>
|
2544
|
/// Check if this collection contains an item equivalent according to the
|
2545
|
/// itemequalityComparer to a particular value. If so, return in the ref argument (a
|
2546
|
/// binary copy of) the actual value found.
|
2547
|
/// </summary>
|
2548
|
/// <param name="item">The value to look for.</param>
|
2549
|
/// <returns>True if the items is in this collection.</returns>
|
2550
|
[Tested]
|
2551
|
public virtual bool Find(ref T item)
|
2552
|
{
|
2553
|
validitycheck();
|
2554
|
Node node;
|
2555
|
if (contains(item, out node)) { item = node.item; return true; }
|
2556
|
return false;
|
2557
|
}
|
2558
|
|
2559
|
/// <summary>
|
2560
|
/// Check if this collection contains an item equivalent according to the
|
2561
|
/// itemequalityComparer to a particular value. If so, update the item in the collection
|
2562
|
/// to with a binary copy of the supplied value. Will update a single item.
|
2563
|
/// </summary>
|
2564
|
/// <param name="item">Value to update.</param>
|
2565
|
/// <returns>True if the item was found and hence updated.</returns>
|
2566
|
[Tested]
|
2567
|
public virtual bool Update(T item) { T olditem; return Update(item, out olditem); }
|
2568
|
|
2569
|
/// <summary>
|
2570
|
///
|
2571
|
/// </summary>
|
2572
|
/// <param name="item"></param>
|
2573
|
/// <param name="olditem"></param>
|
2574
|
/// <returns></returns>
|
2575
|
public virtual bool Update(T item, out T olditem)
|
2576
|
{
|
2577
|
updatecheck();
|
2578
|
Node node;
|
2579
|
|
2580
|
if (contains(item, out node))
|
2581
|
{
|
2582
|
olditem = node.item;
|
2583
|
node.item = item;
|
2584
|
#if HASHINDEX
|
2585
|
//Avoid clinging onto a reference to olditem via dict!
|
2586
|
dict.Update(item, node);
|
2587
|
#endif
|
2588
|
(underlying ?? this).raiseForUpdate(item, olditem);
|
2589
|
return true;
|
2590
|
}
|
2591
|
|
2592
|
olditem = default(T);
|
2593
|
return false;
|
2594
|
}
|
2595
|
|
2596
|
/// <summary>
|
2597
|
/// Check if this collection contains an item equivalent according to the
|
2598
|
/// itemequalityComparer to a particular value. If so, return in the ref argument (a
|
2599
|
/// binary copy of) the actual value found. Else, add the item to the collection.
|
2600
|
/// </summary>
|
2601
|
/// <param name="item">The value to look for.</param>
|
2602
|
/// <returns>True if the item was found (hence not added).</returns>
|
2603
|
[Tested]
|
2604
|
public virtual bool FindOrAdd(ref T item)
|
2605
|
{
|
2606
|
updatecheck();
|
2607
|
#if HASHINDEX
|
2608
|
//This is an extended myinsert:
|
2609
|
Node node = new Node(item);
|
2610
|
if (!dict.FindOrAdd(item, ref node))
|
2611
|
{
|
2612
|
insertNode(true, endsentinel, node);
|
2613
|
(underlying ?? this).raiseForAdd(item);
|
2614
|
return false;
|
2615
|
}
|
2616
|
if (!insideview(node))
|
2617
|
throw new ArgumentException("Item alredy in indexed list but outside view");
|
2618
|
item = node.item;
|
2619
|
return true;
|
2620
|
#else
|
2621
|
if (Find(ref item))
|
2622
|
return true;
|
2623
|
|
2624
|
Add(item);
|
2625
|
return false;
|
2626
|
#endif
|
2627
|
}
|
2628
|
|
2629
|
/// <summary>
|
2630
|
/// Check if this collection contains an item equivalent according to the
|
2631
|
/// itemequalityComparer to a particular value. If so, update the item in the collection
|
2632
|
/// to with a binary copy of the supplied value; else add the value to the collection.
|
2633
|
/// </summary>
|
2634
|
/// <param name="item">Value to add or update.</param>
|
2635
|
/// <returns>True if the item was found and updated (hence not added).</returns>
|
2636
|
[Tested]
|
2637
|
public virtual bool UpdateOrAdd(T item) { T olditem; return UpdateOrAdd(item, out olditem); }
|
2638
|
|
2639
|
/// <summary>
|
2640
|
///
|
2641
|
/// </summary>
|
2642
|
/// <param name="item"></param>
|
2643
|
/// <param name="olditem"></param>
|
2644
|
/// <returns></returns>
|
2645
|
public virtual bool UpdateOrAdd(T item, out T olditem)
|
2646
|
{
|
2647
|
updatecheck();
|
2648
|
#if HASHINDEX
|
2649
|
Node node = new Node(item);
|
2650
|
//NOTE: it is hard to do this without double access to the dictionary
|
2651
|
//in the update case
|
2652
|
if (dict.FindOrAdd(item, ref node))
|
2653
|
{
|
2654
|
if (!insideview(node))
|
2655
|
throw new ArgumentException("Item in indexed list but outside view");
|
2656
|
olditem = node.item;
|
2657
|
//Avoid clinging onto a reference to olditem via dict!
|
2658
|
dict.Update(item, node);
|
2659
|
node.item = item;
|
2660
|
(underlying ?? this).raiseForUpdate(item, olditem);
|
2661
|
return true;
|
2662
|
}
|
2663
|
insertNode(true, endsentinel, node);
|
2664
|
(underlying ?? this).raiseForAdd(item);
|
2665
|
#else
|
2666
|
if (Update(item, out olditem))
|
2667
|
return true;
|
2668
|
Add(item);
|
2669
|
#endif
|
2670
|
olditem = default(T);
|
2671
|
return false;
|
2672
|
}
|
2673
|
|
2674
|
/// <summary>
|
2675
|
/// Remove a particular item from this collection. Since the collection has bag
|
2676
|
/// semantics only one copy equivalent to the supplied item is removed.
|
2677
|
/// </summary>
|
2678
|
/// <param name="item">The value to remove.</param>
|
2679
|
/// <returns>True if the item was found (and removed).</returns>
|
2680
|
[Tested]
|
2681
|
public virtual bool Remove(T item)
|
2682
|
{
|
2683
|
updatecheck();
|
2684
|
int i = 0;
|
2685
|
Node node;
|
2686
|
#if HASHINDEX
|
2687
|
if (!dictremove(item, out node))
|
2688
|
#else
|
2689
|
node = fIFO ? startsentinel.next : endsentinel.prev;
|
2690
|
if (!(fIFO ? find(item, ref node, ref i) : dnif(item, ref node, ref i)))
|
2691
|
#endif
|
2692
|
return false;
|
2693
|
T removeditem = remove(node, i);
|
2694
|
(underlying ?? this).raiseForRemove(removeditem);
|
2695
|
return true;
|
2696
|
}
|
2697
|
|
2698
|
/// <summary>
|
2699
|
/// Remove a particular item from this collection if found (only one copy).
|
2700
|
/// If an item was removed, report a binary copy of the actual item removed in
|
2701
|
/// the argument.
|
2702
|
/// </summary>
|
2703
|
/// <param name="item">The value to remove on input.</param>
|
2704
|
/// <param name="removeditem">The value removed.</param>
|
2705
|
/// <returns>True if the item was found (and removed).</returns>
|
2706
|
[Tested]
|
2707
|
public virtual bool Remove(T item, out T removeditem)
|
2708
|
{
|
2709
|
updatecheck();
|
2710
|
int i = 0;
|
2711
|
Node node;
|
2712
|
#if HASHINDEX
|
2713
|
if (!dictremove(item, out node))
|
2714
|
#else
|
2715
|
node = fIFO ? startsentinel.next : endsentinel.prev;
|
2716
|
if (!(fIFO ? find(item, ref node, ref i) : dnif(item, ref node, ref i)))
|
2717
|
#endif
|
2718
|
{
|
2719
|
removeditem = default(T);
|
2720
|
return false;
|
2721
|
}
|
2722
|
removeditem = node.item;
|
2723
|
remove(node, i);
|
2724
|
(underlying ?? this).raiseForRemove(removeditem);
|
2725
|
return true;
|
2726
|
}
|
2727
|
|
2728
|
/// <summary>
|
2729
|
/// Remove all items in another collection from this one, taking multiplicities into account.
|
2730
|
/// <para>Always removes from the front of the list.
|
2731
|
/// </para>
|
2732
|
/// <para>The asymptotic running time complexity of this method is <code>O(n+m+v*log(v))</code>,
|
2733
|
/// where <code>n</code> is the size of this list, <code>m</code> is the size of the
|
2734
|
/// <code>items</code> collection and <code>v</code> is the number of views.
|
2735
|
/// The method will temporarily allocate memory of size <code>O(m+v)</code>.
|
2736
|
/// </para>
|
2737
|
/// </summary>
|
2738
|
/// <typeparam name="U"></typeparam>
|
2739
|
/// <param name="items">The items to remove.</param>
|
2740
|
[Tested]
|
2741
|
public virtual void RemoveAll<U>(SCG.IEnumerable<U> items) where U : T
|
2742
|
{
|
2743
|
updatecheck();
|
2744
|
if (size == 0)
|
2745
|
return;
|
2746
|
RaiseForRemoveAllHandler raiseHandler = new RaiseForRemoveAllHandler(underlying ?? this);
|
2747
|
bool mustFire = raiseHandler.MustFire;
|
2748
|
#if HASHINDEX
|
2749
|
Node node;
|
2750
|
foreach (T item in items)
|
2751
|
if (dictremove(item, out node))
|
2752
|
{
|
2753
|
if (mustFire)
|
2754
|
raiseHandler.Remove(node.item);
|
2755
|
remove(node, 118);
|
2756
|
}
|
2757
|
#else
|
2758
|
HashBag<T> toremove = new HashBag<T>(itemequalityComparer);
|
2759
|
toremove.AddAll(items);
|
2760
|
ViewHandler viewHandler = new ViewHandler(this);
|
2761
|
int index = 0, removed = 0, myoffset = Offset;
|
2762
|
Node node = startsentinel.next;
|
2763
|
while (node != endsentinel)
|
2764
|
{
|
2765
|
//pass by a stretch of nodes
|
2766
|
while (node != endsentinel && !toremove.Contains(node.item))
|
2767
|
{
|
2768
|
node = node.next;
|
2769
|
index++;
|
2770
|
}
|
2771
|
viewHandler.skipEndpoints(removed, myoffset + index);
|
2772
|
//Remove a stretch of nodes
|
2773
|
Node localend = node.prev; //Latest node not to be removed
|
2774
|
while (node != endsentinel && toremove.Remove(node.item))
|
2775
|
{
|
2776
|
if (mustFire)
|
2777
|
raiseHandler.Remove(node.item);
|
2778
|
removed++;
|
2779
|
node = node.next;
|
2780
|
index++;
|
2781
|
viewHandler.updateViewSizesAndCounts(removed, myoffset + index);
|
2782
|
}
|
2783
|
viewHandler.updateSentinels(myoffset + index, localend, node);
|
2784
|
localend.next = node;
|
2785
|
node.prev = localend;
|
2786
|
}
|
2787
|
index = underlying != null ? underlying.size + 1 - myoffset : size + 1 - myoffset;
|
2788
|
viewHandler.updateViewSizesAndCounts(removed, myoffset + index);
|
2789
|
size -= removed;
|
2790
|
if (underlying != null)
|
2791
|
underlying.size -= removed;
|
2792
|
#endif
|
2793
|
raiseHandler.Raise();
|
2794
|
}
|
2795
|
|
2796
|
/// <summary>
|
2797
|
///
|
2798
|
/// </summary>
|
2799
|
/// <param name="predicate"></param>
|
2800
|
void RemoveAll(Fun<T, bool> predicate)
|
2801
|
{
|
2802
|
updatecheck();
|
2803
|
if (size == 0)
|
2804
|
return;
|
2805
|
RaiseForRemoveAllHandler raiseHandler = new RaiseForRemoveAllHandler(underlying ?? this);
|
2806
|
bool mustFire = raiseHandler.MustFire;
|
2807
|
#if HASHINDEX
|
2808
|
{
|
2809
|
Node n = startsentinel.next;
|
2810
|
|
2811
|
while (n != endsentinel)
|
2812
|
{
|
2813
|
bool removeIt = predicate(n.item);
|
2814
|
updatecheck();
|
2815
|
if (removeIt)
|
2816
|
{
|
2817
|
dict.Remove(n.item);
|
2818
|
remove(n, 119);
|
2819
|
if (mustFire)
|
2820
|
raiseHandler.Remove(n.item);
|
2821
|
}
|
2822
|
|
2823
|
n = n.next;
|
2824
|
}
|
2825
|
}
|
2826
|
#else
|
2827
|
ViewHandler viewHandler = new ViewHandler(this);
|
2828
|
int index = 0, removed = 0, myoffset = Offset;
|
2829
|
Node node = startsentinel.next;
|
2830
|
while (node != endsentinel)
|
2831
|
{
|
2832
|
//pass by a stretch of nodes
|
2833
|
while (node != endsentinel && !predicate(node.item))
|
2834
|
{
|
2835
|
updatecheck();
|
2836
|
node = node.next;
|
2837
|
index++;
|
2838
|
}
|
2839
|
updatecheck();
|
2840
|
viewHandler.skipEndpoints(removed, myoffset + index);
|
2841
|
//Remove a stretch of nodes
|
2842
|
Node localend = node.prev; //Latest node not to be removed
|
2843
|
while (node != endsentinel && predicate(node.item))
|
2844
|
{
|
2845
|
updatecheck();
|
2846
|
if (mustFire)
|
2847
|
raiseHandler.Remove(node.item);
|
2848
|
removed++;
|
2849
|
node = node.next;
|
2850
|
index++;
|
2851
|
viewHandler.updateViewSizesAndCounts(removed, myoffset + index);
|
2852
|
}
|
2853
|
updatecheck();
|
2854
|
viewHandler.updateSentinels(myoffset + index, localend, node);
|
2855
|
localend.next = node;
|
2856
|
node.prev = localend;
|
2857
|
}
|
2858
|
index = underlying != null ? underlying.size + 1 - myoffset : size + 1 - myoffset;
|
2859
|
viewHandler.updateViewSizesAndCounts(removed, myoffset + index);
|
2860
|
size -= removed;
|
2861
|
if (underlying != null)
|
2862
|
underlying.size -= removed;
|
2863
|
#endif
|
2864
|
raiseHandler.Raise();
|
2865
|
}
|
2866
|
|
2867
|
/// <summary>
|
2868
|
/// Remove all items from this collection.
|
2869
|
/// </summary>
|
2870
|
[Tested]
|
2871
|
public virtual void Clear()
|
2872
|
{
|
2873
|
updatecheck();
|
2874
|
if (size == 0)
|
2875
|
return;
|
2876
|
int oldsize = size;
|
2877
|
#if HASHINDEX
|
2878
|
if (underlying == null)
|
2879
|
dict.Clear();
|
2880
|
else
|
2881
|
foreach (T item in this)
|
2882
|
dict.Remove(item);
|
2883
|
#endif
|
2884
|
clear();
|
2885
|
(underlying ?? this).raiseForRemoveInterval(Offset, oldsize);
|
2886
|
}
|
2887
|
|
2888
|
void clear()
|
2889
|
{
|
2890
|
if (size == 0)
|
2891
|
return;
|
2892
|
#if HASHINDEX
|
2893
|
//TODO: mix with tag maintenance to only run through list once?
|
2894
|
ViewHandler viewHandler = new ViewHandler(this);
|
2895
|
if (viewHandler.viewCount > 0)
|
2896
|
{
|
2897
|
int removed = 0;
|
2898
|
Node n = startsentinel.next;
|
2899
|
viewHandler.skipEndpoints(0, n);
|
2900
|
while (n != endsentinel)
|
2901
|
{
|
2902
|
removed++;
|
2903
|
n = n.next;
|
2904
|
viewHandler.updateViewSizesAndCounts(removed, n);
|
2905
|
}
|
2906
|
viewHandler.updateSentinels(endsentinel, startsentinel, endsentinel);
|
2907
|
if (underlying != null)
|
2908
|
viewHandler.updateViewSizesAndCounts(removed, underlying.endsentinel);
|
2909
|
}
|
2910
|
#else
|
2911
|
fixViewsBeforeRemove(Offset, size, startsentinel.next, endsentinel.prev);
|
2912
|
#endif
|
2913
|
#if HASHINDEX
|
2914
|
if (underlying != null)
|
2915
|
{
|
2916
|
Node n = startsentinel.next;
|
2917
|
|
2918
|
while (n != endsentinel)
|
2919
|
{
|
2920
|
n.next.prev = startsentinel;
|
2921
|
startsentinel.next = n.next;
|
2922
|
removefromtaggroup(n);
|
2923
|
n = n.next;
|
2924
|
}
|
2925
|
}
|
2926
|
else
|
2927
|
taggroups = 0;
|
2928
|
#endif
|
2929
|
endsentinel.prev = startsentinel;
|
2930
|
startsentinel.next = endsentinel;
|
2931
|
if (underlying != null)
|
2932
|
underlying.size -= size;
|
2933
|
size = 0;
|
2934
|
}
|
2935
|
|
2936
|
/// <summary>
|
2937
|
/// Remove all items not in some other collection from this one, taking multiplicities into account.
|
2938
|
/// <para>The asymptotic running time complexity of this method is <code>O(n+m+v*log(v))</code>,
|
2939
|
/// where <code>n</code> is the size of this collection, <code>m</code> is the size of the
|
2940
|
/// <code>items</code> collection and <code>v</code> is the number of views.
|
2941
|
/// The method will temporarily allocate memory of size <code>O(m+v)</code>. The stated complexitiy
|
2942
|
/// holds under the assumption that the itemequalityComparer of this list is well-behaved.
|
2943
|
/// </para>
|
2944
|
/// </summary>
|
2945
|
/// <typeparam name="U"></typeparam>
|
2946
|
/// <param name="items">The items to retain.</param>
|
2947
|
[Tested]
|
2948
|
public virtual void RetainAll<U>(SCG.IEnumerable<U> items) where U : T
|
2949
|
{
|
2950
|
updatecheck();
|
2951
|
if (size == 0)
|
2952
|
return;
|
2953
|
RaiseForRemoveAllHandler raiseHandler = new RaiseForRemoveAllHandler(underlying ?? this);
|
2954
|
bool mustFire = raiseHandler.MustFire;
|
2955
|
#if HASHINDEX
|
2956
|
/*if (underlying == null)
|
2957
|
{
|
2958
|
HashDictionary<T, Node> newdict = new HashDictionary<T, Node>(itemequalityComparer);
|
2959
|
foreach (T item in items)
|
2960
|
{
|
2961
|
Node node;
|
2962
|
|
2963
|
if (dict.Remove(item, out node))
|
2964
|
newdict.Add(item, node);
|
2965
|
}
|
2966
|
foreach (KeyValuePair<T, Node> pair in dict)
|
2967
|
{
|
2968
|
Node n = pair.Value;
|
2969
|
fixViewsBeforeSingleRemove(n, 117);
|
2970
|
Node p = n.prev, s = n.next; s.prev = p; p.next = s;
|
2971
|
removefromtaggroup(n);
|
2972
|
}
|
2973
|
dict = newdict;
|
2974
|
size = dict.Count;
|
2975
|
//For a small number of items to retain it might be faster to
|
2976
|
//iterate through the list and splice out the chunks not needed
|
2977
|
}
|
2978
|
else*/
|
2979
|
{
|
2980
|
HashSet<T> toremove = new HashSet<T>(itemequalityComparer);
|
2981
|
|
2982
|
foreach (T item in this)
|
2983
|
toremove.Add(item);
|
2984
|
|
2985
|
foreach (T item in items)
|
2986
|
toremove.Remove(item);
|
2987
|
|
2988
|
Node n = startsentinel.next;
|
2989
|
|
2990
|
while (n != endsentinel && toremove.Count > 0)
|
2991
|
{
|
2992
|
if (toremove.Contains(n.item))
|
2993
|
{
|
2994
|
dict.Remove(n.item);
|
2995
|
remove(n, 119);
|
2996
|
if (mustFire)
|
2997
|
raiseHandler.Remove(n.item);
|
2998
|
}
|
2999
|
|
3000
|
n = n.next;
|
3001
|
}
|
3002
|
}
|
3003
|
#else
|
3004
|
HashBag<T> toretain = new HashBag<T>(itemequalityComparer);
|
3005
|
toretain.AddAll(items);
|
3006
|
ViewHandler viewHandler = new ViewHandler(this);
|
3007
|
int index = 0, removed = 0, myoffset = Offset;
|
3008
|
Node node = startsentinel.next;
|
3009
|
while (node != endsentinel)
|
3010
|
{
|
3011
|
//Skip a stretch of nodes
|
3012
|
while (node != endsentinel && toretain.Remove(node.item))
|
3013
|
{
|
3014
|
node = node.next;
|
3015
|
index++;
|
3016
|
}
|
3017
|
viewHandler.skipEndpoints(removed, myoffset + index);
|
3018
|
//Remove a stretch of nodes
|
3019
|
Node localend = node.prev; //Latest node not to be removed
|
3020
|
while (node != endsentinel && !toretain.Contains(node.item))
|
3021
|
{
|
3022
|
if (mustFire)
|
3023
|
raiseHandler.Remove(node.item);
|
3024
|
removed++;
|
3025
|
node = node.next;
|
3026
|
index++;
|
3027
|
viewHandler.updateViewSizesAndCounts(removed, myoffset + index);
|
3028
|
}
|
3029
|
viewHandler.updateSentinels(myoffset + index, localend, node);
|
3030
|
localend.next = node;
|
3031
|
node.prev = localend;
|
3032
|
}
|
3033
|
index = underlying != null ? underlying.size + 1 - myoffset : size + 1 - myoffset;
|
3034
|
viewHandler.updateViewSizesAndCounts(removed, myoffset + index);
|
3035
|
size -= removed;
|
3036
|
if (underlying != null)
|
3037
|
underlying.size -= removed;
|
3038
|
#endif
|
3039
|
raiseHandler.Raise();
|
3040
|
}
|
3041
|
|
3042
|
/// <summary>
|
3043
|
///
|
3044
|
/// </summary>
|
3045
|
/// <param name="predicate"></param>
|
3046
|
void RetainAll(Fun<T, bool> predicate)
|
3047
|
{
|
3048
|
updatecheck();
|
3049
|
if (size == 0)
|
3050
|
return;
|
3051
|
RaiseForRemoveAllHandler raiseHandler = new RaiseForRemoveAllHandler(underlying ?? this);
|
3052
|
bool mustFire = raiseHandler.MustFire;
|
3053
|
#if HASHINDEX
|
3054
|
{
|
3055
|
Node n = startsentinel.next;
|
3056
|
|
3057
|
while (n != endsentinel)
|
3058
|
{
|
3059
|
bool removeIt = !predicate(n.item);
|
3060
|
updatecheck();
|
3061
|
if (removeIt)
|
3062
|
{
|
3063
|
dict.Remove(n.item);
|
3064
|
remove(n, 119);
|
3065
|
if (mustFire)
|
3066
|
raiseHandler.Remove(n.item);
|
3067
|
}
|
3068
|
|
3069
|
n = n.next;
|
3070
|
}
|
3071
|
}
|
3072
|
#else
|
3073
|
ViewHandler viewHandler = new ViewHandler(this);
|
3074
|
int index = 0, removed = 0, myoffset = Offset;
|
3075
|
Node node = startsentinel.next;
|
3076
|
while (node != endsentinel)
|
3077
|
{
|
3078
|
//Skip a stretch of nodes
|
3079
|
while (node != endsentinel && predicate(node.item))
|
3080
|
{
|
3081
|
updatecheck();
|
3082
|
node = node.next;
|
3083
|
index++;
|
3084
|
}
|
3085
|
updatecheck();
|
3086
|
viewHandler.skipEndpoints(removed, myoffset + index);
|
3087
|
//Remove a stretch of nodes
|
3088
|
Node localend = node.prev; //Latest node not to be removed
|
3089
|
while (node != endsentinel && !predicate(node.item))
|
3090
|
{
|
3091
|
updatecheck();
|
3092
|
if (mustFire)
|
3093
|
raiseHandler.Remove(node.item);
|
3094
|
removed++;
|
3095
|
node = node.next;
|
3096
|
index++;
|
3097
|
viewHandler.updateViewSizesAndCounts(removed, myoffset + index);
|
3098
|
}
|
3099
|
updatecheck();
|
3100
|
viewHandler.updateSentinels(myoffset + index, localend, node);
|
3101
|
localend.next = node;
|
3102
|
node.prev = localend;
|
3103
|
}
|
3104
|
index = underlying != null ? underlying.size + 1 - myoffset : size + 1 - myoffset;
|
3105
|
viewHandler.updateViewSizesAndCounts(removed, myoffset + index);
|
3106
|
size -= removed;
|
3107
|
if (underlying != null)
|
3108
|
underlying.size -= removed;
|
3109
|
#endif
|
3110
|
raiseHandler.Raise();
|
3111
|
}
|
3112
|
|
3113
|
/// <summary>
|
3114
|
/// Check if this collection contains all the values in another collection
|
3115
|
/// with respect to multiplicities.
|
3116
|
/// </summary>
|
3117
|
/// <param name="items">The </param>
|
3118
|
/// <typeparam name="U"></typeparam>
|
3119
|
/// <returns>True if all values in <code>items</code>is in this collection.</returns>
|
3120
|
[Tested]
|
3121
|
public virtual bool ContainsAll<U>(SCG.IEnumerable<U> items) where U : T
|
3122
|
{
|
3123
|
validitycheck();
|
3124
|
#if HASHINDEX
|
3125
|
Node node;
|
3126
|
foreach (T item in items)
|
3127
|
if (!contains(item, out node))
|
3128
|
return false;
|
3129
|
return true;
|
3130
|
#else
|
3131
|
HashBag<T> tocheck = new HashBag<T>(itemequalityComparer);
|
3132
|
tocheck.AddAll(items);
|
3133
|
if (tocheck.Count > size)
|
3134
|
return false;
|
3135
|
Node node = startsentinel.next;
|
3136
|
while (node != endsentinel)
|
3137
|
{
|
3138
|
tocheck.Remove(node.item);
|
3139
|
node = node.next;
|
3140
|
}
|
3141
|
return tocheck.IsEmpty;
|
3142
|
#endif
|
3143
|
}
|
3144
|
|
3145
|
|
3146
|
/// <summary>
|
3147
|
/// Create a new list consisting of the items of this list satisfying a
|
3148
|
/// certain predicate.
|
3149
|
/// </summary>
|
3150
|
/// <param name="filter">The filter delegate defining the predicate.</param>
|
3151
|
/// <returns>The new list.</returns>
|
3152
|
[Tested]
|
3153
|
public IList<T> FindAll(Fun<T, bool> filter)
|
3154
|
{
|
3155
|
validitycheck();
|
3156
|
int stamp = this.stamp;
|
3157
|
LinkedList<T> retval = new LinkedList<T>();
|
3158
|
Node cursor = startsentinel.next;
|
3159
|
Node mcursor = retval.startsentinel;
|
3160
|
#if HASHINDEX
|
3161
|
double tagdelta = int.MaxValue / (size + 1.0);
|
3162
|
int count = 1;
|
3163
|
TagGroup taggroup = new TagGroup();
|
3164
|
retval.taggroups = 1;
|
3165
|
#endif
|
3166
|
while (cursor != endsentinel)
|
3167
|
{
|
3168
|
bool found = filter(cursor.item);
|
3169
|
modifycheck(stamp);
|
3170
|
if (found)
|
3171
|
{
|
3172
|
mcursor.next = new Node(cursor.item, mcursor, null);
|
3173
|
mcursor = mcursor.next;
|
3174
|
retval.size++;
|
3175
|
#if HASHINDEX
|
3176
|
retval.dict.Add(cursor.item, mcursor);
|
3177
|
mcursor.taggroup = taggroup;
|
3178
|
mcursor.tag = (int)(tagdelta * count++);
|
3179
|
#endif
|
3180
|
}
|
3181
|
cursor = cursor.next;
|
3182
|
}
|
3183
|
#if HASHINDEX
|
3184
|
if (retval.size > 0)
|
3185
|
{
|
3186
|
taggroup.count = retval.size;
|
3187
|
taggroup.first = retval.startsentinel.next;
|
3188
|
taggroup.last = mcursor;
|
3189
|
}
|
3190
|
#endif
|
3191
|
retval.endsentinel.prev = mcursor;
|
3192
|
mcursor.next = retval.endsentinel;
|
3193
|
return retval;
|
3194
|
}
|
3195
|
|
3196
|
|
3197
|
/// <summary>
|
3198
|
/// Count the number of items of the collection equal to a particular value.
|
3199
|
/// Returns 0 if and only if the value is not in the collection.
|
3200
|
/// </summary>
|
3201
|
/// <param name="item">The value to count.</param>
|
3202
|
/// <returns>The number of copies found.</returns>
|
3203
|
[Tested]
|
3204
|
public virtual int ContainsCount(T item)
|
3205
|
{
|
3206
|
#if HASHINDEX
|
3207
|
return Contains(item) ? 1 : 0;
|
3208
|
#else
|
3209
|
validitycheck();
|
3210
|
int retval = 0;
|
3211
|
Node node = startsentinel.next;
|
3212
|
while (node != endsentinel)
|
3213
|
{
|
3214
|
if (itemequalityComparer.Equals(node.item, item))
|
3215
|
retval++;
|
3216
|
node = node.next;
|
3217
|
}
|
3218
|
return retval;
|
3219
|
#endif
|
3220
|
}
|
3221
|
|
3222
|
/// <summary>
|
3223
|
///
|
3224
|
/// </summary>
|
3225
|
/// <returns></returns>
|
3226
|
public virtual ICollectionValue<T> UniqueItems()
|
3227
|
{
|
3228
|
#if HASHINDEX
|
3229
|
return this;
|
3230
|
#else
|
3231
|
HashBag<T> hashbag = new HashBag<T>(itemequalityComparer);
|
3232
|
hashbag.AddAll(this);
|
3233
|
return hashbag.UniqueItems();
|
3234
|
#endif
|
3235
|
}
|
3236
|
|
3237
|
/// <summary>
|
3238
|
///
|
3239
|
/// </summary>
|
3240
|
/// <returns></returns>
|
3241
|
public virtual ICollectionValue<KeyValuePair<T, int>> ItemMultiplicities()
|
3242
|
{
|
3243
|
#if HASHINDEX
|
3244
|
return new MultiplicityOne<T>(this);
|
3245
|
#else
|
3246
|
HashBag<T> hashbag = new HashBag<T>(itemequalityComparer);
|
3247
|
hashbag.AddAll(this);
|
3248
|
return hashbag.ItemMultiplicities();
|
3249
|
#endif
|
3250
|
}
|
3251
|
|
3252
|
/// <summary>
|
3253
|
/// Remove all items equivalent to a given value.
|
3254
|
/// <para>The asymptotic complexity of this method is <code>O(n+v*log(v))</code>,
|
3255
|
/// where <code>n</code> is the size of the collection and <code>v</code>
|
3256
|
/// is the number of views.
|
3257
|
/// </para>
|
3258
|
/// </summary>
|
3259
|
/// <param name="item">The value to remove.</param>
|
3260
|
[Tested]
|
3261
|
public virtual void RemoveAllCopies(T item)
|
3262
|
{
|
3263
|
#if HASHINDEX
|
3264
|
Remove(item);
|
3265
|
#else
|
3266
|
updatecheck();
|
3267
|
if (size == 0)
|
3268
|
return;
|
3269
|
RaiseForRemoveAllHandler raiseHandler = new RaiseForRemoveAllHandler(underlying ?? this);
|
3270
|
bool mustFire = raiseHandler.MustFire;
|
3271
|
ViewHandler viewHandler = new ViewHandler(this);
|
3272
|
int index = 0, removed = 0, myoffset = Offset;
|
3273
|
//
|
3274
|
Node node = startsentinel.next;
|
3275
|
while (node != endsentinel)
|
3276
|
{
|
3277
|
//pass by a stretch of nodes
|
3278
|
while (node != endsentinel && !itemequalityComparer.Equals(node.item, item))
|
3279
|
{
|
3280
|
node = node.next;
|
3281
|
index++;
|
3282
|
}
|
3283
|
viewHandler.skipEndpoints(removed, myoffset + index);
|
3284
|
//Remove a stretch of nodes
|
3285
|
Node localend = node.prev; //Latest node not to be removed
|
3286
|
while (node != endsentinel && itemequalityComparer.Equals(node.item, item))
|
3287
|
{
|
3288
|
if (mustFire)
|
3289
|
raiseHandler.Remove(node.item);
|
3290
|
removed++;
|
3291
|
node = node.next;
|
3292
|
index++;
|
3293
|
viewHandler.updateViewSizesAndCounts(removed, myoffset + index);
|
3294
|
}
|
3295
|
viewHandler.updateSentinels(myoffset + index, localend, node);
|
3296
|
localend.next = node;
|
3297
|
node.prev = localend;
|
3298
|
}
|
3299
|
index = underlying != null ? underlying.size + 1 - myoffset : size + 1 - myoffset;
|
3300
|
viewHandler.updateViewSizesAndCounts(removed, myoffset + index);
|
3301
|
size -= removed;
|
3302
|
if (underlying != null)
|
3303
|
underlying.size -= removed;
|
3304
|
raiseHandler.Raise();
|
3305
|
#endif
|
3306
|
}
|
3307
|
|
3308
|
#endregion
|
3309
|
|
3310
|
#region ICollectionValue<T> Members
|
3311
|
|
3312
|
/// <summary>
|
3313
|
///
|
3314
|
/// </summary>
|
3315
|
/// <value>The number of items in this collection</value>
|
3316
|
[Tested]
|
3317
|
public override int Count { [Tested]get { validitycheck(); return size; } }
|
3318
|
|
3319
|
/// <summary>
|
3320
|
/// Choose some item of this collection.
|
3321
|
/// </summary>
|
3322
|
/// <exception cref="NoSuchItemException">if collection is empty.</exception>
|
3323
|
/// <returns></returns>
|
3324
|
[Tested]
|
3325
|
public override T Choose() { return First; }
|
3326
|
|
3327
|
/// <summary>
|
3328
|
/// Create an enumerable, enumerating the items of this collection that satisfies
|
3329
|
/// a certain condition.
|
3330
|
/// </summary>
|
3331
|
/// <param name="filter">The T->bool filter delegate defining the condition</param>
|
3332
|
/// <returns>The filtered enumerable</returns>
|
3333
|
public override SCG.IEnumerable<T> Filter(Fun<T, bool> filter) { validitycheck(); return base.Filter(filter); }
|
3334
|
|
3335
|
#endregion
|
3336
|
|
3337
|
#region IEnumerable<T> Members
|
3338
|
/// <summary>
|
3339
|
/// Create an enumerator for the collection
|
3340
|
/// </summary>
|
3341
|
/// <returns>The enumerator</returns>
|
3342
|
[Tested]
|
3343
|
public override SCG.IEnumerator<T> GetEnumerator()
|
3344
|
{
|
3345
|
validitycheck();
|
3346
|
Node cursor = startsentinel.next;
|
3347
|
int enumeratorstamp = underlying != null ? underlying.stamp : this.stamp;
|
3348
|
|
3349
|
while (cursor != endsentinel)
|
3350
|
{
|
3351
|
modifycheck(enumeratorstamp);
|
3352
|
yield return cursor.item;
|
3353
|
cursor = cursor.next;
|
3354
|
}
|
3355
|
}
|
3356
|
|
3357
|
#endregion
|
3358
|
|
3359
|
#region IExtensible<T> Members
|
3360
|
/// <summary>
|
3361
|
/// Add an item to this collection if possible.
|
3362
|
/// </summary>
|
3363
|
/// <param name="item">The item to add.</param>
|
3364
|
/// <returns>True.</returns>
|
3365
|
[Tested]
|
3366
|
public virtual bool Add(T item)
|
3367
|
{
|
3368
|
updatecheck();
|
3369
|
#if HASHINDEX
|
3370
|
Node node = new Node(item);
|
3371
|
if (!dict.FindOrAdd(item, ref node))
|
3372
|
{
|
3373
|
insertNode(true, endsentinel, node);
|
3374
|
(underlying ?? this).raiseForAdd(item);
|
3375
|
return true;
|
3376
|
}
|
3377
|
return false;
|
3378
|
#else
|
3379
|
insert(size, endsentinel, item);
|
3380
|
(underlying ?? this).raiseForAdd(item);
|
3381
|
return true;
|
3382
|
#endif
|
3383
|
}
|
3384
|
|
3385
|
/// <summary>
|
3386
|
///
|
3387
|
/// </summary>
|
3388
|
/// <value>True since this collection has bag semantics.</value>
|
3389
|
[Tested]
|
3390
|
public virtual bool AllowsDuplicates
|
3391
|
{
|
3392
|
[Tested]
|
3393
|
get
|
3394
|
{
|
3395
|
#if HASHINDEX
|
3396
|
return false;
|
3397
|
#else
|
3398
|
return true;
|
3399
|
#endif
|
3400
|
}
|
3401
|
}
|
3402
|
|
3403
|
/// <summary>
|
3404
|
/// By convention this is true for any collection with set semantics.
|
3405
|
/// </summary>
|
3406
|
/// <value>True if only one representative of a group of equal items
|
3407
|
/// is kept in the collection together with the total count.</value>
|
3408
|
public virtual bool DuplicatesByCounting
|
3409
|
{
|
3410
|
get
|
3411
|
{
|
3412
|
#if HASHINDEX
|
3413
|
return true;
|
3414
|
#else
|
3415
|
return false;
|
3416
|
#endif
|
3417
|
}
|
3418
|
}
|
3419
|
|
3420
|
/// <summary>
|
3421
|
/// Add the elements from another collection with a more specialized item type
|
3422
|
/// to this collection.
|
3423
|
/// </summary>
|
3424
|
/// <typeparam name="U">The type of items to add</typeparam>
|
3425
|
/// <param name="items">The items to add</param>
|
3426
|
[Tested]
|
3427
|
public virtual void AddAll<U>(SCG.IEnumerable<U> items) where U : T
|
3428
|
{
|
3429
|
#if HASHINDEX
|
3430
|
updatecheck();
|
3431
|
int added = 0;
|
3432
|
Node pred = endsentinel.prev;
|
3433
|
foreach (U item in items)
|
3434
|
{
|
3435
|
Node node = new Node(item);
|
3436
|
if (!dict.FindOrAdd(item, ref node))
|
3437
|
{
|
3438
|
insertNode(false, endsentinel, node);
|
3439
|
added++;
|
3440
|
}
|
3441
|
}
|
3442
|
if (added > 0)
|
3443
|
{
|
3444
|
fixViewsAfterInsert(endsentinel, pred, added, 0);
|
3445
|
raiseForInsertAll(pred, size - added, added, false);
|
3446
|
}
|
3447
|
#else
|
3448
|
insertAll(size, items, false);
|
3449
|
#endif
|
3450
|
}
|
3451
|
|
3452
|
#endregion
|
3453
|
|
3454
|
#if HASHINDEX
|
3455
|
#else
|
3456
|
#region IStack<T> Members
|
3457
|
|
3458
|
/// <summary>
|
3459
|
/// Push an item to the top of the stack.
|
3460
|
/// </summary>
|
3461
|
/// <param name="item">The item</param>
|
3462
|
[Tested]
|
3463
|
public void Push(T item)
|
3464
|
{
|
3465
|
InsertLast(item);
|
3466
|
}
|
3467
|
|
3468
|
/// <summary>
|
3469
|
/// Pop the item at the top of the stack from the stack.
|
3470
|
/// </summary>
|
3471
|
/// <returns>The popped item.</returns>
|
3472
|
[Tested]
|
3473
|
public T Pop()
|
3474
|
{
|
3475
|
return RemoveLast();
|
3476
|
}
|
3477
|
|
3478
|
#endregion
|
3479
|
|
3480
|
#region IQueue<T> Members
|
3481
|
|
3482
|
/// <summary>
|
3483
|
/// Enqueue an item at the back of the queue.
|
3484
|
/// </summary>
|
3485
|
/// <param name="item">The item</param>
|
3486
|
[Tested]
|
3487
|
public virtual void Enqueue(T item)
|
3488
|
{
|
3489
|
InsertLast(item);
|
3490
|
}
|
3491
|
|
3492
|
/// <summary>
|
3493
|
/// Dequeue an item from the front of the queue.
|
3494
|
/// </summary>
|
3495
|
/// <returns>The item</returns>
|
3496
|
[Tested]
|
3497
|
public virtual T Dequeue()
|
3498
|
{
|
3499
|
return RemoveFirst();
|
3500
|
}
|
3501
|
#endregion
|
3502
|
#endif
|
3503
|
|
3504
|
#region Diagnostic
|
3505
|
|
3506
|
private bool checkViews()
|
3507
|
{
|
3508
|
if (underlying != null)
|
3509
|
throw new InternalException(System.Reflection.MethodInfo.GetCurrentMethod() + " called on a view");
|
3510
|
if (views == null)
|
3511
|
return true;
|
3512
|
bool retval = true;
|
3513
|
|
3514
|
Node[] nodes = new Node[size + 2];
|
3515
|
int i = 0;
|
3516
|
Node n = startsentinel;
|
3517
|
while (n != null)
|
3518
|
{
|
3519
|
nodes[i++] = n;
|
3520
|
n = n.next;
|
3521
|
}
|
3522
|
//Console.WriteLine("###");
|
3523
|
foreach (LinkedList<T> view in views)
|
3524
|
{
|
3525
|
if (!view.isValid)
|
3526
|
{
|
3527
|
Console.WriteLine("Invalid view(hash {0}, offset {1}, size {2})",
|
3528
|
view.GetHashCode(), view.offset, view.size);
|
3529
|
retval = false;
|
3530
|
continue;
|
3531
|
}
|
3532
|
if (view.Offset > size || view.Offset < 0)
|
3533
|
{
|
3534
|
Console.WriteLine("Bad view(hash {0}, offset {1}, size {2}), Offset > underlying.size ({2})",
|
3535
|
view.GetHashCode(), view.offset, view.size, size);
|
3536
|
retval = false;
|
3537
|
}
|
3538
|
else if (view.startsentinel != nodes[view.Offset])
|
3539
|
{
|
3540
|
Console.WriteLine("Bad view(hash {0}, offset {1}, size {2}), startsentinel {3} should be {4}",
|
3541
|
view.GetHashCode(), view.offset, view.size,
|
3542
|
view.startsentinel + " " + view.startsentinel.GetHashCode(),
|
3543
|
nodes[view.Offset] + " " + nodes[view.Offset].GetHashCode());
|
3544
|
retval = false;
|
3545
|
}
|
3546
|
if (view.Offset + view.size > size || view.Offset + view.size < 0)
|
3547
|
{
|
3548
|
Console.WriteLine("Bad view(hash {0}, offset {1}, size {2}), end index > underlying.size ({3})",
|
3549
|
view.GetHashCode(), view.offset, view.size, size);
|
3550
|
retval = false;
|
3551
|
}
|
3552
|
else if (view.endsentinel != nodes[view.Offset + view.size + 1])
|
3553
|
{
|
3554
|
Console.WriteLine("Bad view(hash {0}, offset {1}, size {2}), endsentinel {3} should be {4}",
|
3555
|
view.GetHashCode(), view.offset, view.size,
|
3556
|
view.endsentinel + " " + view.endsentinel.GetHashCode(),
|
3557
|
nodes[view.Offset + view.size + 1] + " " + nodes[view.Offset + view.size + 1].GetHashCode());
|
3558
|
retval = false;
|
3559
|
}
|
3560
|
if (view.views != views)
|
3561
|
{
|
3562
|
Console.WriteLine("Bad view(hash {0}, offset {1}, size {2}), wrong views list {3} <> {4}",
|
3563
|
view.GetHashCode(), view.offset, view.size, view.views.GetHashCode(), views.GetHashCode());
|
3564
|
retval = false;
|
3565
|
}
|
3566
|
if (view.underlying != this)
|
3567
|
{
|
3568
|
Console.WriteLine("Bad view(hash {0}, offset {1}, size {2}), wrong underlying {3} <> this {4}",
|
3569
|
view.GetHashCode(), view.offset, view.size, view.underlying.GetHashCode(), GetHashCode());
|
3570
|
retval = false;
|
3571
|
}
|
3572
|
if (view.stamp != stamp)
|
3573
|
{
|
3574
|
//Console.WriteLine("Bad view(hash {0}, offset {1}, size {2}), wrong stamp view:{2} underlying: {3}", view.GetHashCode(),view.offset, view.size, view.stamp, stamp);
|
3575
|
//retval = false;
|
3576
|
}
|
3577
|
}
|
3578
|
return retval;
|
3579
|
}
|
3580
|
|
3581
|
string zeitem(Node node)
|
3582
|
{
|
3583
|
return node == null ? "(null node)" : node.item.ToString();
|
3584
|
}
|
3585
|
|
3586
|
/// <summary>
|
3587
|
/// Check the sanity of this list
|
3588
|
/// </summary>
|
3589
|
/// <returns>true if sane</returns>
|
3590
|
[Tested]
|
3591
|
public virtual bool Check()
|
3592
|
{
|
3593
|
bool retval = true;
|
3594
|
|
3595
|
/*if (underlying != null && underlying.stamp != stamp)
|
3596
|
{
|
3597
|
Console.WriteLine("underlying != null && underlying.stamp({0}) != stamp({1})", underlying.stamp, stamp);
|
3598
|
retval = false;
|
3599
|
}*/
|
3600
|
|
3601
|
if (underlying != null)
|
3602
|
{
|
3603
|
//TODO: check that this view is included in viewsEndpoints tree
|
3604
|
return underlying.Check();
|
3605
|
}
|
3606
|
|
3607
|
if (startsentinel == null)
|
3608
|
{
|
3609
|
Console.WriteLine("startsentinel == null");
|
3610
|
retval = false;
|
3611
|
}
|
3612
|
|
3613
|
if (endsentinel == null)
|
3614
|
{
|
3615
|
Console.WriteLine("endsentinel == null");
|
3616
|
retval = false;
|
3617
|
}
|
3618
|
|
3619
|
if (size == 0)
|
3620
|
{
|
3621
|
if (startsentinel != null && startsentinel.next != endsentinel)
|
3622
|
{
|
3623
|
Console.WriteLine("size == 0 but startsentinel.next != endsentinel");
|
3624
|
retval = false;
|
3625
|
}
|
3626
|
|
3627
|
if (endsentinel != null && endsentinel.prev != startsentinel)
|
3628
|
{
|
3629
|
Console.WriteLine("size == 0 but endsentinel.prev != startsentinel");
|
3630
|
retval = false;
|
3631
|
}
|
3632
|
}
|
3633
|
|
3634
|
if (startsentinel == null)
|
3635
|
{
|
3636
|
Console.WriteLine("NULL startsentinel");
|
3637
|
return retval;
|
3638
|
}
|
3639
|
|
3640
|
int count = 0;
|
3641
|
Node node = startsentinel.next, prev = startsentinel;
|
3642
|
#if HASHINDEX
|
3643
|
int taggroupsize = 0, oldtaggroupsize = losize + 1, seentaggroups = 0;
|
3644
|
TagGroup oldtg = null;
|
3645
|
|
3646
|
if (underlying == null)
|
3647
|
{
|
3648
|
TagGroup tg = startsentinel.taggroup;
|
3649
|
|
3650
|
if (tg.count != 0 || tg.first != null || tg.last != null || tg.tag != int.MinValue)
|
3651
|
{
|
3652
|
Console.WriteLine("Bad startsentinel tag group: {0}", tg);
|
3653
|
retval = false;
|
3654
|
}
|
3655
|
|
3656
|
tg = endsentinel.taggroup;
|
3657
|
if (tg.count != 0 || tg.first != null || tg.last != null || tg.tag != int.MaxValue)
|
3658
|
{
|
3659
|
Console.WriteLine("Bad endsentinel tag group: {0}", tg);
|
3660
|
retval = false;
|
3661
|
}
|
3662
|
}
|
3663
|
#endif
|
3664
|
while (node != endsentinel)
|
3665
|
{
|
3666
|
count++;
|
3667
|
if (node.prev != prev)
|
3668
|
{
|
3669
|
Console.WriteLine("Bad backpointer at node {0}", count);
|
3670
|
retval = false;
|
3671
|
}
|
3672
|
#if HASHINDEX
|
3673
|
if (underlying == null)
|
3674
|
{
|
3675
|
if (!node.prev.precedes(node))
|
3676
|
{
|
3677
|
Console.WriteLine("node.prev.tag ({0}, {1}) >= node.tag ({2}, {3}) at index={4} item={5} ", node.prev.taggroup.tag, node.prev.tag, node.taggroup.tag, node.tag, count, node.item);
|
3678
|
retval = false;
|
3679
|
}
|
3680
|
|
3681
|
if (node.taggroup != oldtg)
|
3682
|
{
|
3683
|
|
3684
|
if (node.taggroup.first != node)
|
3685
|
{
|
3686
|
string ntfi = zeitem(node.taggroup.first);
|
3687
|
Console.WriteLine("Bad first pointer in taggroup: node.taggroup.first.item ({0}), node.item ({1}) at index={2} item={3}", ntfi, node.item, count, node.item);
|
3688
|
retval = false;
|
3689
|
}
|
3690
|
|
3691
|
if (oldtg != null)
|
3692
|
{
|
3693
|
if (oldtg.count != taggroupsize)
|
3694
|
{
|
3695
|
Console.WriteLine("Bad taggroupsize: oldtg.count ({0}) != taggroupsize ({1}) at index={2} item={3}", oldtg.count, taggroupsize, count, node.item);
|
3696
|
retval = false;
|
3697
|
}
|
3698
|
|
3699
|
if (oldtaggroupsize <= losize && taggroupsize <= losize)
|
3700
|
{
|
3701
|
Console.WriteLine("Two small taggroups in a row: oldtaggroupsize ({0}), taggroupsize ({1}) at index={2} item={3}", oldtaggroupsize, taggroupsize, count, node.item);
|
3702
|
retval = false;
|
3703
|
}
|
3704
|
|
3705
|
if (node.taggroup.tag <= oldtg.tag)
|
3706
|
{
|
3707
|
Console.WriteLine("Taggroup tags not strictly increasing: oldtaggrouptag ({0}), taggrouptag ({1}) at index={2} item={3}", oldtg.tag, node.taggroup.tag, count, node.item);
|
3708
|
retval = false;
|
3709
|
}
|
3710
|
|
3711
|
if (oldtg.last != node.prev)
|
3712
|
{
|
3713
|
Console.WriteLine("Bad last pointer in taggroup: oldtg.last.item ({0}), node.prev.item ({1}) at index={2} item={3}", oldtg.last.item, node.prev.item, count, node.item);
|
3714
|
retval = false;
|
3715
|
}
|
3716
|
|
3717
|
oldtaggroupsize = taggroupsize;
|
3718
|
}
|
3719
|
|
3720
|
seentaggroups++;
|
3721
|
oldtg = node.taggroup;
|
3722
|
taggroupsize = 1;
|
3723
|
}
|
3724
|
else
|
3725
|
{
|
3726
|
taggroupsize++;
|
3727
|
}
|
3728
|
}
|
3729
|
|
3730
|
#endif
|
3731
|
prev = node;
|
3732
|
node = node.next;
|
3733
|
if (node == null)
|
3734
|
{
|
3735
|
Console.WriteLine("Null next pointer at node {0}", count);
|
3736
|
return false;
|
3737
|
}
|
3738
|
}
|
3739
|
|
3740
|
#if HASHINDEX
|
3741
|
if (underlying == null && size == 0 && taggroups != 0)
|
3742
|
{
|
3743
|
Console.WriteLine("Bad taggroups for empty list: size={0} taggroups={1}", size, taggroups);
|
3744
|
retval = false;
|
3745
|
}
|
3746
|
if (underlying == null && size > 0)
|
3747
|
{
|
3748
|
oldtg = node.prev.taggroup;
|
3749
|
if (oldtg != null)
|
3750
|
{
|
3751
|
if (oldtg.count != taggroupsize)
|
3752
|
{
|
3753
|
Console.WriteLine("Bad taggroupsize: oldtg.count ({0}) != taggroupsize ({1}) at index={2} item={3}", oldtg.count, taggroupsize, count, node.item);
|
3754
|
retval = false;
|
3755
|
}
|
3756
|
|
3757
|
if (oldtaggroupsize <= losize && taggroupsize <= losize)
|
3758
|
{
|
3759
|
Console.WriteLine("Two small taggroups in a row: oldtaggroupsize ({0}), taggroupsize ({1}) at index={2} item={3}", oldtaggroupsize, taggroupsize, count, node.item);
|
3760
|
retval = false;
|
3761
|
}
|
3762
|
|
3763
|
if (node.taggroup.tag <= oldtg.tag)
|
3764
|
{
|
3765
|
Console.WriteLine("Taggroup tags not strictly increasing: oldtaggrouptag ({0}), taggrouptag ({1}) at index={2} item={3}", oldtg.tag, node.taggroup.tag, count, node.item);
|
3766
|
retval = false;
|
3767
|
}
|
3768
|
|
3769
|
if (oldtg.last != node.prev)
|
3770
|
{
|
3771
|
Console.WriteLine("Bad last pointer in taggroup: oldtg.last.item ({0}), node.prev.item ({1}) at index={2} item={3}", zeitem(oldtg.last), zeitem(node.prev), count, node.item);
|
3772
|
retval = false;
|
3773
|
}
|
3774
|
}
|
3775
|
|
3776
|
if (seentaggroups != taggroups)
|
3777
|
{
|
3778
|
Console.WriteLine("seentaggroups ({0}) != taggroups ({1}) (at size {2})", seentaggroups, taggroups, size);
|
3779
|
retval = false;
|
3780
|
}
|
3781
|
}
|
3782
|
#endif
|
3783
|
if (count != size)
|
3784
|
{
|
3785
|
Console.WriteLine("size={0} but enumeration gives {1} nodes ", size, count);
|
3786
|
retval = false;
|
3787
|
}
|
3788
|
|
3789
|
retval = checkViews() && retval;
|
3790
|
|
3791
|
#if HASHINDEX
|
3792
|
if (!retval)
|
3793
|
return false;
|
3794
|
if (underlying == null)
|
3795
|
{
|
3796
|
if (size != dict.Count)
|
3797
|
{
|
3798
|
Console.WriteLine("list.size ({0}) != dict.Count ({1})", size, dict.Count);
|
3799
|
retval = false;
|
3800
|
}
|
3801
|
Node n = startsentinel.next, n2;
|
3802
|
while (n != endsentinel)
|
3803
|
{
|
3804
|
if (!dict.Find(n.item, out n2))
|
3805
|
{
|
3806
|
Console.WriteLine("Item in list but not dict: {0}", n.item);
|
3807
|
retval = false;
|
3808
|
}
|
3809
|
else if (n != n2)
|
3810
|
{
|
3811
|
Console.WriteLine("Wrong node in dict for item: {0}", n.item);
|
3812
|
retval = false;
|
3813
|
}
|
3814
|
n = n.next;
|
3815
|
}
|
3816
|
}
|
3817
|
#endif
|
3818
|
return retval;
|
3819
|
}
|
3820
|
#endregion
|
3821
|
|
3822
|
#region ICloneable Members
|
3823
|
|
3824
|
/// <summary>
|
3825
|
/// Make a shallow copy of this LinkedList.
|
3826
|
/// </summary>
|
3827
|
/// <returns></returns>
|
3828
|
public virtual object Clone()
|
3829
|
{
|
3830
|
LinkedList<T> clone = new LinkedList<T>(itemequalityComparer);
|
3831
|
clone.AddAll(this);
|
3832
|
return clone;
|
3833
|
}
|
3834
|
|
3835
|
#endregion
|
3836
|
|
3837
|
#region System.Collections.Generic.IList<T> Members
|
3838
|
|
3839
|
void System.Collections.Generic.IList<T>.RemoveAt(int index)
|
3840
|
{
|
3841
|
RemoveAt(index);
|
3842
|
}
|
3843
|
|
3844
|
void System.Collections.Generic.ICollection<T>.Add(T item)
|
3845
|
{
|
3846
|
Add(item);
|
3847
|
}
|
3848
|
|
3849
|
#endregion
|
3850
|
|
3851
|
#region System.Collections.ICollection Members
|
3852
|
|
3853
|
bool System.Collections.ICollection.IsSynchronized
|
3854
|
{
|
3855
|
get { return false; }
|
3856
|
}
|
3857
|
|
3858
|
[Obsolete]
|
3859
|
Object System.Collections.ICollection.SyncRoot
|
3860
|
{
|
3861
|
// Presumably safe to use the startsentinel (of type Node, always != null) as SyncRoot
|
3862
|
// since the class Node is private.
|
3863
|
get { return underlying != null ? ((System.Collections.ICollection)underlying).SyncRoot : startsentinel; }
|
3864
|
}
|
3865
|
|
3866
|
void System.Collections.ICollection.CopyTo(Array arr, int index)
|
3867
|
{
|
3868
|
if (index < 0 || index + Count > arr.Length)
|
3869
|
throw new ArgumentOutOfRangeException();
|
3870
|
|
3871
|
foreach (T item in this)
|
3872
|
arr.SetValue(item, index++);
|
3873
|
}
|
3874
|
|
3875
|
#endregion
|
3876
|
|
3877
|
#region System.Collections.IList Members
|
3878
|
|
3879
|
Object System.Collections.IList.this[int index]
|
3880
|
{
|
3881
|
get { return this[index]; }
|
3882
|
set { this[index] = (T)value; }
|
3883
|
}
|
3884
|
|
3885
|
int System.Collections.IList.Add(Object o)
|
3886
|
{
|
3887
|
bool added = Add((T)o);
|
3888
|
// What position to report if item not added? SC.IList.Add doesn't say
|
3889
|
return added ? Count-1 : -1;
|
3890
|
}
|
3891
|
|
3892
|
bool System.Collections.IList.Contains(Object o)
|
3893
|
{
|
3894
|
return Contains((T)o);
|
3895
|
}
|
3896
|
|
3897
|
int System.Collections.IList.IndexOf(Object o)
|
3898
|
{
|
3899
|
return Math.Max(-1, IndexOf((T)o));
|
3900
|
}
|
3901
|
|
3902
|
void System.Collections.IList.Insert(int index, Object o)
|
3903
|
{
|
3904
|
Insert(index, (T)o);
|
3905
|
}
|
3906
|
|
3907
|
void System.Collections.IList.Remove(Object o)
|
3908
|
{
|
3909
|
Remove((T)o);
|
3910
|
}
|
3911
|
|
3912
|
void System.Collections.IList.RemoveAt(int index)
|
3913
|
{
|
3914
|
RemoveAt(index);
|
3915
|
}
|
3916
|
|
3917
|
#endregion
|
3918
|
}
|
3919
|
}
|