首页 > 解决方案 > 如何根据属性合并两个对象列表并将重复项合并到新对象中

问题描述

我正在努力为我的问题找到一个简单的解决方案:我有两个对象列表,并希望基于一个属性(串行)比较它们并创建一个包含两个列表中的对象的新列表。如果对象仅在列表一中,我想将其标记为已删除(状态),如果它仅在列表二中,则应将其标记为新(状态)。如果它在两者中我想将其标记为已更改(状态)并存储旧值和新值(金额/新金额)。

所以它看起来像这样:

清单一:

[
    { 
        serial: 63245-8,
        amount:  10
    },
    { 
        serial: 08657-5,
        amount:  100
    }
    ,
    { 
        serial: 29995-0,
        amount:  500
    }
]

清单二:

[
    { 
        serial: 63245-8,
        amount:  100
    },
    { 
        serial: 67455-1,
        amount:  100
    }
    ,
    { 
        serial: 44187-10,
        amount:  50
    }
]

输出:

[
    { 
        serial: 63245-8,
        amount:  10,
        newAmount:  100
        status: "changed"
    },
    { 
        serial: 08657-5,
        amount:  100
        status: "deleted"
    },
    { 
        serial: 29995-0,
        amount:  500,
        status: "deleted"
    }
    { 
        serial: 67455-1,
        amount:  100
        status: "new"
    }
    ,
    { 
        serial: 44187-10,
        amount:  50
        status: "new"
    }
]

除了遍历两个列表并与另一个列表进行比较之外,我想不出任何好的解决方案,构建三个不同的列表并将它们合并到最后,甚至最终对它们进行排序。我很确定有更好的解决方案,甚至可能使用 AutoMapper ?谁能帮我 ?

谢谢!

编辑:因为问题出现在评论中。如果项目在两个列表中,则状态可以是“已更改”或“未更改”。这对实现并不重要,因为我显示对象的新旧数量,只需要特别标记已删除和新对象。不过,状态“未更改”将是一个很好的参考。

标签: c#list.net-core

解决方案


这是列表的双向比较,您可以通过使用 LinqIEnumerable.Except()IEnumerable.Intersect().

您应该做的第一件事是编写一个类来保存数据项:

sealed class Data
{
    public string Serial { get; }
    public int    Amount { get; }

    public Data(string serial, int amount)
    {
        Serial = serial;
        Amount = amount;
    }
}

接下来你需要做的是写一个IEqualityComparer<T>你可以用来比较项目的(你需要这个来使用Intersect()Except()

sealed class DataComparer : IEqualityComparer<Data>
{
    public bool Equals(Data x, Data y)
    {
        return x.Serial.Equals(y.Serial);
    }

    public int GetHashCode(Data obj)
    {
        return obj.Serial.GetHashCode();
    }
}

现在编写一个类来接收比较数据:

enum ComparisonState
{
    Unchanged,
    Changed,
    New,
    Deleted
}

sealed class ComparedData
{
    public Data            Data            { get; }
    public int             PreviousAmount  { get; }
    public ComparisonState ComparisonState { get; }

    public ComparedData(Data data, ComparisonState comparisonState, int previousAmount)
    {
        Data            = data;
        ComparisonState = comparisonState;
        PreviousAmount  = previousAmount;
    }

    public override string ToString()
    {
        if (ComparisonState == ComparisonState.Changed)
            return $"Serial: {Data.Serial}, Amount: {PreviousAmount}, New amount: {Data.Amount}, Status: Changed";
        else
            return $"Serial: {Data.Serial}, Amount: {Data.Amount}, Status: {ComparisonState}";
    }
}

(为方便起见,我在ToString()该类中添加了一个。)

现在您可以按如下方式使用 Linq。阅读评论以了解其工作原理:

class Program
{
    public static void Main()
    {
        var list1 = new List<Data>
        {
            new Data("63245-8",  10),
            new Data("08657-5", 100),
            new Data("29995-0", 500),
            new Data("12345-0",  42)
        };

        var list2 = new List<Data>
        {
            new Data("63245-8", 100),
            new Data("12345-0",  42),
            new Data("67455-1", 100),
            new Data("44187-10", 50),
        };

        var comparer = new DataComparer();

        var newItems     = list2.Except(list1, comparer);    // The second list without items from the first list = new items.
        var deletedItems = list1.Except(list2, comparer);    // The first list without items from the second list = deleted items.
        var keptItems    = list2.Intersect(list1, comparer); // Items in both lists = kept items (but note: Amount may have changed).

        List<ComparedData> result = new List<ComparedData>();

        result.AddRange(newItems    .Select(item => new ComparedData(item, ComparisonState.New,     0)));
        result.AddRange(deletedItems.Select(item => new ComparedData(item, ComparisonState.Deleted, 0)));

        // For each item in the kept list, determine if it changed by comparing it to the first list.
        // Note that the "list1.Find()` is an O(N) operation making this quite slow.
        // You could speed it up for large collections by putting list1 into a dictionary and looking items up in it -
        // but this is unlikely to be needed for smaller collections.

        result.AddRange(keptItems.Select(item =>
        {
            var previous = list1.Find(other => other.Serial == item.Serial);
            return new ComparedData(item, item.Amount == previous.Amount ? ComparisonState.Unchanged : ComparisonState.Changed, previous.Amount);
        }));

        // Print the result, for illustration.

        foreach (var item in result)
            Console.WriteLine(item);
    }
}

其输出如下:

Serial: 67455-1, Amount: 100, Status: New
Serial: 44187-10, Amount: 50, Status: New
Serial: 08657-5, Amount: 100, Status: Deleted
Serial: 29995-0, Amount: 500, Status: Deleted
Serial: 63245-8, Amount: 10, New amount: 100, Status: Changed
Serial: 12345-0, Amount: 42, Status: Unchanged

DotNet 小提琴就在这里


推荐阅读