【问题标题】:C# Sortable collection which allows duplicate keys允许重复键的 C# 可排序集合
【发布时间】:2011-04-19 12:35:19
【问题描述】:

我正在编写一个程序来设置各种对象在报告中出现的顺序。 该序列是 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>>

谢谢。

【问题讨论】:

  • 在获得初始成员列表之后,集合是否需要支持插入/删除
  • 您尝试List时有什么问题?
  • 我不想只是排序和获取对象。但我想得到整个排序列表。因此,在下面的示例中,两个 Header 对象都应该存在,并且顺序一个在另一个之下。如果我添加另一个带有 XPos=2 的 Header 对象,那么我应该在列表中有 3 个对象,2 个 XPos=1 的对象和第三个 XPos=2 的对象
  • 请注意:当我遇到这种情况时,我发现通用 List 与 little-known BinarySearch behavior for items not found 结合使用会产生奇迹。

标签: c# .net collections sortedlist


【解决方案1】:

使用您自己的 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 being greater. Note: this will break Remove(key) or
        else          // IndexOfKey(key) since the comparer never returns 0 to signal key equality
            return result;
    }

    #endregion
}

您将在实例化新的 SortedList、SortedDictionary 等时使用它:

SortedList<int, MyValueClass> slist = new SortedList<int, MyValueClass>(new DuplicateKeyComparer<int>());

这里的int是可以复制的key。

【讨论】:

  • 但是您将无法从中删除任何密钥。
  • 是的,没错,Shachwat!您不能使用 Remove(key) 或 IndexOfKey(key),因为比较器永远不会返回 0 来表示键相等。但是,如果您有索引,则可以 RemoveAt(index) 删除项目。
  • 我也遇到了同样的问题,我用的是SortedDictionary。它也允许删除。
  • 请注意,您正在以这种方式破坏比较器的reflexivity。它可以(并且将会)破坏 BCL 中的内容。
  • 这实际上应该返回 -1 以保持秩序
【解决方案2】:

您可以安全地使用 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);
    }
}

【讨论】:

  • 我不想只是排序和获取对象。但我想得到整个排序列表。因此,在下面的示例中,两个 Header 对象都应该存在,并且按顺序依次位于另一个之下。如果我添加另一个带有 XPos=2 的 Header 对象,那么我应该在列表中有 3 个对象,2 个 XPos=1 的对象和第三个 XPos=2 的对象
  • 好吧,你的意思是说,当一个元素被插入到列表中时,它应该按照排序插入到正确的位置。如有错误请指正。让我看看,一会儿就回来
  • 请注意,List.Sort 根据集合大小使用多种排序算法,并且并非所有这些算法都是稳定的排序。因此,添加到集合中的与等效对象进行比较的对象可能不会按照它们添加的顺序出现。
  • 我选择了这个选项,这样我就可以通过将 reduce 函数应用于字典来停止创建过多的 KeyValuePairs
【解决方案3】:

我使用以下:

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

【讨论】:

  • Tuple 并非在所有 .NET 版本中都可用,但可以替换为 KeyValuePair
【解决方案4】:

问题是数据结构设计不符合要求:需要为同一个 XPos 存储多个 Headers。因此,SortedList&lt;XPos, value&gt; 的值不应为Header,而应为List&lt;Header&gt;。这是一个简单而小的更改,但它解决了所有问题并避免像其他建议的解决方案一样产生新问题(请参阅下面的说明):

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, 1, "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 不同会导致许多其他问题。例如,删除特定的 Header 变得困难甚至不可能。

另外请注意,如果只需要对少数 List&lt;Header&gt; 进行排序,则排序性能要好于每个 Header。示例:如果有 100 个 XPos,每个都有 100 个标头,则需要对 10000 个Header 进行排序,而不是 100 个List&lt;Header&gt;

当然,这个方案也有一个缺点:如果有很多 XPos 只有 1 个 Header,那么需要创建很多 List,这是一些开销。

22.12.2021 更新

我终于有时间编写一个名为SortedBucketCollection 的合适集合,它的行为类似于SortedList。它为每个项目使用 2 个键,第一个键与 SortedList 键相同,并且该键的许多项目可以具有相同的值。第二个键用于区分与 key1 共享相同值的项目。 SortedBucketCollection 使用的存储空间比SortedList&lt;int, List&lt;Header&gt;&gt; 少,因为它为每个“桶”使用一个链表而不是List&lt;&gt;

使用SortedBucketCollection 的代码如下所示:

使用系统;

namespace SortedBucketCollectionDemo {

  public record FinanceTransaction
  (int No, DateTime Date, string Description, decimal Amount);

