【问题标题】:Sort groups based on values within groups根据组内的值对组进行排序
【发布时间】:2017-12-02 05:21:18
【问题描述】:

我正在尝试对包含逻辑人群和人们分数的数组进行排序。

Name   | Group | Score
----------------------
Alfred |     1 |     3
Boris  |     3 |     3
Cameron|     3 |     1
Donna  |     1 |     2
Emily  |     2 |     2

应根据组中的最低分数按组对人员进行排序。因此,第 3 组是第一个,因为它包含得分最低的人。然后是第 1 组中的人,因为它的人得分次低(并且组号低于第 2 组)。

所以结果将是:Cameron、Boris、Donna、Alfred、Emily

我已经做到了,但我想知道是否有更好的方法来做到这一点。我收到一个数组,并最终以正确的顺序对数组进行排序。

我使用 LINQ(主要从 Linq order by, group by and order by each group? 获得)创建一个目标排序数组,该数组映射一个人应该在哪里,与他们当前在数组中的位置相比。

然后我使用 Array.Sort 使用我的目标排序数组,但是 LINQ 语句创建的数组在索引和值方面是“反转的”,所以我必须反转索引和值(而不是顺序)。

我在下面附上了我的代码。有更好的方法吗?

using System;
using System.Collections.Generic;
using System.Linq;

namespace Sorter
{
    class Program
    {
        static void Main(string[] args)
        {
            // Sample person array.
            // Lower score is better.
            Person[] peopleArray = new Person[]
            {
                new Person { Name = "Alfred", Group = "1", Score = 3, ArrayIndex = 0 },
                new Person { Name = "Boris", Group = "3", Score = 3, ArrayIndex = 1 },
                new Person { Name = "Cameron", Group = "3", Score = 1, ArrayIndex = 2 },
                new Person { Name = "Donna", Group = "1", Score = 2, ArrayIndex = 3 },
                new Person { Name = "Emily", Group = "2", Score = 2, ArrayIndex = 4 }
            };

            // Create people list. 
            List<Person> peopleModel = peopleArray.ToList();

            // Sort the people based on the following:
            // Sort people into groups (1, 2, 3)
            // Sort the groups by the lowest score within the group.
            // So, the first group would be group 3, because it has the
            // member with the lowest score (Cameron with 1).
            // The people are therefore sorted in the following order:
            //  Cameron, Boris, Donna, Alfred, Emily
            int[] targetOrder = peopleModel.GroupBy(x => x.Group)
                                           .Select(group => new
                                           {
                                               Rank = group.OrderBy(g => g.Score)
                                           })
                                           .OrderBy(g => g.Rank.First().Score)
                                           .SelectMany(g => g.Rank)
                                           .Select(i => i.ArrayIndex)
                                           .ToArray();

            // This will give the following array:
            // [2, 1, 3, 0, 4]
            // I.e: Post-sort, 
            //  the person who should be in index 0, is currently at index 2 (Cameron).
            //  the person who should be in index 1, is currently at index 1 (Boris).
            //                 etc.

            // I want to use my target array to sort my people array.

            // However, the Array.sort method works in the reverse.
            // For example, in my target order array:  [2, 1, 3, 0, 4]
            //    person currently at index 2 should be sorted into index 0.
            // I need the following target order array: [3, 1, 0, 2, 4], 
            //    person currently at index 0, should be sorted into index 3
            // So, "reverse" the target order array.
            int[] reversedArray = ReverseArrayIndexValue(targetOrder);

            // Finally, sort the base array.
            Array.Sort(reversedArray, peopleArray);

            // Display names in order.
            foreach (var item in peopleArray)
            {
                Console.WriteLine(item.Name);
            }

            Console.Read();
        }

        /// <summary>
        /// "Reverses" the indices and values of an array.
        /// E.g.: [2, 0, 1] becomes [1, 2, 0].
        /// The value at index 0 is 2, so the value at index 2 is 0.
        /// The value at index 1 is 0, so the value at index 0 is 1.
        /// The value at index 2 is 1, so the value at index 1 is 2.
        /// </summary>
        /// <param name="target"></param>
        /// <returns></returns>
        private static int[] ReverseArrayIndexValue(int[] target)
        {
            int[] swappedArray = new int[target.Length];

            for (int i = 0; i < target.Length; i++)
            {
                swappedArray[i] = Array.FindIndex(target, t => t == i);
            }

            return swappedArray;
        }
    }
}

