3

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.

Roger Oliveira
  • 1,589
  • 1
  • 27
  • 55

2 Answers2

9

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

Rui Jarimba
  • 11,166
  • 11
  • 56
  • 86
  • Is it possible to return the Order numbers as well? the list of order number grouped together? – Roger Oliveira Nov 04 '18 at 22:42
  • also, the result was wrong, it is grouping by quantity and sku, but it should compare the whole list, for example: list 1: has 2 items A,10/B,20 these values together need to find another list with both A,10/B,20 in the list. – Roger Oliveira Nov 04 '18 at 22:52
  • 1
    Impressive, congrats! – Roger Oliveira Nov 05 '18 at 01:48
  • Is it also possible to split the group by a specific value? such as I want to break down the group in 2 records each as max, so instead of having for example 2 groups of 10 records each, to have instead 10 groups of 2 records each if I say that I want to split in groups of max 2 records each? – Roger Oliveira Nov 28 '18 at 04:52
  • @RogerOliveira see https://stackoverflow.com/questions/3773403/linq-partition-list-into-lists-of-8-members – Rui Jarimba Nov 28 '18 at 07:52
1

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();
    }
}
Kirill Polishchuk
  • 54,804
  • 11
  • 122
  • 125