#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
}
}