【问题标题】:How to Order By or Sort an integer List and select the Nth element如何排序或排序整数列表并选择第 N 个元素
【发布时间】:2018-06-06 07:51:30
【问题描述】:

我有一个列表,我想从中选择第五高的元素:

List<int> list = new List<int>();

list.Add(2);
list.Add(18);
list.Add(21);
list.Add(10);
list.Add(20);
list.Add(80);
list.Add(23);
list.Add(81);
list.Add(27);
list.Add(85);

OrderbyDescending 不适用于此int 列表...

【问题讨论】:

  • 请参阅stackoverflow.com/q/21726334/613130,了解正在发生的事情。
  • 请注意,通过使用 OrderBy,您正在将 O(N) 操作转换为 O(NlogN) 操作。
  • @Fabjan OrderBy 不会“订购”list。它返回一个有序的IEnumerable&lt;&gt;。他可能在做list.OrderByDescending(x =&gt; x);,没有任务。
  • @xanatos 如果 OP 添加了一个 minimal complete and verifiable example 会很好,他会在其中显示他 尝试 做什么以及究竟是什么不起作用
  • 如果有可能,您应该指定当元素少于 4 个时要执行的操作。

标签: c# list linq sorting


【解决方案1】:
int fifth = list.OrderByDescending(x => x).Skip(4).First();

【讨论】:

  • 这种方法在列表太小时会抛出异常,这可能会导致性能问题。 @bommelding:您需要添加一个 if(list.Count &gt;= 5) 保护来包装此代码以缓解问题。
  • 同意,但我会保持原样,除非 OP 更好地指定它。伯爵可能会花费,以及以更少的物品返回什么?合同可以保证总有五分之一。
  • if 守卫的成本与使用 OrderByDescending 创建可枚举和排序所有内容的成本相比是 NONE。
  • 我说 could ,例如当源不是内存中的 List 而是 Queryable 时。
【解决方案2】:

根据不超过 5 个元素的列表的严重程度,您有 2 个选项。

如果列表永远不会超过 5,我会将其视为异常:

int fifth;
try
{
    fifth = list.OrderByDescending(x => x).ElementAt(4);
}
catch (ArgumentOutOfRangeException)
{
    //Handle the exception
}

如果您希望它少于 5 个元素,那么您可以将其保留为默认值并检查它。

int fifth = list.OrderByDescending(x => x).ElementAtOrDefault(4);

if (fifth == 0)
{
    //handle default
}

这仍然存在一些缺陷,因为您最终可能会将第五个元素设为 0。这可以通过在 linq 之前将列表类型转换为可空整数列表来解决:

var newList = list.Select(i => (int?)i).ToList();
int? fifth = newList.OrderByDescending(x => x).ElementAtOrDefault(4);

if (fifth == null)
{
    //handle default
}

【讨论】:

  • 我没有在第三个块中看到可为空的 int 技巧。虽然现在这似乎不是一个高性能的解决方案,因为它首先创建列表的副本,然后在 LINQ 表达式期间生成一个可枚举
  • 好吧,你可以把它放在一条线上。但是,转换会更加微妙。此答案中不包括性能调整,因为它不是问题的一部分。
  • 查看我发布的两个答案,其中涵盖了列表本身为 null 或其中包含少于 5 个元素的情况。
【解决方案3】:

没有 LINQ 表达式:

int result;
if(list != null && list.Count >= 5)
{
    list.Sort();
    result = list[list.Count - 5];
}
else // define behavior when list is null OR has less than 5 elements

与 LINQ 表达式相比,这具有更好的性能,尽管我在第二个答案中提出的 LINQ solutions 舒适可靠。

如果您需要为庞大的整数列表提供极致性能,我建议您使用更专业的算法,例如 Matthew Watson's answer

注意:当调用Sort() 方法时,列表会被修改。如果您不想这样做,则必须使用列表的副本,如下所示:

List<int> copy = new List<int>(original);

List<int> copy = original.ToList();

