#region Copyright 2009-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0 /* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #endregion using System; using System.Collections; using System.Collections.Generic; using CSharpTest.Net.Interfaces; namespace CSharpTest.Net.Collections { /// Represents an immutable collection of unique items that can be manipulated as a set, intersect/union/etc. [System.Diagnostics.DebuggerDisplay("{ToArray()}")] public class SetList : IList, ICollection, IEnumerable, IList, ICollection, IEnumerable, ICloneable> { private static SetList __empty; /// Provides an empty set public static SetList EmptySet { get { return __empty ?? (__empty = new SetList(0, new T[0], Comparer.Default)); } } private readonly List _list; private readonly IComparer _comparer; /// Constructs a SetList public SetList() /* */ : this(0, EmptySet, Comparer.Default) { } /// Constructs a SetList public SetList(IComparer comparer) /* */ : this(0, EmptySet, comparer) { } /// Constructs a SetList public SetList(int capacity) /* */ : this(capacity, EmptySet, Comparer.Default) { } /// Constructs a SetList public SetList(int capacity, IComparer comparer) /* */ : this(capacity, EmptySet, comparer) { } /// Constructs a SetList public SetList(IEnumerable items) /* */ : this(0, items, Comparer.Default) { } /// Constructs a SetList public SetList(ICollection items) /* */ : this(items.Count, items, Comparer.Default) { } /// Constructs a SetList public SetList(IEnumerable items, IComparer comparer) /* */ : this(0, items, comparer) { } /// Constructs a SetList public SetList(ICollection items, IComparer comparer) /* */ : this(items.Count, items, comparer) { } private SetList(int capacity, IEnumerable items, IComparer comparer) { if (capacity < 0) throw new ArgumentOutOfRangeException("capacity"); if (items == null) throw new ArgumentNullException("items"); if (comparer == null) throw new ArgumentNullException("comparer"); _comparer = comparer; _list = new List(capacity); AddRange(items); } private SetList(List items, IComparer comparer) { _list = items; _comparer = comparer; } #region IList like members... /// Access an item by it's ordinal offset in the list public T this[int index] { get { return _list[index]; } set { throw new NotSupportedException(); } } /// Returns true if the list is read-only public bool IsReadOnly { get { return false; } } /// Returns the count of items in the list public int Count { get { return _list.Count; } } /// Returns the zero-based index of the item or -1 public int IndexOf(T item) { int pos = _list.BinarySearch(item, _comparer); return pos >= 0 ? pos : -1; } /// Returns true if the item is already in the collection public bool Contains(T item) { int pos = _list.BinarySearch(item, _comparer); return pos >= 0; } /// Copy the collection to an array public void CopyTo(T[] array, int arrayIndex) { _list.CopyTo(array, arrayIndex); } /// Returns this collection as an array public T[] ToArray() { return _list.ToArray(); } #endregion #region SetList manipulation: Add, Remove, IntersectWith, UnionWith, ComplementOf, RemoveAll, ExclusiveOrWith /// Removes all items from the collection public void Clear() { _list.Clear(); } /// Returns a new collection adding the item provided public void Add(T item) { int pos; Add(item, out pos); } /// Returns a new collection adding the item provided public void Add(T item, out int index) { int pos = _list.BinarySearch(item, _comparer); if (pos < 0) { _list.Insert(~pos, item); index = ~pos; } else index = pos; } /// Adds a range of items to the collection public void AddRange(IEnumerable other) { int pos; foreach (T item in other) { pos = _list.BinarySearch(item, _comparer); if (pos < 0) _list.Insert(~pos, item); } } /// Adds or replaces an item in the collection, returns true if an entry was replaced public bool Replace(T item) { int pos = _list.BinarySearch(item, _comparer); if (pos < 0) _list.Insert(~pos, item); else _list[pos] = item; return pos >= 0; } /// Adds or replaces an item in the collection, returns true if any item was replaced public bool ReplaceAll(IEnumerable items) { bool exists = false; foreach (T item in items) { int pos = _list.BinarySearch(item, _comparer); exists |= pos >= 0; if (pos < 0) _list.Insert(~pos, item); else _list[pos] = item; } return exists; } /// Not supported, the list is sorted. void IList.Insert(int index, T item) { throw new NotSupportedException(); } /// Returns a new collection with the item provided removed public bool Remove(T item) { int pos = _list.BinarySearch(item, _comparer); if (pos >= 0) { RemoveAt(pos); return true; } return false; } /// Removes an item by it's ordinal index in the collection public void RemoveAt(int index) { _list.RemoveAt(index); } /// Removes the items in this set that are not in the provided set /// { 1, 2, 3 }.RemoveAll({ 2, 3, 4 }) == { 1 } public void RemoveAll(IEnumerable other) { int pos; foreach (T item in other) { pos = _list.BinarySearch(item, _comparer); if (pos >= 0) _list.RemoveAt(pos); } } /// Returns the set of items that are in both this set and the provided set /// { 1, 2, 3 }.IntersectWith({ 2, 3, 4 }) == { 2, 3 } public SetList IntersectWith(SetList other) { List result = new List(Math.Max(0, _list.Count - other._list.Count)); int pos; foreach (T item in other._list) { pos = _list.BinarySearch(item, _comparer); if (pos >= 0) result.Add(item); } return new SetList(result, _comparer); } /// Returns the set of items that are in either this set or the provided set /// { 1, 2, 3 }.UnionWith({ 2, 3, 4 }) == { 1, 2, 3, 4 } public SetList UnionWith(SetList other) { SetList copy = this.Clone(); copy.AddRange(other._list); return copy; } /// Returns the items in the provided set that are not in this set /// { 1, 2, 3 }.ComplementOf({ 2, 3, 4 }) == { 4 } public SetList ComplementOf(SetList other) { List result = new List(other._list.Count); int pos; foreach (T item in other._list) { pos = _list.BinarySearch(item, _comparer); if (pos < 0) result.Add(item); } return new SetList(result, _comparer); } /// Returns the items in this set that are not in the provided set /// { 1, 2, 3 }.RemoveAll({ 2, 3, 4 }) == { 1 } public SetList SubtractSet(SetList other) { List result = new List(_list.Count); int pos; foreach (T item in _list) { pos = other._list.BinarySearch(item, _comparer); if (pos < 0) result.Add(item); } return new SetList(result, _comparer); } /// Returns the items in this set that are not in the provided set /// { 1, 2, 3 }.ExclusiveOrWith({ 2, 3, 4 }) == { 1, 4 } public SetList ExclusiveOrWith(SetList other) { List result = new List(other._list.Count); int pos; foreach (T item in other._list) { pos = _list.BinarySearch(item, _comparer); if (pos < 0) result.Add(item); } foreach (T item in _list) { pos = other._list.BinarySearch(item, _comparer); if (pos < 0) { pos = result.BinarySearch(item, _comparer); result.Insert(~pos, item); } } return new SetList(result, _comparer); } #endregion #region SetList testing subset/superset: IsEqualTo, IsSubsetOf, IsSupersetOf /// Returns true if all items in this set are also in the provided set /// { 1, 2 }.IsEqualTo({ 1, 2 }) == true && {}.IsEqualTo({}) == true public bool IsEqualTo(SetList other) { if (_list.Count != other._list.Count) return false; for (int i = 0; i < _list.Count; i++) { if (_comparer.Compare(_list[i], other._list[i]) != 0) return false; } return true; } /// Returns true if all items in this set are also in the provided set /// { 1, 2, 4 }.IsSubsetOf({ 1, 2, 3, 4 }) == true && {}.IsSubsetOf({ 1 }) == true public bool IsSubsetOf(SetList other) { int pos; foreach (T item in _list) { pos = other._list.BinarySearch(item, _comparer); if (pos < 0) return false; } return true; } /// Returns true if all items in the provided set are also in this set /// { 1, 2, 3, 4 }.IsSupersetOf({ 1, 2, 4 }) == true && { 1 }.IsSupersetOf({}) == true public bool IsSupersetOf(SetList other) { int pos; foreach (T item in other._list) { pos = _list.BinarySearch(item, _comparer); if (pos < 0) return false; } return true; } #endregion #region ICollection Members /// Copies collection to array void ICollection.CopyTo(Array array, int index) { ICollection c = _list; c.CopyTo(array, index); } /// Returns false bool ICollection.IsSynchronized { get { return false; } } /// Returns SyncRoot object ICollection.SyncRoot { get { return this; } } /// Returns an enumerator IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)_list).GetEnumerator(); } /// Returns a typed enumerator public IEnumerator GetEnumerator() { return _list.GetEnumerator(); } #endregion #region IList Members int IList.Add(object value) { if (value is T) { int pos; this.Add((T)value, out pos); return pos; } else throw new ArgumentException(); } bool IList.Contains(object value) { if (value is T) return this.Contains((T)value); else return false; } int IList.IndexOf(object value) { if (value is T) return this.IndexOf((T)value); else return -1; } void IList.Insert(int index, object value) { throw new NotSupportedException(); } bool IList.IsFixedSize { get { return false; } } void IList.Remove(object value) { if (value is T) this.Remove((T)value); else throw new ArgumentException(); } object IList.this[int index] { get { return this[index]; } set { throw new NotSupportedException(); } } #endregion #region ICloneable Members /// Returns a shallow clone of this object public SetList Clone() { return new SetList(new List(_list), _comparer); } object ICloneable.Clone() { return this.Clone(); } #endregion } }