Project

General

Profile

root / branches / compiler / cSharp / ooasCompiler / src / libs / c5 / C5 / hashing / HashBag.cs @ 3

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
using System;
23
using System.Diagnostics;
24
using SCG = System.Collections.Generic;
25

    
26
namespace C5
27
{
28
  /// <summary>
29
  /// A bag collection based on a hash table of (item,count) pairs. 
30
  /// </summary>
31
  [Serializable]
32
  public class HashBag<T> : CollectionBase<T>, ICollection<T>
33
  {
34
    #region Fields
35
    HashSet<KeyValuePair<T, int>> dict;
36
    #endregion
37

    
38
    #region Events
39

    
40
    /// <summary>
41
    /// 
42
    /// </summary>
43
    /// <value></value>
44
    public override EventTypeEnum ListenableEvents { get { return EventTypeEnum.Basic; } }
45

    
46
    #endregion
47

    
48
    #region Constructors
49
    /// <summary>
50
    /// Create a hash bag with the deafult item equalityComparer.
51
    /// </summary>
52
    public HashBag() : this(EqualityComparer<T>.Default) { }
53

    
54
    /// <summary>
55
    /// Create a hash bag with an external item equalityComparer.
56
    /// </summary>
57
    /// <param name="itemequalityComparer">The external item equalityComparer.</param>
58
    public HashBag(SCG.IEqualityComparer<T> itemequalityComparer)
59
      : base(itemequalityComparer)
60
    {
61
      dict = new HashSet<KeyValuePair<T, int>>(new KeyValuePairEqualityComparer<T, int>(itemequalityComparer));
62
    }
63

    
64
    /// <summary>
65
    /// Create a hash bag with external item equalityComparer, prescribed initial table size and default fill threshold (66%)
66
    /// </summary>
67
    /// <param name="capacity">Initial table size (rounded to power of 2, at least 16)</param>
68
    /// <param name="itemequalityComparer">The external item equalityComparer</param>
69
    public HashBag(int capacity, SCG.IEqualityComparer<T> itemequalityComparer)
70
      : base(itemequalityComparer)
71
    {
72
      dict = new HashSet<KeyValuePair<T, int>>(capacity, new KeyValuePairEqualityComparer<T, int>(itemequalityComparer));
73
    }
74

    
75

    
76
    /// <summary>
77
    /// Create a hash bag with external item equalityComparer, prescribed initial table size and fill threshold.
78
    /// </summary>
79
    /// <param name="capacity">Initial table size (rounded to power of 2, at least 16)</param>
80
    /// <param name="fill">Fill threshold (valid range 10% to 90%)</param>
81
    /// <param name="itemequalityComparer">The external item equalityComparer</param>
82
    public HashBag(int capacity, double fill, SCG.IEqualityComparer<T> itemequalityComparer)
83
      : base(itemequalityComparer)
84
    {
85
      dict = new HashSet<KeyValuePair<T, int>>(capacity, fill, new KeyValuePairEqualityComparer<T, int>(itemequalityComparer));
86
    }
87

    
88
    #endregion
89

    
90
    #region IEditableCollection<T> Members
91

    
92
    /// <summary>
93
    /// The complexity of the Contains operation
94
    /// </summary>
95
    /// <value>Always returns Speed.Constant</value>
96
    [Tested]
97
    public virtual Speed ContainsSpeed { [Tested]get { return Speed.Constant; } }
98

    
99
    /// <summary>
100
    /// Check if an item is in the bag 
101
    /// </summary>
102
    /// <param name="item">The item to look for</param>
103
    /// <returns>True if bag contains item</returns>
104
    [Tested]
105
    public virtual bool Contains(T item)
106
    { return dict.Contains(new KeyValuePair<T, int>(item, 0)); }
107

    
108

    
109
    /// <summary>
110
    /// Check if an item (collection equal to a given one) is in the bag and
111
    /// if so report the actual item object found.
112
    /// </summary>
113
    /// <param name="item">On entry, the item to look for.
114
    /// On exit the item found, if any</param>
115
    /// <returns>True if bag contains item</returns>
116
    [Tested]
117
    public virtual bool Find(ref T item)
118
    {
119
      KeyValuePair<T, int> p = new KeyValuePair<T, int>(item, 0);
120

    
121
      if (dict.Find(ref p))
122
      {
123
        item = p.Key;
124
        return true;
125
      }
126

    
127
      return false;
128
    }
129

    
130

    
131
    /// <summary>
132
    /// Check if an item (collection equal to a given one) is in the bag and
133
    /// if so replace the item object in the bag with the supplied one.
134
    /// </summary>
135
    /// <param name="item">The item object to update with</param>
136
    /// <returns>True if item was found (and updated)</returns>
137
    [Tested]
138
    public virtual bool Update(T item)
139
    { T olditem = default(T); return Update(item, out olditem); }
140

    
141

    
142
    /// <summary>
143
    /// 
144
    /// </summary>
145
    /// <param name="item"></param>
146
    /// <param name="olditem"></param>
147
    /// <returns></returns>
148
    public virtual bool Update(T item, out T olditem)
149
    {
150
      KeyValuePair<T, int> p = new KeyValuePair<T, int>(item, 0);
151

    
152
      updatecheck();
153

    
154
      //Note: we cannot just do dict.Update: we have to lookup the count before we 
155
      //know what to update with. There is of course a way around if we use the 
156
      //implementation of hashset -which we do not want to do.
157
      //The hashbag is moreover mainly a proof of concept
158
      if (dict.Find(ref p))
159
      {
160
        olditem = p.Key;
161
        p.Key = item;
162
        dict.Update(p);
163
        if (ActiveEvents != 0)
164
          raiseForUpdate(item, olditem, p.Value);
165
        return true;
166
      }
167

    
168
      olditem = default(T);
169
      return false;
170
    }
171

    
172

    
173
    /// <summary>
174
    /// Check if an item (collection equal to a given one) is in the bag.
175
    /// If found, report the actual item object in the bag,
176
    /// else add the supplied one.
177
    /// </summary>
178
    /// <param name="item">On entry, the item to look for or add.
179
    /// On exit the actual object found, if any.</param>
180
    /// <returns>True if item was found</returns>
181
    [Tested]
182
    public virtual bool FindOrAdd(ref T item)
183
    {
184
      updatecheck();
185
      if (Find(ref item))
186
        return true;
187

    
188
      Add(item);
189
      return false;
190
    }
191

    
192

    
193
    /// <summary>
194
    /// Check if an item (collection equal to a supplied one) is in the bag and
195
    /// if so replace the item object in the set with the supplied one; else
196
    /// add the supplied one.
197
    /// </summary>
198
    /// <param name="item">The item to look for and update or add</param>
199
    /// <returns>True if item was updated</returns>
200
    [Tested]
201
    public virtual bool UpdateOrAdd(T item)
202
    {
203
      updatecheck();
204
      if (Update(item))
205
        return true;
206

    
207
      Add(item);
208
      return false;
209
    }
210

    
211
    /// <summary>
212
    /// 
213
    /// </summary>
214
    /// <param name="item"></param>
215
    /// <param name="olditem"></param>
216
    /// <returns></returns>
217
    public virtual bool UpdateOrAdd(T item, out T olditem)
218
    {
219
      updatecheck();
220
      if (Update(item, out olditem))
221
        return true;
222

    
223
      Add(item);
224
      return false;
225
    }
226

    
227
    /// <summary>
228
    /// Remove one copy af an item from the bag
229
    /// </summary>
230
    /// <param name="item">The item to remove</param>
231
    /// <returns>True if item was (found and) removed </returns>
232
    [Tested]
233
    public virtual bool Remove(T item)
234
    {
235
      KeyValuePair<T, int> p = new KeyValuePair<T, int>(item, 0);
236

    
237
      updatecheck();
238
      if (dict.Find(ref p))
239
      {
240
        size--;
241
        if (p.Value == 1)
242
          dict.Remove(p);
243
        else
244
        {
245
          p.Value--;
246
          dict.Update(p);
247
        }
248
        if (ActiveEvents != 0)
249
          raiseForRemove(p.Key);
250
        return true;
251
      }
252

    
253
      return false;
254
    }
255

    
256

    
257
    /// <summary>
258
    /// Remove one copy of an item from the bag, reporting the actual matching item object.
259
    /// </summary>
260
    /// <param name="item">The value to remove.</param>
261
    /// <param name="removeditem">The removed value.</param>
262
    /// <returns>True if item was found.</returns>
263
    [Tested]
264
    public virtual bool Remove(T item, out T removeditem)
265
    {
266
      updatecheck();
267
      KeyValuePair<T, int> p = new KeyValuePair<T, int>(item, 0);
268
      if (dict.Find(ref p))
269
      {
270
        removeditem = p.Key;
271
        size--;
272
        if (p.Value == 1)
273
          dict.Remove(p);
274
        else
275
        {
276
          p.Value--;
277
          dict.Update(p);
278
        }
279
        if (ActiveEvents != 0)
280
          raiseForRemove(removeditem);
281

    
282
        return true;
283
      }
284

    
285
      removeditem = default(T);
286
      return false;
287
    }
288

    
289
    /// <summary>
290
    /// Remove all items in a supplied collection from this bag, counting multiplicities.
291
    /// </summary>
292
    /// <typeparam name="U"></typeparam>
293
    /// <param name="items">The items to remove.</param>
294
    [Tested]
295
    public virtual void RemoveAll<U>(SCG.IEnumerable<U> items) where U : T
296
    {
297
#warning Improve if items is a counting bag
298
      updatecheck();
299
      bool mustRaise = (ActiveEvents & (EventTypeEnum.Changed | EventTypeEnum.Removed)) != 0;
300
      RaiseForRemoveAllHandler raiseHandler = mustRaise ? new RaiseForRemoveAllHandler(this) : null;
301
      foreach (U item in items)
302
      {
303
        KeyValuePair<T, int> p = new KeyValuePair<T, int>(item, 0);
304
        if (dict.Find(ref p))
305
        {
306
          size--;
307
          if (p.Value == 1)
308
            dict.Remove(p);
309
          else
310
          {
311
            p.Value--;
312
            dict.Update(p);
313
          }
314
          if (mustRaise)
315
            raiseHandler.Remove(p.Key);
316
        }
317
      }
318
      if (mustRaise)
319
        raiseHandler.Raise();
320
    }
321

    
322
    /// <summary>
323
    /// Remove all items from the bag, resetting internal table to initial size.
324
    /// </summary>
325
    [Tested]
326
    public virtual void Clear()
327
    {
328
      updatecheck();
329
      if (size == 0)
330
        return;
331
      dict.Clear();
332
      int oldsize = size;
333
      size = 0;
334
      if ((ActiveEvents & EventTypeEnum.Cleared) != 0)
335
        raiseCollectionCleared(true, oldsize);
336
      if ((ActiveEvents & EventTypeEnum.Changed) != 0)
337
        raiseCollectionChanged();
338
    }
339

    
340

    
341
    /// <summary>
342
    /// Remove all items *not* in a supplied collection from this bag,
343
    /// counting multiplicities.
344
    /// </summary>
345
    /// <typeparam name="U"></typeparam>
346
    /// <param name="items">The items to retain</param>
347
    [Tested]
348
    public virtual void RetainAll<U>(SCG.IEnumerable<U> items) where U : T
349
    {
350
      updatecheck();
351

    
352
      HashBag<T> res = new HashBag<T>(itemequalityComparer);
353

    
354
      foreach (U item in items)
355
      {
356
        KeyValuePair<T, int> p = new KeyValuePair<T, int>(item);
357
        if (dict.Find(ref p))
358
        {
359
          KeyValuePair<T, int> q = p;
360
          if (res.dict.Find(ref q))
361
          {
362
            if (q.Value < p.Value)
363
            {
364
              q.Value++;
365
              res.dict.Update(q);
366
              res.size++;
367
            }
368
          }
369
          else
370
          {
371
            q.Value = 1;
372
            res.dict.Add(q);
373
            res.size++;
374
          }
375
        }
376
      }
377

    
378
      if (size == res.size)
379
        return;
380

    
381
      CircularQueue<T> wasRemoved = null;
382
      if ((ActiveEvents & EventTypeEnum.Removed) != 0)
383
      {
384
        wasRemoved = new CircularQueue<T>();
385
        foreach (KeyValuePair<T, int> p in dict)
386
        {
387
          int removed = p.Value - res.ContainsCount(p.Key);
388
          if (removed > 0)
389
#warning We could send bag events here easily using a CircularQueue of (should?)
390
            for (int i = 0; i < removed; i++)
391
              wasRemoved.Enqueue(p.Key);
392
        }
393
      }
394
      dict = res.dict;
395
      size = res.size;
396

    
397
      if ((ActiveEvents & EventTypeEnum.Removed) != 0)
398
        raiseForRemoveAll(wasRemoved);
399
      else if ((ActiveEvents & EventTypeEnum.Changed) != 0)
400
        raiseCollectionChanged();
401
    }
402

    
403
    /// <summary>
404
    /// Check if all items in a supplied collection is in this bag
405
    /// (counting multiplicities). 
406
    /// </summary>
407
    /// <param name="items">The items to look for.</param>
408
    /// <typeparam name="U"></typeparam>
409
    /// <returns>True if all items are found.</returns>
410
    [Tested]
411
    public virtual bool ContainsAll<U>(SCG.IEnumerable<U> items) where U : T
412
    {
413
      HashBag<T> res = new HashBag<T>(itemequalityComparer);
414

    
415
      foreach (T item in items)
416
        if (res.ContainsCount(item) < ContainsCount(item))
417
          res.Add(item);
418
        else
419
          return false;
420

    
421
      return true;
422
    }
423

    
424

    
425
    /// <summary>
426
    /// Create an array containing all items in this bag (in enumeration order).
427
    /// </summary>
428
    /// <returns>The array</returns>
429
    [Tested]
430
    public override T[] ToArray()
431
    {
432
      T[] res = new T[size];
433
      int ind = 0;
434

    
435
      foreach (KeyValuePair<T, int> p in dict)
436
        for (int i = 0; i < p.Value; i++)
437
          res[ind++] = p.Key;
438

    
439
      return res;
440
    }
441

    
442

    
443
    /// <summary>
444
    /// Count the number of times an item is in this set.
445
    /// </summary>
446
    /// <param name="item">The item to look for.</param>
447
    /// <returns>The count</returns>
448
    [Tested]
449
    public virtual int ContainsCount(T item)
450
    {
451
      KeyValuePair<T, int> p = new KeyValuePair<T, int>(item, 0);
452

    
453
      if (dict.Find(ref p))
454
        return p.Value;
455

    
456
      return 0;
457
    }
458

    
459
    /// <summary>
460
    /// 
461
    /// </summary>
462
    /// <returns></returns>
463
    public virtual ICollectionValue<T> UniqueItems() { return new DropMultiplicity<T>(dict); }
464

    
465
    /// <summary>
466
    /// 
467
    /// </summary>
468
    /// <returns></returns>
469
    public virtual ICollectionValue<KeyValuePair<T, int>> ItemMultiplicities()
470
    {
471
      return new GuardedCollectionValue<KeyValuePair<T, int>>(dict);
472
    }
473

    
474
    /// <summary>
475
    /// Remove all copies of item from this set.
476
    /// </summary>
477
    /// <param name="item">The item to remove</param>
478
    [Tested]
479
    public virtual void RemoveAllCopies(T item)
480
    {
481
      updatecheck();
482

    
483
      KeyValuePair<T, int> p = new KeyValuePair<T, int>(item, 0);
484

    
485
      if (dict.Find(ref p))
486
      {
487
        size -= p.Value;
488
        dict.Remove(p);
489
        if ((ActiveEvents & EventTypeEnum.Removed) != 0)
490
          raiseItemsRemoved(p.Key, p.Value);
491
        if ((ActiveEvents & EventTypeEnum.Changed) != 0)
492
          raiseCollectionChanged();
493
      }
494
    }
495

    
496
    #endregion
497

    
498
    #region ICollection<T> Members
499

    
500

    
501
    /// <summary>
502
    /// Copy the items of this bag to part of an array.
503
    /// <exception cref="ArgumentOutOfRangeException"/> if i is negative.
504
    /// <exception cref="ArgumentException"/> if the array does not have room for the items.
505
    /// </summary>
506
    /// <param name="array">The array to copy to</param>
507
    /// <param name="index">The starting index.</param>
508
    [Tested]
509
    public override void CopyTo(T[] array, int index)
510
    {
511
      if (index < 0 || index + Count > array.Length)
512
        throw new ArgumentOutOfRangeException();
513

    
514
      foreach (KeyValuePair<T, int> p in dict)
515
        for (int j = 0; j < p.Value; j++)
516
          array[index++] = p.Key;
517
    }
518

    
519
    #endregion
520

    
521
    #region IExtensible<T> Members
522

    
523
    /// <summary>
524
    /// Report if this is a set collection.
525
    /// </summary>
526
    /// <value>Always true</value>
527
    [Tested]
528
    public virtual bool AllowsDuplicates { [Tested] get { return true; } }
529

    
530
    /// <summary>
531
    /// By convention this is true for any collection with set semantics.
532
    /// </summary>
533
    /// <value>True if only one representative of a group of equal items 
534
    /// is kept in the collection together with the total count.</value>
535
    public virtual bool DuplicatesByCounting { get { return true; } }
536

    
537
    /// <summary>
538
    /// Add an item to this bag.
539
    /// </summary>
540
    /// <param name="item">The item to add.</param>
541
    /// <returns>Always true</returns>
542
    [Tested]
543
    public virtual bool Add(T item)
544
    {
545
      updatecheck();
546
      add(ref item);
547
      if (ActiveEvents != 0)
548
        raiseForAdd(item);
549
      return true;
550
    }
551

    
552
    /// <summary>
553
    /// Add an item to this bag.
554
    /// </summary>
555
    /// <param name="item">The item to add.</param>
556
    [Tested]
557
    void SCG.ICollection<T>.Add(T item)
558
    {
559
        Add(item);
560
    }
561

    
562
    private void add(ref T item)
563
    {
564
      KeyValuePair<T, int> p = new KeyValuePair<T, int>(item, 1);
565
      if (dict.Find(ref p))
566
      {
567
        p.Value++;
568
        dict.Update(p);
569
        item = p.Key;
570
      }
571
      else
572
        dict.Add(p);
573
      size++;
574
    }
575

    
576
    /// <summary>
577
    /// Add the elements from another collection with a more specialized item type 
578
    /// to this collection. 
579
    /// </summary>
580
    /// <typeparam name="U">The type of items to add</typeparam>
581
    /// <param name="items">The items to add</param>
582
    public virtual void AddAll<U>(SCG.IEnumerable<U> items) where U : T
583
    {
584
      updatecheck();
585
#warning We could easily raise bag events
586
      bool mustRaiseAdded = (ActiveEvents & EventTypeEnum.Added) != 0;
587
      CircularQueue<T> wasAdded = mustRaiseAdded ? new CircularQueue<T>() : null;
588
      bool wasChanged = false;
589
      foreach (T item in items)
590
      {
591
        T jtem = item;
592
        add(ref jtem);
593
        wasChanged = true;
594
        if (mustRaiseAdded)
595
          wasAdded.Enqueue(jtem);
596
      }
597
      if (!wasChanged)
598
        return;
599
      if (mustRaiseAdded)
600
        foreach (T item in wasAdded)
601
          raiseItemsAdded(item, 1);
602
      if ((ActiveEvents & EventTypeEnum.Changed) != 0)
603
        raiseCollectionChanged();
604
    }
605

    
606
    #endregion
607

    
608
    #region IEnumerable<T> Members
609

    
610

    
611
    /// <summary>
612
    /// Choose some item of this collection. 
613
    /// </summary>
614
    /// <exception cref="NoSuchItemException">if collection is empty.</exception>
615
    /// <returns></returns>
616
    public override T Choose()
617
    {
618
      return dict.Choose().Key;
619
    }
620

    
621
    /// <summary>
622
    /// Create an enumerator for this bag.
623
    /// </summary>
624
    /// <returns>The enumerator</returns>
625
    [Tested]
626
    public override SCG.IEnumerator<T> GetEnumerator()
627
    {
628
      int left;
629
      int mystamp = stamp;
630

    
631
      foreach (KeyValuePair<T, int> p in dict)
632
      {
633
        left = p.Value;
634
        while (left > 0)
635
        {
636
          if (mystamp != stamp)
637
            throw new CollectionModifiedException();
638

    
639
          left--;
640
          yield return p.Key;
641
        }
642
      }
643
    }
644
    #endregion
645

    
646
    #region ICloneable Members
647

    
648
    /// <summary>
649
    /// Make a shallow copy of this HashBag.
650
    /// </summary>
651
    /// <returns></returns>
652
    public virtual object Clone()
653
    {
654
      //TODO: make sure this 
655
      HashBag<T> clone = new HashBag<T>(dict.Count > 0 ? dict.Count : 1, itemequalityComparer);
656
      //TODO: make sure this really adds in the counting bag way!
657
      clone.AddAll(this);
658
      return clone;
659
    }
660

    
661
    #endregion
662

    
663

    
664
    #region Diagnostics
665
    /// <summary>
666
    /// Test internal structure of data (invariants)
667
    /// </summary>
668
    /// <returns>True if pass</returns>
669
    [Tested]
670
    public virtual bool Check()
671
    {
672
      bool retval = dict.Check();
673
      int count = 0;
674

    
675
      foreach (KeyValuePair<T, int> p in dict)
676
        count += p.Value;
677

    
678
      if (count != size)
679
      {
680
        Console.WriteLine("count({0}) != size({1})", count, size);
681
        retval = false;
682
      }
683

    
684
      return retval;
685
    }
686
    #endregion
687
  }
688
}