根据组内的值对组进行排序

时间:2017-06-28 17:44:37

标签: c# linq

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

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

4 个答案:

答案 0 :(得分:2)

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

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

答案 1 :(得分:2)

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

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

  

IGrouping&lt; TKey,TElement&gt;根据源中元素的顺序产生对象,这些元素产生每个IGrouping&lt; TKey,TElement&gt;的第一个键。分组中的元素按它们在源中出现的顺序产生。

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

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;

答案 2 :(得分:0)

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

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

1)按分数列出的订单

2)通过分组

对其进行分组

3)展平分组列表并使用&#34; Name&#34;创建新列表。使用SelectMany的属性

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

答案 3 :(得分:0)

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"