如何使用Linq在C#中根据相等性分组对象

3

场景:

我有一个'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行(订单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),然后我就能生成我的行/件输出...现在唯一的问题是能否通过Lines列表对象相似性来分组我的订单对象列表。

我希望我的问题表述清楚了,如果您需要更多细节,请告诉我,我会在这里添加。

2个回答

9
第一步 - 为了在OrderOrderItem类中使用GroupBy(),你需要实现Equals()GetHashCode()或为这两个类创建一个EqualityComparer。请参考此链接
Order中重载Equals()GetHashCode()(仅基于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}";
    }
}

OrderItem中覆盖Equals()GetHashCode()方法(基于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();

输出:

输出


是否可以同时返回订单号?将订单号列表分组在一起? - Roger Oliveira
另外,结果是错误的,它正在按数量和sku进行分组,但应该比较整个列表,例如:列表1:有2个项目A,10/B,20,这些值需要在另一个列表中找到同时包含A,10/B,20的列表。 - Roger Oliveira
1
令人印象深刻,祝贺! - Roger Oliveira
是否也可以按特定值拆分组?例如,我想将组分解为每个最大值的2条记录,因此如果我说我要将其拆分为每个最大2条记录的组,则不是每个10条记录的2个组,而是10个2条记录的组? - Roger Oliveira
@RogerOliveira 请参考 https://dev59.com/Sm865IYBdhLWcg3whe9f - Rui Jarimba

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接