【问题标题】:How to use LINQ to group objects in two groups如何使用 LINQ 将对象分成两组
【发布时间】:2013-12-12 22:53:31
【问题描述】:

假设我有类 Invoice:

public class Invoice
{
    public int PartNumber { get; set; }

    public string PartDescription { get; set; }

    public int Quantity { get; set; }

    public decimal Price { get; set; }
}

然后我在变量 arrayOfInvoices 中有一个对象数组。

如果我必须将发票分成两组 - 单价低于 12 的发票和单价高于或等于 12 的发票,并按价格升序显示每组发票的详细信息,我该怎么做?

【问题讨论】:

  • 你有什么尝试吗?

标签: c# arrays linq group-by


【解决方案1】:

你可以简单地做这样的事情:

var results = 
    from inv in arrayOfInvoices 
    orderby inv.Price
    group inv by inv.Price < 12;

或者如果你喜欢流畅的语法:

var results = arrayOfInvoices.OrderBy(inv => inv.Price)
                             .GroupBy(inv => inv.Price < 12);

要将发票分成三个或更多“桶”,您可以使用BinarySearch

var priceBoundaries = new[] { 12m, 20m };
var results = 
    from inv in arrayOfInvoices  
    orderby inv.Price
    let index = Array.BinarySearch(priceBoundaries, inv.Price)
    group inv by (index < 0 ? ~index : index + 1);

或者使用副作用,像这样:

var priceBoundaries = new[] { 12m, 20m, Decimal.MaxValue }; // Note the addition of MaxValue
var i = 0;
var results = 
    from inv in arrayOfInvoices 
    orderby inv.Price
    group inv by (inv.Price < priceBoundaries[i] ? i : ++i);

这通常是不好的做法,但应该比上面的 BinarySearch 方法执行得更好。

【讨论】:

  • @KevinDeLorey 是的,我刚刚也包含了流畅的语法。
  • 我想它会起作用,但是如果我想将它们分成三组 12 和 20 会发生什么?
【解决方案2】:

如果使用组函数很痛苦(有时会很烦人),那么你也可以使用“Where”

var invoices = new List<Invoice> ();
var group1= invoices.Where(i=> i.Price<12).Orderby(i=> i.Price).ToList();
var group2= invoices.Where(i=> i.Price>=12).Orderby(i=> i.Price).ToList();

【讨论】:

  • 这个答案本质上没有错,所以我会平衡否决票。
  • ... 除了循环遍历整个输入两次并根据需要执行两倍的比较。
【解决方案3】:

您可以将范围的概念封装在一个类中:

private class PriceRange<T> : IEquatable<PriceRange<T>>
{
    public T Min { get; set; }
    public T Max { get; set; }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = 17;
            hash = hash * 31 + Min.GetHashCode();
            hash = hash * 31 + Max.GetHashCode();
            return hash;
        }
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as PriceRange<T>);
    }

    public bool Equals(PriceRange<T> other)
    {
        if (other == null) return false;

        if (!Min.Equals(other.Min)) return false;
        if (!Max.Equals(other.Max)) return false;

        return true;
    }
}

然后使用映射函数将每张发票的价格映射到适当的范围:

private class PriceRangeFactory<T>
{
    public PriceRangeFactory(T[] rangeCutoffs)
    {
        _RangeCutoffs = rangeCutoffs;
    }

    private T[] _RangeCutoffs;

    public PriceRange<T> Map(T price)
    {
        var index = Array.BinarySearch(_RangeCutoffs, price);
        // Assume that the _RangeCutoffs that we have fully cover all possible values for T.
        if (index < 0) index = ~index;

        return new PriceRange<T>() { Min = _RangeCutoffs[index - 1], Max = _RangeCutoffs[index] };
    }
}

然后将 GroupBy 与该地图功能一起使用:

var rangeFactory = new PriceRangeFactory<decimal>(new decimal[] { decimal.MinValue, 12, 20, decimal.MaxValue });
var grouped = foos.GroupBy(a => rangeFactory.Map(a.Price));

您将获得一个 IGrouping 列表,每个 IGrouping 由您指定的范围作为键,并附有适合该范围的适当对象。

现在,显然上面的代码还没有达到生产级别,但应该足以让您入门。至于为什么不是生产级别:

  • 没有检查断言提供给 PriceRangeFactory 的范围确实完全涵盖了所有可能的值。
  • 它始终假定范围被描述为 > X 和
  • 没有检查 Range.Min
  • 没有断言提供给 PriceRangeFactory 的范围截止列表是正确排序的(BinarySearch 需要)。
  • 肯定有一些我没有涵盖的边缘情况。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多