C#可排序集合,允许重复的键

问题描述

| 我正在编写一个程序来设置顺序,在该顺序中各种对象将出现在报表中。 序列是Excel电子表格上的Y位置(单元格)。 下面是代码的演示部分。 我想要完成的是拥有一个集合,这将允许我添加多个对象,并且我可以基于序列获得排序的集合
SortedList list = new SortedList();

Header h = new Header();
h.XPos = 1;
h.name = \"Header_1\";
list.Add(h.XPos,h);

h = new Header();
h.XPos = 1;
h.name = \"Header_2\";
list.Add(h.XPos,h);
我知道SortedList不允许这样做,我一直在寻找替代项。我不想消除重复项,而已经尝试过
List<keyvaluePair<int,object>>
。 谢谢。

解决方法

使用您自己的IComparer! 就像其他答案中已经提到的那样,您应该使用自己的比较器类。为此,我使用一个通用的IComparer类,该类可与实现IComparable的任何对象一起使用:
/// <summary>
/// Comparer for comparing two keys,handling equality as beeing greater
/// Use this Comparer e.g. with SortedLists or SortedDictionaries,that don\'t allow duplicate keys
/// </summary>
/// <typeparam name=\"TKey\"></typeparam>
public class DuplicateKeyComparer<TKey>
                :
             IComparer<TKey> where TKey : IComparable
{
    #region IComparer<TKey> Members

    public int Compare(TKey x,TKey y)
    {
        int result = x.CompareTo(y);

        if (result == 0)
            return 1;   // Handle equality as beeing greater
        else
            return result;
    }

    #endregion
}
实例化新的SortedList,SortedDictionary等时,将使用它:
SortedList<int,MyValueClass> slist = new SortedList<int,MyValueClass>(new DuplicateKeyComparer<int>());
int是可以重复的键。,您可以安全地使用List <>。 List具有Sort方法,该方法的重载接受IComparer。您可以创建自己的分类器类。这是一个例子:
private List<Curve> Curves;
this.Curves.Sort(new CurveSorter());

public class CurveSorter : IComparer<Curve>
{
    public int Compare(Curve c1,Curve c2)
    {
        return c2.CreationTime.CompareTo(c1.CreationTime);
    }
}
,我使用以下内容:
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);
    }

}
我的测试用例:
[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);
    }
输出:
1:cab
1:coconut
1:car
1:cat
2:door
2:dog
3:elephant
,最简单的解决方案(与上述所有方法相比):使用
SortedSet<T>
,它接受
IComparer<SortableKey>
类,然后以这种方式实现Compare方法:
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();

}
,问题是数据结构设计不符合要求:必须为同一XPos存储多个Header。因此,
SortedList<XPos,value>
的值不应为
Header
,而应为
List<Header>
。这是一个简单而小的更改,但是它可以解决所有问题,并且避免像其他建议的解决方案一样产生新问题(请参见下面的说明):
using System;
using System.Collections.Generic;

namespace TrySortedList {
  class Program {

    class Header {
      public int XPos;
      public string Name;
    }

    static void Main(string[] args) {
      SortedList<int,List<Header>> sortedHeaders = new SortedList<int,List<Header>>();
      add(sortedHeaders,1,\"Header_1\");
      add(sortedHeaders,\"Header_2\");
      add(sortedHeaders,2,\"Header_3\");
      foreach (var headersKvp in sortedHeaders) {
        foreach (Header header in headersKvp.Value) {
          Console.WriteLine(header.XPos + \": \" + header.Name);
        }
      }
    }

    private static void add(SortedList<int,List<Header>> sortedHeaders,int xPos,string name) {
      List<Header> headers;
      if (!sortedHeaders.TryGetValue(xPos,out headers)){
        headers = new List<Header>();
        sortedHeaders[xPos] = headers;
      }
      headers.Add(new Header { XPos = xPos,Name = name });
    }
  }
}

Output:
1: Header_1
1: Header_2
2: Header_3
请注意,添加一个“有趣的”键,例如添加一个随机数或假装2个具有相同值的XPos是不同的,这会导致许多其他问题。例如,很难或什至不可能删除特定的标题。 还要注意,如果只需要排序几个
List<Header>
,而不是每个
Header
,则排序性能会好得多。示例:如果有100个XPos,并且每个都有100个标头,则需要排序10000
Header
,而不是100
List<Header>
。 当然,该解决方案也有一个缺点:如果有很多XPos只有1个标头,则需要创建许多列表,这会产生一些开销。,您是否尝试过允许重复密钥的
Lookup<TKey,TElement>
http://msdn.microsoft.com/en-us/library/bb460184.aspx,非常感谢你的帮助。在搜索更多内容时,我找到了此解决方案。 (在其他问题中可在Stackoverflow.com中获得) 首先,我创建了一个类,该类将封装我的对象用于类(Headers,Footer等)
public class MyPosition
{
    public int Position { get; set; }
    public object MyObjects{ get; set; }
}
因此,该类应该保留在这些对象上,并且每个对象的PosX都作为int Position
List<MyPosition> Sequence= new List<MyPosition>();
Sequence.Add(new MyPosition() { Position = 1,Headerobject });
Sequence.Add(new MyPosition() { Position = 2,Headerobject1 });
Sequence.Add(new MyPosition() { Position = 1,Footer });

League.Sort((PosA,PosB) => PosA.Position.CompareTo(PosB.Position));
最终我得到的是排序后的“序列”列表。,该集合类将维护重复项,并为重复项插入排序顺序。诀窍是使用唯一值标记项目 插入它们以保持稳定的排序顺序。然后,我们将其全部包装到 ICollection接口。
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; }
    }
}
考试班
[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,5,9,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,23));
    xs = set.Select(p=>p.X).ToList();
    xs.Count.Should()
       .Be(8);
    xs.ShouldBeEquivalentTo(new[]{-1,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();

}
标记结构
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);
    }
}
Lambda比较器助手
public class Comparer<T> : IComparer<T>
{
    private readonly Func<T,T,int> _comparer;

