C# Sortable collection which allows duplicate keys

前端 未结 16 2112
旧巷少年郎
旧巷少年郎 2020-11-28 10:06

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

相关标签:
16条回答
  • 2020-11-28 10:37

    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
        }
    }
    
    0 讨论(0)
  • 2020-11-28 10:38

    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();
    
    }
    
    0 讨论(0)
  • 2020-11-28 10:39

    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
    
    0 讨论(0)
  • 2020-11-28 10:40

    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);
        }
    }
    
    0 讨论(0)
  • 2020-11-28 10:40

    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).

    0 讨论(0)
  • 2020-11-28 10:44

    Did you try Lookup<TKey, TElement> that will allow duplicate keys http://msdn.microsoft.com/en-us/library/bb460184.aspx

    0 讨论(0)
提交回复
热议问题