[英]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
我建議您創建幾個自定義類來簡化代碼理解
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.