  class Program {
    static void Main(string[] args) {
      //Constructing a SortedBucketCollection
      var transactions = 
        new SortedBucketCollection<DateTime, int, FinanceTransaction>
                                  (ft=>ft.Date, ft=>ft.No);
      var date1 = DateTime.Now.Date;

      //Adding an item to SortedBucketCollection
      transactions.Add(new FinanceTransaction(3, date1, "1.1", 1m));
      transactions.Add(new FinanceTransaction(1, date1, "1.2", 2m));
      transactions.Add(new FinanceTransaction(0, date1, "1.3", 3m));
      var date2 = date1.AddDays(-1);
      transactions.Add(new FinanceTransaction(1, date2, "2.1", 4m));
      transactions.Add(new FinanceTransaction(2, date2, "2.2", 5m));

      //Looping over all items in a SortedBucketCollection
      Console.WriteLine("foreach over all transactions");
      foreach (var transaction in transactions) {
        Console.WriteLine(transaction.ToString());
      }

      //Accessing one particular transaction
      var transaction12 = transactions[date1, 1];

      //Removing  a transaction
      transactions.Remove(transaction12!);

      //Accessing all items of one day
      Console.WriteLine();
      Console.WriteLine("foreach over transactions of one day");
      Console.WriteLine(date1);
      foreach (var transaction in transactions[date1]) {
        Console.WriteLine(transaction.ToString());
      }
    }
  }
}

第一个 foreach 的输出:

FinanceTransaction { No = 1, Date = 07.11.2021 00:00:00, Description = 2.1, Amount = 4 }
FinanceTransaction { No = 2, Date = 07.11.2021 00:00:00, Description = 2.2, Amount = 5 }
FinanceTransaction { No = 0, Date = 08.11.2021 00:00:00, Description = 1.3, Amount = 3 }
FinanceTransaction { No = 1, Date = 08.11.2021 00:00:00, Description = 1.2, Amount = 2 }
FinanceTransaction { No = 3, Date = 08.11.2021 00:00:00, Description = 1.1, Amount = 1 }

请注意,项目不是按添加顺序进行迭代,而是按其key1key2 排序。

SortedBucketCollection的详细说明及源代码见我在CodeProject上的文章SortedBucketCollection: A memory efficient SortedList accepting multiple items with the same key

【讨论】:

  • 这是最直接的解决方案。还要检查 SortedDictionary,它是类似的,在某些情况下更快。
  • 这是一个非常好的解决方案。可以很容易地将该功能包装到一些自定义集合对象中,这将非常有用。好主意,谢谢分享彼得!
  • @AdamP:谢谢你的建议,我就是这么做的,写了一个名为 SortedBucketCollection 的集合,它的作用类似于 SortedList>,但使用的内存更少。请参阅我的答案中的更新。
【解决方案5】:

最简单的解决方案(与以上所有方法相比):使用SortedSet&lt;T&gt;,它接受IComparer&lt;SortableKey&gt; 类,然后以这种方式实现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();

}

【讨论】:

  • 我使用了 SortedSet 并且 T 有它自己的递增 int ID,该 ID 在每次实例化时递增,以确保每个 T 都是唯一的,即使其他字段相同。
  • GetHashCode 比较危险。可能会导致意外的错误重复。它可能大部分时间都有效,但我永远不会将它用于任何严重的事情。
【解决方案6】:

非常感谢您的帮助。在搜索更多时,我找到了这个解决方案。 (可在 Stackoverflow.com 中找到其他问题)

首先,我创建了一个类,它将封装我的类对象(页眉、页脚等)

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

最终我得到的是排序后的“序列”列表。

