簡體   English   中英

如何根據屬性合並兩個對象列表並將重復項合並到新的 object

[英]How to merge two lists of objects based on properties and merge duplicates into new object

我正在努力尋找一個簡單的解決方案來解決我的問題:我有兩個對象列表,並希望根據一個屬性(串行)比較它們,並創建一個包含兩個列表中的對象的新列表。 如果 object 僅在列表一中,我想將其標記為已刪除(狀態),如果僅在列表二中,則應將其標記為新(狀態)。 如果它在兩者中我想將其標記為已更改(狀態)並存儲舊值和新值(金額/新金額)。

所以它看起來像這樣:

清單一:

[
    { 
        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
    }
]

Output:

[
    { 
        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? 誰能幫我?

謝謝!

編輯:因為問題出現在評論中。 如果項目在兩個列表中,則狀態可以是“已更改”或“未更改”。 這對實現並不重要,因為我用它們的新舊數量顯示對象,只需要特別標記已刪除和新對象。 不過,狀態“未更改”將是一個很好的參考。

這是列表的雙向比較,您可以通過使用 Linq 的IEnumerable.Except()IEnumerable.Intersect()來實現。

您應該做的第一件事是編寫一個 class 來保存數據項:

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

現在寫一個 class 來接收比較數據:

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

(為方便起見,我在 class 中添加了一個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);
    }
}

這個的output如下:

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 小提琴就在這里

我建議您創建幾個自定義類來簡化代碼理解

public class Item
{
    public string serial;
    public int? amount;
    public int? newAmount;
    public string status;
}

public class L1Item : Item
{       
    public L1Item(string s, int a)
    {
        serial = s;
        amount = a;
        status = "deleted";
    }
}

public class L2Item : Item
{
    public L2Item(string s, int a)
    {
        serial = s;
        amount = a;
        status = "new";
    }
}

然后使用您提供的輸入,您可以創建兩個單獨的列表

List<Item> l1 = new List<Item>() { new L1Item("63245-8", 10), new L1Item("08657-5", 100), new L1Item("29995-0", 500) };
List<Item> l2 = new List<Item>() { new L2Item("63245-8", 100), new L2Item("67455-1", 100), new L2Item("44187-10", 50) };

然后您可以將它們連接成一個列表並按serial分組

var groupedList = l1.Concat(l2).GroupBy(x => x.serial);

最后,對每個序列的所有項目進行分組,進行相應的更改並檢索它們。

var output = groupedList.Select(g => new Item()
{
    serial = g.Key,
    amount = g.First().amount,
    newAmount = g.Count() > 1 ? g.Last().amount : null,
    status = g.Count() > 1 ? "changed" : g.First().status
});

這是一個可能的實現示例

public class Obj
{
    public string serial { get; set; }
    public int amount { get; set; }
    public int? newAmount { get; set; }
    public Status status { get; set; }
}

public enum Status
{
    undefined,
    changed,
    deleted,
    @new
}
static void Main(string[] args)
    {
        string listOneJson = @"[
                                { 
                                    serial: '63245-8',
                                    amount:  10
                                },
                                { 
                                    serial: '08657-5',
                                    amount:  100
                                }
                                ,
                                { 
                                    serial: '29995-0',
                                    amount:  500
                                }
                            ]";
        string listTwoJson = @"[
                                {
                                    serial: '63245-8',
                                    amount: 100
                                },
                                {
                                    serial: '67455-1',
                                    amount: 100
                                }
                                ,
                                {
                                    serial: '44187-10',
                                    amount: 50
                                }
                               ]";
        IList<Obj> listOne = JsonConvert.DeserializeObject<IList<Obj>>(listOneJson);
        IList<Obj> listTwo = JsonConvert.DeserializeObject<IList<Obj>>(listTwoJson);

        var result = merge(listOne, listTwo);
    }

 public static IEnumerable<Obj> merge(IList<Obj> listOne, IList<Obj> listTwo)
 {

        List<Obj> allElements = new List<Obj>();
        allElements.AddRange(listOne);
        allElements.AddRange(listTwo);

        IDictionary<string, int> dict1 = listOne.ToDictionary(x => x.serial, x => x.amount);
        IDictionary<string, int> dict2 = listTwo.ToDictionary(x => x.serial, x => x.amount);
        IDictionary<string, Obj> dictResults = new Dictionary<string, Obj>();

        foreach (var obj in allElements)
        {
            string serial = obj.serial;

            if (!dictResults.ContainsKey(serial))
            {
                bool inListOne = dict1.ContainsKey(serial);
                bool inListTwo = dict2.ContainsKey(obj.serial);

                Obj result = new Obj { serial = serial };

                if (inListOne && inListTwo) {
                    result.status = Status.changed;
                    result.amount = dict1[serial];
                    result.newAmount = dict2[serial];
                }
                else if (!inListOne && inListTwo)
                {
                    result.status = Status.@new;
                    result.amount = dict2[serial];
                }
                else if (inListOne && !inListTwo)
                {
                    result.status = Status.deleted;
                    result.amount = dict1[serial];
                }

                dictResults.Add(serial, result);
            }
        }
        return dictResults.Values;
   }

