I am writing a program to set a sequence in which various objects will appear in report. The sequence is the Y position (cell) on Excel spreadsheet.
A demo part of co
The key (pun intended) to this is to create an IComparable
-based class that maintains equality and hashing, but never compares to 0 if not equal. This can be done, and can be created with a couple bonuses - stable sorting (that is, values added to the sorted list first will maintain their position), and ToString()
can simply return the actual key string value.
Here's a struct key that should do the trick:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace System
{
/// <summary>
/// Defined in Totlsoft.Util.
/// A key that will always be unique but compares
/// primarily on the Key property, which is not required
/// to be unique.
/// </summary>
public struct StableKey : IComparable<StableKey>, IComparable
{
private static long s_Next;
private long m_Sequence;
private IComparable m_Key;
/// <summary>
/// Defined in Totlsoft.Util.
/// Constructs a StableKey with the given IComparable key.
/// </summary>
/// <param name="key"></param>
public StableKey( IComparable key )
{
if( null == key )
throw new ArgumentNullException( "key" );
m_Sequence = Interlocked.Increment( ref s_Next );
m_Key = key;
}
/// <summary>
/// Overridden. True only if internal sequence and the
/// Key are equal.
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool Equals( object obj )
{
if( !( obj is StableKey ) )
return false;
var dk = (StableKey)obj;
return m_Sequence.Equals( dk.m_Sequence ) &&
Key.Equals( dk.Key );
}
/// <summary>
/// Overridden. Gets the hash code of the internal
/// sequence and the Key.
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
return m_Sequence.GetHashCode() ^ Key.GetHashCode();
}
/// <summary>
/// Overridden. Returns Key.ToString().
/// </summary>
/// <returns></returns>
public override string ToString()
{
return Key.ToString();
}
/// <summary>
/// The key that will be compared on.
/// </summary>
public IComparable Key
{
get
{
if( null == m_Key )
return 0;
return m_Key;
}
}
#region IComparable<StableKey> Members
/// <summary>
/// Compares this Key property to another. If they
/// are the same, compares the incremented value.
/// </summary>
/// <param name="other"></param>
/// <returns></returns>
public int CompareTo( StableKey other )
{
var cmp = Key.CompareTo( other.Key );
if( cmp == 0 )
cmp = m_Sequence.CompareTo( other.m_Sequence );
return cmp;
}
#endregion
#region IComparable Members
int IComparable.CompareTo( object obj )
{
return CompareTo( (StableKey)obj );
}
#endregion
}
}
Simplest solution (compared to all of the above): use SortedSet<T>
, it accepts an IComparer<SortableKey>
class, then implement the Compare method this way:
public int Compare(SomeClass x, SomeClass y)
{
var compared = x.SomeSortableKeyTypeField.CompareTo(y.SomeSortableKeyTypeField);
if (compared != 0)
return compared;
// to allow duplicates
var hashCodeCompare = x.GetHashCode().CompareTo(y.GetHashCode());
if (hashCodeCompare != 0)
return hashCodeCompare;
if (Object.ReferenceEquals(x, y))
return 0;
// for weird duplicate hashcode cases, throw as below or implement your last chance comparer
throw new ComparisonFailureException();
}
I use the following:
public class TupleList<T1, T2> : List<Tuple<T1, T2>> where T1 : IComparable
{
public void Add(T1 item, T2 item2)
{
Add(new Tuple<T1, T2>(item, item2));
}
public new void Sort()
{
Comparison<Tuple<T1, T2>> c = (a, b) => a.Item1.CompareTo(b.Item1);
base.Sort(c);
}
}
My test case:
[TestMethod()]
public void SortTest()
{
TupleList<int, string> list = new TupleList<int, string>();
list.Add(1, "cat");
list.Add(1, "car");
list.Add(2, "dog");
list.Add(2, "door");
list.Add(3, "elephant");
list.Add(1, "coconut");
list.Add(1, "cab");
list.Sort();
foreach(Tuple<int, string> tuple in list)
{
Console.WriteLine(string.Format("{0}:{1}", tuple.Item1,tuple.Item2));
}
int expected_first = 1;
int expected_last = 3;
int first = list.First().Item1; //requires using System.Linq
int last = list.Last().Item1; //requires using System.Linq
Assert.AreEqual(expected_first, first);
Assert.AreEqual(expected_last, last);
}
The output:
1:cab
1:coconut
1:car
1:cat
2:door
2:dog
3:elephant
This collection class will maintain duplicates and insert sort order for the duplicate. The trick is to tag the items with a unique value as they are inserted to maintain a stable sort order. Then we wrap it all up in an ICollection interface.
public class SuperSortedSet<TValue> : ICollection<TValue>
{
private readonly SortedSet<Indexed<TValue>> _Container;
private int _Index = 0;
private IComparer<TValue> _Comparer;
public SuperSortedSet(IComparer<TValue> comparer)
{
_Comparer = comparer;
var c2 = new System.Linq.Comparer<Indexed<TValue>>((p0, p1) =>
{
var r = _Comparer.Compare(p0.Value, p1.Value);
if (r == 0)
{
if (p0.Index == -1
|| p1.Index == -1)
return 0;
return p0.Index.CompareTo(p1.Index);
}
else return r;
});
_Container = new SortedSet<Indexed<TValue>>(c2);
}
public IEnumerator<TValue> GetEnumerator() { return _Container.Select(p => p.Value).GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
public void Add(TValue item) { _Container.Add(Indexed.Create(_Index++, item)); }
public void Clear() { _Container.Clear();}
public bool Contains(TValue item) { return _Container.Contains(Indexed.Create(-1,item)); }
public void CopyTo(TValue[] array, int arrayIndex)
{
foreach (var value in this)
{
if (arrayIndex >= array.Length)
{
throw new ArgumentException("Not enough space in array");
}
array[arrayIndex] = value;
arrayIndex++;
}
}
public bool Remove(TValue item) { return _Container.Remove(Indexed.Create(-1, item)); }
public int Count {
get { return _Container.Count; }
}
public bool IsReadOnly {
get { return false; }
}
}
a test class
[Fact]
public void ShouldWorkWithSuperSortedSet()
{
// Sort points according to X
var set = new SuperSortedSet<Point2D>
(new System.Linq.Comparer<Point2D>((p0, p1) => p0.X.CompareTo(p1.X)));
set.Add(new Point2D(9,10));
set.Add(new Point2D(1,25));
set.Add(new Point2D(11,-10));
set.Add(new Point2D(2,99));
set.Add(new Point2D(5,55));
set.Add(new Point2D(5,23));
set.Add(new Point2D(11,11));
set.Add(new Point2D(21,12));
set.Add(new Point2D(-1,76));
set.Add(new Point2D(16,21));
var xs = set.Select(p=>p.X).ToList();
xs.Should().BeInAscendingOrder();
xs.Count.Should()
.Be(10);
xs.ShouldBeEquivalentTo(new[]{-1,1,2,5,5,9,11,11,16,21});
set.Remove(new Point2D(5,55));
xs = set.Select(p=>p.X).ToList();
xs.Count.Should()
.Be(9);
xs.ShouldBeEquivalentTo(new[]{-1,1,2,5,9,11,11,16,21});
set.Remove(new Point2D(5,23));
xs = set.Select(p=>p.X).ToList();
xs.Count.Should()
.Be(8);
xs.ShouldBeEquivalentTo(new[]{-1,1,2,9,11,11,16,21});
set.Contains(new Point2D(11, 11))
.Should()
.BeTrue();
set.Contains(new Point2D(-1, 76))
.Should().BeTrue();
// Note that the custom compartor function ignores the Y value
set.Contains(new Point2D(-1, 66))
.Should().BeTrue();
set.Contains(new Point2D(27, 66))
.Should().BeFalse();
}
The tagging struct
public struct Indexed<T>
{
public int Index { get; private set; }
public T Value { get; private set; }
public Indexed(int index, T value) : this()
{
Index = index;
Value = value;
}
public override string ToString()
{
return "(Indexed: " + Index + ", " + Value.ToString () + " )";
}
}
public class Indexed
{
public static Indexed<T> Create<T>(int indexed, T value)
{
return new Indexed<T>(indexed, value);
}
}
The lambda comparer helper
public class Comparer<T> : IComparer<T>
{
private readonly Func<T, T, int> _comparer;
public Comparer(Func<T, T, int> comparer)
{
if (comparer == null)
throw new ArgumentNullException("comparer");
_comparer = comparer;
}
public int Compare(T x, T y)
{
return _comparer(x, y);
}
}
The problem is that you use something as key that isn't a key (cause it occurs multiple times).
So if you have real coordinates you should maybe take the Point as the key for your SortedList.
Or you create a List<List<Header>>
where your first list index defines the x-position and the inner list index the y-position (or vice versa if you like).
Did you try Lookup<TKey, TElement>
that will allow duplicate keys
http://msdn.microsoft.com/en-us/library/bb460184.aspx