【问题讨论】:

  • 您想要的最终结果是什么?您只想要一个按您的标准排序的List&lt;Person&gt; 吗?我有点不清楚你抛出的所有数组什么只是你尝试工作的一部分以及需要什么输出......
  • 第 1 组和第 2 组的得分均次之。将使用什么标准来确定第 1 组是下一个?
  • @Chris 我想要的最终结果是我在本例中以 (peopleArray) 开头的数组,按排序顺序。
  • @Eric 我想在这种情况下,较低的 ArrayIndex (或起始数组中的顺序)。除了小组和得分之外,这并不是很重要。
  • 用您自己的话来说,“更好的方法”是什么?

标签: c# linq


【解决方案1】:

如果您想要的结果是更少的代码行。这个怎么样?

var peoples = peopleModel.OrderBy(i => i.Score).GroupBy(g => 
              g.Group).SelectMany(i => i, (i, j) => new { j.Name });

1) 按分数排序

2) 通过分组进行分组

3) 展平分组列表并使用 SelectMany 创建具有“名称”属性的新列表

对于使用匿名类型的信息 https://dzone.com/articles/selectmany-probably-the-most-p

【讨论】:

    【解决方案2】:
    int[] order = Enumerable.Range(0, peopleArray.Length)
                            .OrderBy(i => peopleArray[i].Score)
                            .GroupBy(i => peopleArray[i].Group)
                            .SelectMany(g => g).ToArray();          // { 2, 1, 3, 0, 4 }
    
    Array.Sort(order, peopleArray); 
    
    Debug.Print(string.Join(", ", peopleArray.Select(p => p.ArrayIndex)));  // "3, 1, 0, 2, 4"
    

    【讨论】:

      【解决方案3】:

      据我了解,您希望对输入数组进行就地排序。

      首先,排序部分可以通过先OrderBy Score 然后GroupBy Group 来简化(并提高效率),利用Enumerable.GroupBy 的定义行为:

      IGrouping 对象的生成顺序基于源中生成每个 IGrouping 的第一个键的元素的顺序。分组中的元素按照它们在源代码中出现的顺序产生。

      一旦你有了它,你所需要的就是展平结果,迭代它(从而执行它)并将产生的项目放在新的位置:

      var sorted = peopleArray
          .OrderBy(e => e.Score)
          .ThenBy(e => e.Group) // to meet your second requirement for equal Scores
          .GroupBy(e => e.Group)
          .SelectMany(g => g);
      int index = 0;
      foreach (var item in sorted)
          peopleArray[index++] = item;
      

      【讨论】:

      • 这非常有效。谢谢你。天哪.. 它的代码比我原来的实现少得多。
      【解决方案4】:

      不确定我是否真的理解期望的结果应该是什么,但这至少给出了与 cmets 示例中提到的相同的顺序:

         var sortedNames = peopleArray
                  // group by group property
                  .GroupBy(x => x.Group)
                  // order groups by min score within the group
                  .OrderBy(x => x.Min(y => y.Score))
                  // order by score within the group, then flatten the list
                  .SelectMany(x => x.OrderBy(y => y.Score))
                  // doing this only to show that it is in right order
                  .Select(x =>
                  {
                      Console.WriteLine(x.Name);
                      return false;
                  }).ToList();
      

      【讨论】:

      • 干得好。我唯一的意见是,在您的测试选择中,您应该使用return x 而不是return false。否则你会在最后得到一个布尔值列表。虽然我知道在最后一个 Select 不会出现在实时代码中,但它仍然更好地让演示尽可能完整地发挥作用。
      • 这里的 LINQ 排序工作得很好,但是有没有办法将排序应用于原始数组?我不想最终得到它的排序副本,我需要对原始副本进行排序。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-04-08
      • 2013-05-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多