Linq集团分组

时间:2013-09-26 20:35:06

标签: c# linq

我在面试过程中遇到了这个问题,有以下列表

Tom 1000
Mark 2200
Antony 3000
Paul 2500
Kris 2800
Ron 3110

使用group by编写linq查询以获取编号介于

之间的人员组
0-1500
1501-2500
2501-4000

怎么做?

4 个答案:

答案 0 :(得分:4)

int?[] ranges = new int?[] { 1500, 2500, 4000 };
var groups = from p in people
             group p by ranges.FirstOrDefault(r => r > p.Value) into g
             where g.Key != null
             select new {
                 People = g,
                 To = g.Key,
                 From = ranges.Where(r => r < g.Key)
                              .Select(r => r + 1).DefaultIfEmpty(0).Last()
             };

示例数据:

var people = new List<Person>
{
    new Person { Name = "Tom", Value = 1000 },
    new Person { Name = "Mark", Value = 2200 },
    new Person { Name = "Antony", Value = 3000 },
    new Person { Name = "Paul", Value = 2500 },
    new Person { Name = "Kris", Value = 2800 },   
    new Person { Name = "Kris", Value = 5800 },
};

输出:

0-1500
1000: Tom

1501-2500
2200: Mark

2501-4000
3000: Antony
2500: Paul
2800: Kris

当然,如果您希望代码看起来更好,可以创建一些Range类。例如。以下查询将返回适合范围的人的范围:

var ranges = new List<Range<int>> { 0.To(1500), 1501.To(2500), 2501.To(4000) };
var groups = from r in ranges
             select new {
                 Range = r,
                 People = people.Where(p => r.Contains(p.Value))
             };

通用范围类:

public class Range<T>
    where T : IComparable
{
    public Range(T from, T to)
    {
        if (from.CompareTo(to) > 0)
            throw new ArgumentException("From should not be greater than To");

        From = from;
        To = to;
    }

    public T From { get; private set; }
    public T To { get; private set; }

    public bool Contains(T value)
    {
        return value.CompareTo(From) >= 0 && value.CompareTo(To) <= 0;
    }
}

和扩展允许编写像0.To(100)这样的代码来创建任何类似类型的范围:

public static Range<T> To<T>(this T from, T to)
    where T: IComparable
{
    return new Range<T>(from, to);
}

答案 1 :(得分:4)

您可以在GroupBy上使用List.FindIndex

var ranges = new[]{
    new{ Start=0, End=1500 },  new{ Start=1501, End=2500}, new{ Start=2501, End=4000}
}.ToList();
var players = new[]{
    new{ Name = "Tom", Score = 1000 },
    new{ Name = "Mark", Score = 2200 },
    new{ Name = "Antony", Score = 3000 },
    new{ Name = "Paul", Score = 2500 },
    new{ Name = "Kris", Score = 2800 },
    new{ Name = "Ron", Score = 3110 },
};

var scoreGroups = players.
    GroupBy(p => ranges.FindIndex(r => p.Score >= r.Start && p.Score <= r.End));

foreach (var scoreGroup in scoreGroups)
    Console.WriteLine("Range: {0} <--> {1} Players: {2}"
        , ranges[scoreGroup.Key].Start
        , ranges[scoreGroup.Key].End
        , string.Join(", ", scoreGroup.Select(p => p.Name));

结果:

Range: 0    <--> 1500 Players: Tom 
Range: 1501 <--> 2500 Players: Mark, Paul
Range: 2501 <--> 4000 Players: Antony, Kris, Ron

答案 2 :(得分:1)

list.GroupBy(g => PickGroup(g));

private string PickGroup(int val)
{
   //some logic to determine which group the input falls into
   //Check the value against the boundaries of the groups  
}

尝试上述方法

根据您的范围定义组并返回表示范围(0-1500)等的字符串

答案 3 :(得分:0)

这是另一种方式:

    var people = new List<Tuple<int, string>> { new Tuple<int,string>(1000, "Tom"),
        new Tuple<int,string>(2200, "Mark"),
        new Tuple<int,string>(3000, "Antony"),
        new Tuple<int,string>(2500, "Paul"),
        new Tuple<int,string>(2800, "Kris"),
        new Tuple<int,string>(3110, "Ron"),
    };

    var ranges = new List<Tuple<int, int>> { new Tuple<int,int>(0,1500),
        new Tuple<int,int>(1501,2500),
        new Tuple<int,int>(2501,4000),

    };

    var result = people.GroupBy(person => ranges.FirstOrDefault(e => e.Item1 < person.Item1 && e.Item2 >= person.Item1),
                                (key, g) => new { Range = key, People = g.Select(k => k.Item2) })

输出

0-1500 TOM

1501-2500 MARK,PAUL

2501-4000 ANTHONY,KRIS,RON