    public Comparer(Func<T,int> comparer)
    {
        if (comparer == null)
            throw new ArgumentNullException(\"comparer\");
        _comparer = comparer;
    }

    public int Compare(T x,T y)
    {
        return _comparer(x,y);
    }
}
,这样做的关键(双关语)是创建一个基于
IComparable
的类,该类保持相等性和哈希值,但是如果不相等,则永远不会与0进行比较。这是可以做到的,并且可以带来一些好处-稳定的排序(也就是说,首先添加到排序列表中的值将保持其位置),而
ToString()
可以简单地返回实际的字符串值。 这是应该完成此操作的struct键:
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
    }
}
,问题是您将某些东西用作不是钥匙的钥匙(因为它多次出现)。 因此,如果您有真实的坐标,则应将ѭ29用作您SortedList的键。 或者,您创建一个“ 30”,其中第一个列表索引定义x位置,内部列表索引定义y位置(或者,如果您愿意,反之亦然)。,创建一个类并查询列表:
Public Class SortingAlgorithm
{
    public int ID {get; set;}
    public string name {get; set;}
    public string address1 {get; set;}
    public string city {get; set;}
    public string state {get; set;}
    public int age {get; set;}
}

//declare a sorting algorithm list
List<SortingAlgorithm> sortAlg = new List<SortingAlgorithm>();

//Add multiple values to the list
sortAlg.Add( new SortingAlgorithm() {ID = ID,name = name,address1 = address1,city = city,state = state,age = age});
sortAlg.Add( new SortingAlgorithm() {ID = ID,age = age});

//query and order by the list
  var sortedlist = (from s in sortAlg
                    select new { s.ID,s.name,s.address1,s.city,s.state,s.age })
                                                     .OrderBy(r => r.ID)
                                                     .ThenBy(r=> r.name)
                                                     .ThenBy(r=> r.city)
                                                     .ThenBy(r=>r.state)
                                                     .ThenBy(r=>r.age);
,Linq.Lookup很酷,但是如果您的目标是在允许重复的同时简单地遍历\“ keys \”,则可以使用以下结构:
List<KeyValuePair<String,String>> FieldPatterns = new List<KeyValuePair<string,string>>() {
   new KeyValuePair<String,String>(\"Address\",\"CommonString\"),new KeyValuePair<String,String>(\"Username\",\"UsernamePattern\"),};
然后您可以编写:
foreach (KeyValuePair<String,String> item in FieldPatterns)
{
   //use item.Key and item.Value
}
高温超导,您可以使用SortedList,将值用作TKey,将int(计数)用作TValue。 这是一个示例:对单词字母排序的函数。
    private string sortLetters(string word)
    {
        var input = new System.Collections.Generic.SortedList<char,int>();

        foreach (var c in word.ToCharArray())
        {
            if (input.ContainsKey(c))
                input[c]++;
            else
                input.Add(c,1);
        }

        var output = new StringBuilder();

        foreach (var kvp in input)
        {
            output.Append(kvp.Key,kvp.Value);
        }

        string s;

        return output.ToString();

    }
,诀窍是用唯一的键扩展对象。请参阅以下通过的测试。我想要 保持我的分数按其X值排序。只需在我的比较函数中使用裸Point2D 导致具有相同X值的点被消除。所以我将Point2D包装在一个称为标签的类中 索引。
[Fact]
public void ShouldBeAbleToUseCustomComparatorWithSortedSet()
{
    // Create comparer that compares on X value but when X
    // X values are uses the index
    var comparer = new 
        System.Linq.Comparer<Indexed<Point2D>>(( p0,p1 ) =>
        {
            var r = p0.Value.X.CompareTo(p1.Value.X);
            return r == 0 ? p0.Index.CompareTo(p1.Index) : r;
        });

    // Sort points according to X
    var set = new SortedSet<Indexed<Point2D>>(comparer);

    int i=0;

    // Create a helper function to wrap each point in a unique index
    Action<Point2D> index = p =>
    {
        var ip = Indexed.Create(i++,p);
        set.Add(ip);
    };

    index(new Point2D(9,10));
    index(new Point2D(1,25));
    index(new Point2D(11,-10));
    index(new Point2D(2,99));
    index(new Point2D(5,55));
    index(new Point2D(5,23));
    index(new Point2D(11,11));
    index(new Point2D(21,12));
    index(new Point2D(-1,76));
    index(new Point2D(16,21));
    set.Count.Should()
       .Be(10);
    var xs = set.Select(p=>p.Value.X).ToList();
    xs.Should()
      .BeInAscendingOrder();
    xs.ShouldBeEquivalentTo(new[]{-1,21});

}
进行这项工作的实用工具是 比较器需要一个lambda
public class Comparer<T> : IComparer<T>
{
    private readonly Func<T,y);
    }
}
标记结构
public struct Indexed<T>
{
    public int Index { get; private set; }
    public T Value { get; private set; }
    public Indexed(int index,value);
    }
}