已經有一些很好的答案。 我只是想添加一種方法來做到這一點,當數據集增加時不會增加時間復雜度。 提醒一下,Linq 在幕后所做的只是 for 循環。

由於您必須為兩個列表中的每個 object 比較 2 個不同的條件,不幸的是,您必須遍歷每個條件。 但是有一種方法可以使這個過程更快。

假設您在 listOne 中有n 個對象,在 listTwo 中有m個對象。

您可以先分別循環遍歷 listOne 和 listTwo 中的所有對象,然后為每個列表創建一個 Dictionary。 即dictOne 和dictTwo。 這將分別需要 O(n) 和 O(m) 時間復雜度。

然后,遍歷 listOne 並檢查項目是否存在於 dictTwo 中。 接下來,遍歷 listTwo 並檢查項目是否存在於 dictOne 中。

這樣,整體時間復雜度大約為 O(n+m)。

數據模型:

public class InputData{
    public InputData(string serial, int amount){
        this.Serial = serial;
        this.Amount = amount;
    }
    public string Serial {get; set;}
    public int Amount{get;set;}
}

public class ResultData{
    public ResultData(string serial, int amount, int newAmount, string status){
        this.Serial = serial;
        this.Amount = amount;
        this.NewAmount = newAmount;
        this.Status = status;
    }

    public ResultData(string serial, int newAmount, string status){
        this.Serial = serial;
        this.NewAmount = newAmount;
        this.Status = status;
    }
    public string Serial {get; set;}
    public int Amount{get;set;}
    public int NewAmount{get;set;}
    public string Status {get;set;}
}

主要方法:

public static void Main()
{
    List<InputData> listOne = new List<InputData>
    {
        new InputData("63245-8", 10),
        new InputData("08657-5", 100),
        new InputData("29995-0", 500)
    };

    List<InputData> listTwo = new List<InputData>
    {
        new InputData("63245-8", 100),
        new InputData("67455-1", 100),
        new InputData("44187-10", 50)
    };

    Dictionary<string, InputData> dictOne = CreateDictionary(listOne);      
    Dictionary<string, InputData> dictTwo = CreateDictionary(listTwo);

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

    result.AddRange(ProcessData(listOne, dictTwo, "deleted"));
    result.AddRange(ProcessData(listTwo, dictOne, "new"));

    foreach(var item in result){
        Console.WriteLine($"Serial: {item.Serial}, Amount: {item.Amount}, Status: {item.Status}");
    }
}

結果:

Serial: 63245-8, Amount: 100, Status: changed
Serial: 08657-5, Amount: 100, Status: deleted
Serial: 29995-0, Amount: 500, Status: deleted
Serial: 63245-8, Amount: 10, Status: changed
Serial: 67455-1, Amount: 100, Status: new
Serial: 44187-10, Amount: 50, Status: new

如果您想查看實際代碼,這是我的小提琴

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM