我正在努力找到解决问题的简单方法: 我有两个对象列表,并且想要基于一个属性(序列)比较它们,并创建一个包含两个列表中的对象的新列表。如果对象仅在列表一中,我想将其标记为已删除(状态),如果对象仅在列表二中,则应将其标记为新(状态)。如果在两者中,我都希望将其标记为已更改(状态)并存储新旧值(金额/新金额)。
所以看起来像这样:
列出一:
[
{
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吗? 谁能帮我吗?
谢谢!
编辑:因为问题出现在注释中。 如果项目都在两个列表中,则状态可以是“已更改”或“未更改”。对于实现而言,这并不重要,因为我以旧的和新的数量显示对象,并且只希望特别标记已删除和新的对象。状态为“不变”将是一个很好的参考。
答案 0 :(得分:1)
这是列表的双向比较,您可以使用Linq的IEnumerable.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
答案 1 :(得分:0)
我建议您创建几个自定义类以简化代码理解
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
});
答案 2 :(得分:0)
这是可能的实现方式的示例
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;
}
答案 3 :(得分:0)
已经有一些好的答案。我只想添加一种方法,在数据集增加时不会增加时间复杂度。提醒一下,Linq在幕后所做的只是循环。
由于您必须比较两个列表中每个对象的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
如果您想看一下实际代码,这是我的Fiddle。