如何使用Linq通过相等性将C#中的对象分组

时间:2018-11-04 21:39:21

标签: c# linq

场景:

我有一个对象'Order的列表,想按相同的List<OrderLine>属性分组,相同的意思是相同数量的行以及相同的Sku / Quantity值订单”并返回分组的订单值列表:

class Order
{
    public int OrderNumber { get; set; }
    public List<OrderLine> Lines { get; set; }
}

class OrderLine
{    
    public string Sku { get; set; }
    public int Quantity { get; set; }       
}

输入示例:

+-------------+-----+----------+
| OrderNumber | Sku | Quantity |
+-------------+-----+----------+
|           1 | A   |       10 |
|           1 | B   |       20 |
|           2 | A   |       10 |
|           3 | A   |       10 |
|           3 | B   |       20 |
+-------------+-----+----------+

所需的输出:

  

Lines = Lines.Count();每个分组相同的行数

     

Pieces = SUM(OrderLine.Quantity);的所有数量的总和   每个分组相同的订单。

+-----------------+-------+--------+
| TotalIdenticals | Lines | Pieces |
+-----------------+-------+--------+
|               1 |     1 |     10 |
|               2 |     2 |     30 |
+-----------------+-------+--------+
  

我使用表格表示使其更清晰。所以上面有   另一方面,只有1条记录(1行)(数量2)和数量10。   有两个具有相同行列表的订单(订单1和3)

所以我需要在运行linq算法之后,它将为我生成一种

> "identical 1".Orders -> [2]
> "identical 2".Order -> [1,3]

我想做什么?

var identicals = orderList.GroupBy(x => x.Lines)
                 .Where(g => g.Count() > 1)
                 .Select(g => g.Key)
                 .ToList();

上面的代码不起作用,基本上我只需要能够将Lines属性分组(这样就等于其他OrderLines了),那么我将能够生成行/片段的输出...唯一的问题现在可以按行列表对象相似性对我的对象顺序列表进行分组。

我希望我的问题很清楚,如果您需要更多详细信息,请告诉我,我会在这里添加。

2 个答案:

答案 0 :(得分:5)

第一步-为了对GroupBy()Order类使用OrderItem,必须为两个类implement Equals() and GetHashCode()或创建一个EqualityComparer

覆盖Equals()中的GetHashCode()Order(仅基于Lines属性):

public class Order
{
    public int OrderNumber { get; set; }
    public List<OrderLine> Lines { get; set; }

    protected bool Equals(Order other)
    {
        var equals = OrderLinesEquals(Lines, other.Lines);

        return equals;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != this.GetType()) return false;
        return Equals((Order) obj);
    }

    public override int GetHashCode()
    {
        if (Lines == null)
        {
            return 0;
        }

        unchecked
        {
            int hash = 19;

            foreach (OrderLine item in Lines.OrderBy(x => x.Sku, StringComparer.OrdinalIgnoreCase))
            {
                hash = hash * 31 + item.GetHashCode();
            }

            return hash;
        }
    }

    private bool OrderLinesEquals(List<OrderLine> x, List<OrderLine> y)
    {
        if (ReferenceEquals(x, y))
        {
            return true;
        }

        if (x == null || y == null)
        {
            return false;
        }

        bool areEquivalent = x.Count == y.Count && !x.Except(y).Any();

        return areEquivalent;
    }

    public override string ToString()
    {
        return $"Sku: {Sku ?? "[null]"}, Quantity: {Quantity}";
    }
}

Equals()中覆盖GetHashCode()OrderItem(基于SkuQuantity属性):

public class OrderLine
{
    public string Sku { get; set; }
    public int Quantity { get; set; }

    protected bool Equals(OrderLine other)
    {
        return string.Equals(Sku, other.Sku) && Quantity == other.Quantity;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != this.GetType()) return false;
        return Equals((OrderLine) obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            return ((Sku != null ? Sku.GetHashCode() : 0) * 397) ^ Quantity;
        }
    }
}

测试代码-订单清单:

var order1 = new Order
{
    OrderNumber = 1,
    Lines = new List<OrderLine>
    {
        new OrderLine
        {
            Quantity = 10,
            Sku = "A"
        },

        new OrderLine
        {
            Quantity = 20,
            Sku = "B"
        }
    }
};

var order2 = new Order
{
    OrderNumber = 2,
    Lines = new List<OrderLine>
    {
        new OrderLine
        {
            Quantity = 10,
            Sku = "A"
        }
    }
};

var order3 = new Order
{
    OrderNumber = 3,
    Lines = new List<OrderLine>
    {
        new OrderLine
        {
            Quantity = 20,
            Sku = "B"
        },
        new OrderLine
        {
            Quantity = 10,
            Sku = "A"
        }
    }
};


var order4 = new Order
{
    OrderNumber = 4,
    Lines = new List<OrderLine>
    {
        new OrderLine
        {
            Quantity = 20,
            Sku = "B"
        },
        new OrderLine
        {
            Quantity = 10,
            Sku = "A"
        }
    }
};


var order5 = new Order
{
    OrderNumber = 5,
    Lines = new List<OrderLine>
    {
        new OrderLine
        {
            Quantity = 30,
            Sku = "C"
        }
    }
};


var order6 = new Order
{
    OrderNumber = 6,
    Lines = new List<OrderLine>
    {
        new OrderLine
        {
            Quantity = 40,
            Sku = "C"
        }
    }
};


var order7 = new Order
{
    OrderNumber = 7,
    Lines = new List<OrderLine>
    {
        new OrderLine
        {
            Quantity = 30,
            Sku = "C"
        }
    }
};

var orderList = new List<Order>(new[] {order1, order2, order3, order4, order5, order6, order7});

将订单分组:

var identicalOrders = orderList.GroupBy(x => x)
                               .Where(g => g.Count() > 1)
                               .Select(g => new
                               {
                                   Count = g.Count(),
                                   OrderItems = g.Key.Lines,
                                   OrderNumbers = orderList.Where(x => x.Equals(g.Key))
                                                           .Select(x => x.OrderNumber)
                                                           .ToList()
                               })
                               .ToList();

输出:

Output

答案 1 :(得分:1)

要能够按Lines分组,您需要实现IEqualityComparer<List<OrderLine>>并将其传递给GroupBy方法:var groups = orders.GroupBy(o => o.Lines, o => o, new OrderLineEqualityComparer());

internal class OrderLineEqualityComparer : IEqualityComparer<List<OrderLine>>
{
    public bool Equals(List<OrderLine> x, List<OrderLine> y)
    {
        throw new NotImplementedException();
    }

    public int GetHashCode(List<OrderLine> obj)
    {
        throw new NotImplementedException();
    }
}