/// <summary> /// Constructs a count dictionary, staying mindful of the known number of elements /// so that we bail early (returning null) if we detect a count mismatch /// </summary> private static CountingSet <TKey> TryBuildElementCountsWithKnownCount <TKey>( IEnumerator <TKey> elements, int remaining, IEqualityComparer <TKey> comparer) { if (remaining == 0) { // don't build the dictionary at all if nothing should be in it return(null); } const int MaxInitialElementCountsCapacity = 1024; var elementCounts = new CountingSet <TKey>(capacity: Math.Min(remaining, MaxInitialElementCountsCapacity), comparer: comparer); elementCounts.Increment(elements.Current); while (elements.MoveNext()) { if (--remaining < 0) { // too many elements return(null); } elementCounts.Increment(elements.Current); } if (remaining > 0) { // too few elements return(null); } return(elementCounts); }
public void RemoveItem_TheSameItemWasAddedTwice_CollectionStillContainsItem() { CountingSet <object> coll = new CountingSet <object>(); object item = new object(); coll.Add(item); coll.Add(item); coll.Remove(item); Assert.IsTrue(coll.Contains(item)); }
public void RemoveItemTwice_TheSameItemWasAddedTwice_CollectionDoesntContainItem() { CountingSet <object> coll = new CountingSet <object>(); object item = new object(); coll.Add(item); coll.Add(item); coll.Remove(item); coll.Remove(item); Assert.IsFalse(coll.Contains(item)); }
private static IUnsafeMarshaller <CountingSet <string> > CreateMarshaller() { return(new UniversalMarshaller <CountingSet <string> >(reader => { var count = reader.ReadInt32(); var set = new CountingSet <string>(count); for (int i = 0; i < count; i++) { set.Add(reader.ReadString(), reader.ReadInt32()); } return set; }, (writer, value) => { writer.Write(value.Count); foreach (var(item, count) in value) { writer.Write(item); writer.Write(count); } }));
/// <summary> /// Determines whether <paramref name="this"/> and <paramref name="that"/> are equal in the sense of having the exact same /// elements. Unlike <see cref="Enumerable.SequenceEqual{TSource}(IEnumerable{TSource}, IEnumerable{TSource})"/>, /// this method disregards order. Unlike <see cref="ISet{T}.SetEquals(IEnumerable{T})"/>, this method does not disregard duplicates. /// An optional <paramref name="comparer"/> allows the equality semantics for the elements to be specified /// </summary> public static bool CollectionEquals <TElement>(this IEnumerable <TElement> @this, IEnumerable <TElement> that, IEqualityComparer <TElement> comparer = null) { if (@this == null) { throw new ArgumentNullException(nameof(@this)); } if (that == null) { throw new ArgumentNullException(nameof(that)); } // FastCount optimization: If both of the collections are materialized and have counts, // we can exit very quickly if those counts differ int thisCount, thatCount; var hasThisCount = TryFastCount(@this, out thisCount); bool hasThatCount; if (hasThisCount) { hasThatCount = TryFastCount(that, out thatCount); if (hasThatCount) { if (thisCount != thatCount) { return(false); } if (thisCount == 0) { return(true); } } } else { hasThatCount = false; } var cmp = comparer ?? EqualityComparer <TElement> .Default; var itemsEnumerated = 0; // SequenceEqual optimization: we reduce/avoid hashing // the collections have common prefixes, at the cost of only one // extra Equals() call in the case where the prefixes are not common using (var thisEnumerator = @this.GetEnumerator()) using (var thatEnumerator = that.GetEnumerator()) { while (true) { var thisFinished = !thisEnumerator.MoveNext(); var thatFinished = !thatEnumerator.MoveNext(); if (thisFinished) { // either this shorter than that, or the two were sequence-equal return(thatFinished); } if (thatFinished) { // that shorter than this return(false); } // keep track of this so that we can factor it into count-based // logic below ++itemsEnumerated; if (!cmp.Equals(thisEnumerator.Current, thatEnumerator.Current)) { break; // prefixes were not equal } } // now, build a dictionary of item => count out of one collection and then // probe it with the other collection to look for mismatches // Build/Probe Choice optimization: if we know the count of one collection, we should // use the other collection to build the dictionary. That way we can bail immediately if // we see too few or too many items CountingSet <TElement> elementCounts; IEnumerator <TElement> probeSide; if (hasThisCount) { // we know this's count => use that as the build side probeSide = thisEnumerator; var remaining = thisCount - itemsEnumerated; if (hasThatCount) { // if we have both counts, that means they must be equal or we would have already // exited. However, in this case, we know exactly the capacity needed for the dictionary // so we can avoid resizing elementCounts = new CountingSet <TElement>(capacity: remaining, comparer: cmp); do { elementCounts.Increment(thatEnumerator.Current); }while (thatEnumerator.MoveNext()); } else { elementCounts = TryBuildElementCountsWithKnownCount(thatEnumerator, remaining, cmp); } } else if (TryFastCount(that, out thatCount)) { // we know that's count => use this as the build side probeSide = thatEnumerator; var remaining = thatCount - itemsEnumerated; elementCounts = TryBuildElementCountsWithKnownCount(thisEnumerator, remaining, cmp); } else { // when we don't know either count, just use that as the build side arbitrarily probeSide = thisEnumerator; elementCounts = new CountingSet <TElement>(cmp); do { elementCounts.Increment(thatEnumerator.Current); }while (thatEnumerator.MoveNext()); } // check whether we failed to construct a dictionary. This happens when we know // one of the counts and we detect, during construction, that the counts are unequal if (elementCounts == null) { return(false); } // probe the dictionary with the probe side enumerator do { if (!elementCounts.TryDecrement(probeSide.Current)) { // element in probe not in build => not equal return(false); } }while (probeSide.MoveNext()); // we are equal only if the loop above completely cleared out the dictionary return(elementCounts.IsEmpty); } }