【讨论】:

    【解决方案4】:

    最简单的方法是对数据进行排序并从前面取出 N 项。这是小型数据集的推荐方法 - 任何更复杂的东西都是不值得的。

    但是,对于大型数据集,执行所谓的Partial Sort 会快得多。

    有两种主要方法可以做到这一点:使用堆,或使用专门的快速排序。

    我链接的文章描述了如何使用堆。我将在下面展示部分排序:

    public static IList<T> PartialSort<T>(IList<T> data, int k) where T : IComparable<T>
    {
        int start = 0;
        int end = data.Count - 1;
    
        while (end > start)
        {
            var index = partition(data, start, end);
            var rank = index + 1;
    
            if (rank >= k)
            {
                end = index - 1;
            }
            else if ((index - start) > (end - index))
            {
                quickSort(data, index + 1, end);
                end = index - 1;
            }
            else
            {
                quickSort(data, start, index - 1);
                start = index + 1;
            }
        }
    
        return data;
    }
    
    static int partition<T>(IList<T> lst, int start, int end) where T : IComparable<T>
    {
        T x = lst[start];
        int i = start;
    
        for (int j = start + 1; j <= end; j++)
        {
            if (lst[j].CompareTo(x) < 0) // Or "> 0" to reverse sort order.
            {
                i = i + 1;
                swap(lst, i, j);
            }
        }
    
        swap(lst, start, i);
        return i;
    }
    
    static void swap<T>(IList<T> lst, int p, int q)
    {
        T temp = lst[p];
        lst[p] = lst[q];
        lst[q] = temp;
    }
    
    static void quickSort<T>(IList<T> lst, int start, int end) where T : IComparable<T>
    {
        if (start >= end)
            return;
    
        int index = partition(lst, start, end);
        quickSort(lst, start, index - 1);
        quickSort(lst, index + 1, end);
    }
    

    然后要访问列表中的第 5 大元素,您可以这样做:

    PartialSort(list, 5);
    Console.WriteLine(list[4]);
    

    对于大型数据集,部分排序比完全排序要快得多。


    附录

    请参阅 here 了解使用 QuickSelect algorithm 的另一个(可能更好)解决方案。

    【讨论】:

    • 这是一个非常适合大型数据集的解决方案,非常感谢您的贡献 +1。只是一个澄清的问题:该算法是否对列表进行排序,直到找到第 K 个排序元素,然后退出?
    • @sɐunıɔןɐqɐp 是的,它确实有效(所以它通常会显着减少工作量)。预期的复杂性是O(n + k log k) - 但是,请注意,最坏​​情况的时间可能非常糟糕(这在我链接的维基百科文章中有所说明)。
    【解决方案5】:

    LINQ 方法检索第 5 个最大元素 OR 抛出异常 WHEN 列表为空或包含少于 5 个元素:

    int fifth = list?.Count >= 5 ?
        list.OrderByDescending(x => x).Take(5).Last() :
        throw new Exception("list is null OR has not enough elements");
    


    这个检索第 5 大元素 OR null WHEN 列表为 null 或包含少于 5 个元素:

    int? fifth = list?.Count >= 5 ?
        list.OrderByDescending(x => x).Take(5).Last() :
        default(int?);
    
    if(fifth == null) // define behavior
    


    这个检索第 5 个最大的元素 OR 最小的元素 WHEN 列表包含少于 5 个元素:

    if(list == null || list.Count <= 0)
        throw new Exception("Unable to retrieve Nth biggest element");
    
    int fifth = list.OrderByDescending(x => x).Take(5).Last();
    


    所有这些解决方案都是可靠的,它们应该绝不抛出“意外”异常。

    PS:我在这个答案中使用的是 .NET 4.7

    【讨论】:

      【解决方案6】:

      Here 有一个 QuickSelect 算法的 C# 实现来选择无序 IList&lt;&gt; 中的第 n 个元素。

      您必须将该页面中包含的所有代码放在static class 中,例如:

      public static class QuickHelpers
      {
          // Put the code here
      }
      

      鉴于那个“库”(实际上是一大块代码),那么您可以:

      int resA = list.QuickSelect(2, (x, y) => Comparer<int>.Default.Compare(y, x));
      int resB = list.QuickSelect(list.Count - 1 - 2);
      

      现在... 通常QuickSelect 会选择第n 个最低的元素。我们通过两种方式对其进行反转:

      • 对于resA,我们基于默认的int 比较器创建一个反向比较器。我们通过反转Compare 方法的参数来做到这一点。请注意,索引是基于 0 的。所以有第 0 个、第 1 个、第 2 个等等。

      • 对于resB,我们使用第 0 个元素是倒序的第 list-1 个元素这一事实。所以我们从后面数。最高元素是有序列表中的list.Count - 1,下一个是list.Count - 1 - 1,然后是list.Count - 1 - 2,依此类推

      理论上使用快速排序应该比排序列表然后选择第 n 个元素更好,因为排序列表平均是 O(NlogN) 操作,然后选择第 n 个元素是 O(1) 操作,所以复合是 O(NlogN) 操作,而 QuickSelect 平均是 O(N) 操作。显然有一个但是。 O 表示法没有显示k 因子...因此具有小 k1 的 O(k1 * NlogN) 可能比具有大 k2 的 O(k2 * N) 更好。只有多个现实生活中的基准可以告诉我们(您)什么更好,这取决于集合的大小。

      关于算法的一个小说明:

      与快速排序一样,快速选择通常作为就地算法实现,除了选择第 k 个元素之外,它还对数据进行部分排序。有关与排序的联系的进一步讨论,请参阅选择算法。

      所以它修改了原来list的顺序。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-09-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-15
        • 2023-04-07
        相关资源
        最近更新 更多