Roger
Roger

Reputation: 1631

How to group object in C# by its equality using Linq

Scenario:

I have a list of the object 'Order and would like to group by the identical's List<OrderLine> property, by identical I mean same quantity of lines as well as same Sku/Quantity values in the same order' and return the list of order values grouped:

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

Input sample:

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

Output desired:

Lines = Lines.Count(); the count of lines for each grouped identical

Pieces = SUM(OrderLine.Quantity); the sum of all quantities for each grouped identical orders.

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

I used a table representation to make it clearer. So as above there is only 1 record with 1 line (order 2) and qty 10. on the other hand, there are two orders with the same list of lines (order 1 and 3)

So I need that after running a linq algorithm, it would generate for me a object kind of

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

What I tried to do?

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

The code above did not work, basicaly I just need to be able to group the Lines property (so its equality to other OrderLines), then I will be able to generate my output of lines/pieces... the only issue now is to be able to group my object order list by Lines list object similarity.

I hope I was clear in my question, if you need more details please let me know and I will add here.

Upvotes: 3

Views: 3345

Answers (2)

Rui Jarimba
Rui Jarimba

Reputation: 18014

First step - in order to use GroupBy() with the Order and OrderItem classes, you have to implement Equals() and GetHashCode() OR create an EqualityComparer for both classes.

Overriding Equals() and GetHashCode() in Order (based on the Lines property only):

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

Overriding Equals() and GetHashCode() in OrderItem (based on both the Sku and Quantity properties):

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

Testing code - list of orders:

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

Grouping the orders:

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:

Output

Upvotes: 9

Kirill Polishchuk
Kirill Polishchuk

Reputation: 56172

To be able to group by Lines you need to implement IEqualityComparer<List<OrderLine>> and pass it into GroupBy method: 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();
    }
}

Upvotes: 2

Related Questions