【讨论】:

    【解决方案7】:

    您是否尝试过Lookup&lt;TKey, TElement&gt; 允许重复键 http://msdn.microsoft.com/en-us/library/bb460184.aspx

    【讨论】:

    • 谢谢。我的问题是对象不仅是一种类型(只是不是标题),它们可能会有所不同(比如页脚、侧边栏等),但每个对象都会有 XPos
    • 另外,我相信Lookup 上没有公共构造函数。有什么好办法吗?
    • @JeffBridgman 你将不得不依赖 Linq。您可以在任何IEnumerable&lt;T&gt; 上执行ToLookup
    • 是的,它允许重复的键,但它不会保持任何排序!
    【解决方案8】:

    您可以使用 SortedList,将您的值用于 TKey,将 int (count) 用于 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();
    
        }
    

    【讨论】:

      【解决方案9】:

      此集合类将维护重复项并为重复项插入排序顺序。诀窍是用唯一值标记项目 因为它们被插入以保持稳定的排序顺序。然后我们把它包起来 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,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();
      
      }
      

      标签结构

      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, T, int> comparer)
          {
              if (comparer == null)
                  throw new ArgumentNullException("comparer");
              _comparer = comparer;
          }
      
          public int Compare(T x, T y)
          {
              return _comparer(x, y);
          }
      }
      

      【讨论】:

        【解决方案10】:

        问题是您使用不是键的东西作为键(因为它出现多次)。

        因此,如果您有真实坐标,您可能应该将Point 作为您的 SortedList 的键。

        或者您创建一个List&lt;List&lt;Header&gt;&gt;,其中您的第一个列表索引定义 x 位置,内部列表索引定义 y 位置(如果您愿意,反之亦然)。

        【讨论】:

        • 一个 Key 可以有多个实例,只要它不是主键即可。至少他们在我参加的数据库课程中是这样告诉我的。
        • 这个答案有点短,但它正确地解释了问题并提供了正确的解决方案,即使用 SortedList>。这保持了标题的排序,并且可以在同一个 xPos 中存储许多标题。有关代码示例,请查找我的答案。我加了一个这个答案,因为它指向正确的方向。如果您觉得有帮助,请加 1 我的回答。
        【解决方案11】:

        Linq.Lookup 很酷,但如果您的目标是简单地循环“键”同时允许它们被复制,您可以使用以下结构:

        List<KeyValuePair<String, String>> FieldPatterns = new List<KeyValuePair<string, string>>() {
           new KeyValuePair<String,String>("Address","CommonString"),
           new KeyValuePair<String,String>("Username","UsernamePattern"),
           new KeyValuePair<String,String>("Username","CommonString"),
        };
        

        然后你可以写:

        foreach (KeyValuePair<String,String> item in FieldPatterns)
        {
           //use item.Key and item.Value
        }
        

        HTH

        【讨论】:

          【解决方案12】:

          关键(双关语)是创建一个基于IComparable 的类,该类保持相等和散列,但如果不相等则永远不会与0 进行比较。这可以做到,并且可以通过几个好处来创建 - 稳定排序(即,首先添加到排序列表的值将保持其位置),ToString() 可以简单地返回实际的键字符串值。

          这是一个可以解决问题的结构键:

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

          【讨论】:

          【解决方案13】:

          这就是我解决问题的方法。它意味着是线程安全的,但如果不需要,您可以简单地删除 locks。另请注意,不支持索引处的任意Insert,因为这可能违反排序条件。

          public class ConcurrentOrderedList<Titem, Tsort> : ICollection<Titem>
          {
              private object _lock = new object();
              private SortedDictionary<Tsort, List<Titem>> _internalLists;
              Func<Titem, Tsort> _getSortValue;
              
              public ConcurrentOrderedList(Func<Titem,Tsort> getSortValue)
              {
                  _getSortValue = getSortValue;
                  _internalLists = new SortedDictionary<Tsort, List<Titem>>();            
              }
          
              public int Count { get; private set; }
          
              public bool IsReadOnly => false;
          
              public void Add(Titem item)
              {
                  lock (_lock)
                  {
                      List<Titem> values;
                      Tsort sortVal = _getSortValue(item);
                      if (!_internalLists.TryGetValue(sortVal, out values))
                      {
                          values = new List<Titem>();
                          _internalLists.Add(sortVal, values);
                      }
                      values.Add(item);
                      Count++;
                  }            
              }
          
              public bool Remove(Titem item)
              {
                  lock (_lock)
                  {
                      List<Titem> values;
                      Tsort sortVal = _getSortValue(item);
                      if (!_internalLists.TryGetValue(sortVal, out values))
                          return false;
          
                      var removed = values.Remove(item);
                      if (removed)
                          Count--;
                      return removed;
                  }
              }
          
              public void Clear()
              {
                  lock (_lock)
                  {
                      _internalLists.Clear();
                  }
              }
          
              public bool Contains(Titem item)
              {
                  lock (_lock)
                  {
                      List<Titem> values;
                      Tsort sortVal = _getSortValue(item);
                      if (!_internalLists.TryGetValue(sortVal, out values))
                          return false;
                      return values.Contains(item);
                  }
              }
          
              public void CopyTo(Titem[] array, int arrayIndex)
              {
                  int i = arrayIndex;
                  lock (_lock)
                  {
                      foreach (var list in _internalLists.Values)
                      {
                          list.CopyTo(array, i);
                          i += list.Count;
                      }
                  }
              }
          
              public IEnumerator<Titem> GetEnumerator()
              {
                  foreach (var list in _internalLists.Values)
                  {
                      foreach (var item in list)
                          yield return item;
                  }
              }
          
              public int IndexOf(Titem item)
              {
                  int i = 0;
                  var sortVal = _getSortValue(item);
                  lock (_lock)
                  {               
                      foreach (var list in _internalLists)
                      {
                          if (object.Equals(list.Key, sortVal))
                          {
                              int intIndex = list.Value.IndexOf(item);
                              if (intIndex == -1)
                                  return -1;
                              return i + intIndex;
                          }
                          i += list.Value.Count;
                      }
                      return -1;
                  }           
              }
          
              public void Insert(int index, Titem item)
              {
                  throw new NotSupportedException();
              }
          
              // Note this method is indeterminate if there are multiple
              // items in the same sort position!
              public void RemoveAt(int index)
              {
                  int i = 0;
                  lock (_lock)
                  {
                      foreach (var list in _internalLists.Values)
                      {
                          if (i + list.Count < index)
                          {
                              i += list.Count;
                              continue;
                          }
                          else
                          {
                              list.RemoveAt(index - i);
                              return;
                          }
                      }
                  }
              }
          
              IEnumerator IEnumerable.GetEnumerator()
              {
                  return this.GetEnumerator();
              }
          }
          

          【讨论】:

            【解决方案14】:

            诀窍是使用唯一键来扩充您的对象。请参阅以下通过的测试。我想 保持我的点按它们的 X 值排序。只需在我的比较函数中使用裸点 2D 即可 导致具有相同 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,1,2,5,5,9,11,11,16,21});
            
            }
            

            完成这项工作的实用程序是

            一个接受 lambda 的比较器

            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);
                }
            }
            

            标签结构

            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);
                }
            }
            

            【讨论】:

            • 查看我的其他答案,将上述概念完整包装到自定义 ICollection 类中
            【解决方案15】:

            创建一个类并查询列表:

            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, name = name, address1 = address1, city = city, state = state, age = age});
            sortAlg.Add( new SortingAlgorithm() {ID = ID, name = name, address1 = address1, city = city, state = state, 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);
            

            【讨论】:

              【解决方案16】:

              这是我对此的看法。请注意,这里可能是龙,C# 对我来说仍然很新。

              • 允许重复键,值存储在列表中
              • 我将它用作排序队列,因此使用了名称和方法

              用法:

              SortedQueue<MyClass> queue = new SortedQueue<MyClass>();
              // new list on key "0" is created and item added
              queue.Enqueue(0, first);
              // new list on key "1" is created and item added
              queue.Enqueue(1, second);
              // items is added into list on key "0"
              queue.Enqueue(0, third);
              // takes the first item from list with smallest key
              MyClass myClass = queue.Dequeue();
              
              class SortedQueue<T> {
                public int Count;
                public SortedList<int, List<T>> Queue;
              
                public SortedQueue() {
                  Count = 0;
                  Queue = new SortedList<int, List<T>>();
                }
              
                public void Enqueue(int key, T value) {
                  List<T> values;
                  if (!Queue.TryGetValue(key, out values)){
                    values = new List<T>();
                    Queue.Add(key, values);
                    Count += 1;
                  }
                  values.Add(value);
                }
              
                public T Dequeue() {
                  if (Queue.Count > 0) {
                    List<T> smallest = Queue.Values[0];
                    if (smallest.Count > 0) {
                      T item = smallest[0];
                      smallest.Remove(item);
                      return item;
                    } else {
                      Queue.RemoveAt(0);
                      Count -= 1;
                      return Dequeue();
                    }
                  }
                  return default(T);
                }
              }
              

              【讨论】:

              • BCL 中已经有一个类Queue,它代表了一个先进先出的项目集合。你的类的语义是不同的。您的课程有一个开始(项目被出队)但没有结束(一个项目可以插入任何地方)。因此,您班级中的Enqueue 方法恕我直言毫无意义。
              • @TheodorZoulias 是的,命名在这里有点糟糕,但我认为它不值得一票否决,它具有 OP 所需要的东西,只是重命名和重新实现输入/输出方法的问题。为什么会这样称呼它?我需要一个可以在 while 循环中从一开始就清空并根据优先级值添加新项目的结构。所以PriorityQueue 是更合适的名字。
              • OP 想要一个允许重复键的可排序集合。你的班级不是collection,因为它不能被枚举。我也不喜欢使用公共领域。不要个人反对。您可以通过一个赞成票 (-2 * 5 == +10) 修复 5 次反对票的声誉损失,所以这没什么大不了的。 :-)
              猜你喜欢
              • 1970-01-01
              • 2016-06-09
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2013-08-22
              • 1970-01-01
              • 2013-01-31
              • 1970-01-01
              相关资源
              最近更新 更多