Home > Back-end >  Can I merge two lists using Linq?
Can I merge two lists using Linq?

Time:10-20

I'm trying to merge two lists and I thought I had a solution but if there are two PackItems with the same length the results are not as expected.

Expectations/requirements.

  1. Both lists contain the same total number of pieces for each length.

EDIT: Added code to clarify the input requirements.

  1. The same length can be used in multiple PacksItems.
  2. The same lengths can be produced out of multiple CoilNums.

The goal is to contain a list the contains a unique entry for each PackItem.ID/CoilNum.

Requirement for the output is that the total number of pieces for each length matched the input lists.

Here is the code I have so far.

public class PackItem
{
    public int ID { get; set; }
    public int Quantity { get; set; }
    public string Length { get; set; }
}

public class ProductionInfo
{
    public ProductionInfo AddID(PackItem item)
    {
        LineID = item.ID;
        Quantity = Math.Min(Quantity, item.Quantity);
        return this;
    }
    public int LineID { get; set; }
    public string CoilNum { get; set; }
    public int Quantity { get; set; }
    public string Length { get; set; }
}

private void DoTest()
{
    var packItems = new List<PackItem>()
    {
        new PackItem() {ID = 4, Quantity = 5, Length = "10"},
        new PackItem() {ID = 5, Quantity = 2, Length = "4"},
        new PackItem() {ID = 6, Quantity = 1, Length = "4"}
    };
    var productionInfoList = new List<ProductionInfo>()
    {
        new ProductionInfo() { CoilNum = "A", Quantity = 4, Length = "10"},
        new ProductionInfo() { CoilNum = "B", Quantity = 1, Length = "10"},
        new ProductionInfo() { CoilNum = "B", Quantity = 2, Length = "4"},
        new ProductionInfo() { CoilNum = "A", Quantity = 1, Length = "4"},
    };

    //assert that both lists meet input requirements
    var result1 = "";
    var sum1 = packItems.GroupBy(i => i.Length);
    foreach (var group in sum1) result1  = $"{group.Sum(i=>i.Quantity)}   |   {group.Key}\n";
    var input2 = "";
    var result2 = "";
    var sum2 = productionInfoList.GroupBy(i => i.Length);
    foreach (var group in sum2) result2  = $"{group.Sum(i => i.Quantity)}   |   {group.Key}\n";
    Console.WriteLine("packItems: \nSum(Quantity)  |    Length");
    Console.WriteLine(result1);
    Console.WriteLine("productionInfoList: \nSum(Quantity)  |    Length");
    Console.WriteLine(result2);
    if (result1 == result2)
    {
        Console.WriteLine("Both Lists have the same quantity of each length");
    }
    else
    {
        Console.WriteLine("Error: Both Lists do not have the same quantity of each length");
        return;
    }


    var merged = productionInfoList.SelectMany(x => packItems, (x, y) => new { x, y })
        .Where(i => i.x.Length == i.y.Length)
            .Select(i => i.x.AddID(i.y));
    Console.WriteLine("ID   |   Coil    |   Qty |   Length");
    foreach (var item in merged)
    {
        Console.WriteLine($"{item.LineID}   |   {item.CoilNum}    |   {item.Quantity} |   {item.Length}");
    }
}


//expected output
ID   |   Coil    |   Qty |   Length
4   |   A    |   4 |   10
4   |   B    |   1 |   10
5   |   B    |   2 |   4
6   |   A    |   1 |   4

//actual output
ID   |   Coil    |   Qty |   Length
4   |   A    |   4 |   10
4   |   B    |   1 |   10
5   |   B    |   2 |   4
6   |   B    |   1 |   4
5   |   A    |   1 |   4
6   |   A    |   1 |   4

I'm stuck at this point and they only way I can think of is splitting each of these lists into individual items of one each, and then compiling a list by looping through them one by one.

Is there a way this can be done with Linq?

CodePudding user response:

Here is a method that produces the correct output. Is there an easier way to do this? Can this be done with Linq only?

private void DoTest()
{
    var packItems = new List<PackItem>()
    {
        new PackItem() {ID = 4, Quantity = 5, Length = "10"},
        new PackItem() {ID = 5, Quantity = 2, Length = "4"},
        new PackItem() {ID = 6, Quantity = 1, Length = "4"}
    };
    var productionInfoList = new List<ProductionInfo>()
    {
        new ProductionInfo() { CoilNum = "A", Quantity = 4, Length = "10"},
        new ProductionInfo() { CoilNum = "B", Quantity = 1, Length = "10"},
        new ProductionInfo() { CoilNum = "B", Quantity = 2, Length = "4"},
        new ProductionInfo() { CoilNum = "A", Quantity = 1, Length = "4"},
    };

    //first create a list with one item for each pieces
    var individualProduction = new List<ProductionInfo>();
    foreach (var item in productionInfoList)
    {
        for (int i = 0; i < item.Quantity; i  )
        {
            individualProduction.Add(new ProductionInfo()
            {
                Quantity = 1,
                Length = item.Length,
                CoilNum = item.CoilNum
            });
        }
    }
    //next loop through and assign all the pack line ids
    foreach (var item in individualProduction)
    {
        var packItem = packItems.FirstOrDefault(i => i.Quantity > 0 && i.Length == item.Length);
        if (packItem != null)
        {
            packItem.Quantity -= 1;
            item.LineID = packItem.ID;
        }
        else
        {
            item.Quantity = 0;
        }
    }
    //now group them back into a merged list
    var grouped = individualProduction.GroupBy(i => (i.CoilNum, i.LineID, i.Length));
    //output the merged list
    var merged1 = grouped.Select(g => new ProductionInfo()
    {
        LineID = g.Key.LineID,
        CoilNum = g.Key.CoilNum,
        Length = g.Key.Length,
        Quantity = g.Count()
    });
}

CodePudding user response:

Quite unclear ... This one is closed of the desired result but doesn't take into consideration any quantity so that the fist PackItem is always choosed. If decreasing the pItem.Quantity this would select the next available pItem.ID where Quantity > 0. But this will require more code :)

            var results = productionInfoList.Select(pInfo =>
            {
                var pItem = packItems.First(z => z.Length == pInfo.Length);
                return new { pItem.ID, pInfo.CoilNum, pInfo.Quantity, pInfo.Length };
            }).ToList();

CodePudding user response:

When you have a goal of : The goal is to contain a list the contains a unique entry for each PackItem.ID/CoilNum. your bottom answer is correct, since it has a unique id coilNum pair. What you are looking for is a different uniquenes.

var l = packItems.Join(productionInfoList, x => x.Length, y => y.Length, (x, y) => { y.AddID(x); return y; }).GroupBy(x => new { x.CoilNum, x.Length }).Select(x => x.First());

It is unclear on the exact rules of the case, but here I am using Length as a unique key to perform a join operation (Would recommend to have a different unique key for join operations).

  • Related