我有以下类的两个列表(ObservableCollections):
public class Data
{
public string Key { get; set; }
public string Value { get; set; }
}
一个代表旧对象(listA
),另一个代表更新的对象(listB
)。我想将listB
中的新数据合并到listA
,而不会破坏任何引用。更准确地说,我想做以下事情:
listA
移除listB
中不存在的所有对象(对象按Key
属性进行比较)listA
{/ 1}}中listB
listA
Value
中{2}中存在的所有对象的listA
属性你能提出一些有效的方法吗?我的解决方案很大,看起来非常无效。
更新 目前的解决方案是:
public void MergeInstanceList(List<Instance> instances)
{
var dicOld = Instances.ToDictionary(a => a.Ip);
var dicNew = instances.ToDictionary(a => a.Ip);
var forUpdate = instances.Intersect(Instances, new Comparer()).ToList();
Instances.Where(a => !dicNew.Keys.Contains(a.Ip)).ToList().ForEach(a => Instances.Remove(a));
instances.Where(a => !dicOld.Keys.Contains(a.Ip)).ToList().ForEach(a => Instances.Add(a));
forUpdate.ForEach(a => dicOld[a.Ip].Name = a.Name);
}
public class Comparer : IEqualityComparer<Instance>
{
public bool Equals(Instance x, Instance y)
{
return x.Ip == y.Ip;
}
public int GetHashCode(Instance obj)
{
return obj.Ip.GetHashCode();
}
}
答案 0 :(得分:2)
var listA = ...;
var listB = ...;
var itemsToRemove = new HashSet<Data>(listA.Except(listB));
var itemsToAdd = listB.Except(listA);
var itemsToUpdate = listA.Join(listB, a => listA.Key, b => listB.Key,
(a, b) => new
{
First = a,
Second = b
});
listA.AddRange(itemsToAdd);
listA.RemoveAll(item => itemsToRemove.Contains);
foreach(var pair in itemsToUpdate)
{
//set properties in First to be that of second
}
正如另一个答案所述,您需要创建一个自定义比较器并将其传递给两个Except
方法,以使它们正常工作,或者您需要覆盖Equals
和{ {1}}方法仅基于GetHashCode
。
答案 1 :(得分:1)
使用以下 EqualityComparer ,
public class DataEqualityComparer : IEqualityComparer<Data>
{
public bool Equals(Data x, Data y)
{
return x != null && y != null && x.Key == y.Key;
}
public int GetHashCode(Data obj)
{
return obj.Key.GetHashCode();
}
}
您可以找到以下元素:
DataEqualityComparer comparer = new DataEqualityComparer();
var InListAButNotInListB = listA.Except(listB, comparer);
var InListBButNotInListA = listB.Except(listA, comparer);
var InListAThatAreAlsoInListB = listA.Intersect(listB, comparer).OrderBy(item => item.Key);
var InListBThatAreAlsoInListA = listB.Intersect(listA, comparer).OrderBy(item => item.Key);
var InBothLists = InListAButNotInListB.Zip(InListBButNotInListA, (fromListA, fromListB) => new { FromListA = fromListA, FromListB = fromListB });
答案 2 :(得分:1)
假设Key是唯一的,并且禁止替换listA ObservableCollection实例...
Dictionary<string, Data> aItems = listA.ToDictionary(x => x.Key);
Dictionary<string, Data> bItems = listB.ToDictionary(x => x.Key);
foreach(Data a in aItems.Values)
{
if (!bItems.ContainsKey(a.Key))
{
listA.Remove(a); //O(n) :(
}
else
{
a.Value = bItems[a.Key].Value;
}
}
foreach(Data b in bItems.Values)
{
if (!aItems.ContainsKey(b.Key)
{
listA.Add(b);
}
}
字典在集合之间提供O(1)查找,并提供一个枚举的副本(因此我们没有得到“无法修改正在枚举的集合”异常)。只要没有删除,这应该是O(n)。如果一切都被删除,最坏的情况是O(n ^ 2)。
如果listA ObservableCollection实例不需要保存答案,最好创建一个listC实例并添加应该存在的所有内容(删除非常糟糕)。
答案 3 :(得分:0)
System.Linq.Enumerable没有Full Outer连接方法,但我们可以构建自己的连接方法。
//eager full outer joiner for in-memory collections.
public class FullOuterJoiner<TLeft, TRight, TKey>
{
public List<TLeft> LeftOnly {get;set;}
public List<TRight> RightOnly {get;set;}
public List<Tuple<TLeft, TRight>> Matches {get;set;}
public FullOuterJoiner(
IEnumerable<TLeft> leftSource, IEnumerable<TRight> rightSource,
Func<TLeft, TKey> leftKeySelector, Func<TRight, TKey> rightKeySelector
)
{
LeftOnly = new List<TLeft>();
RightOnly = new List<TRight>();
Matches = List<Tuple<TLeft, TRight>>();
ILookup<TKey, TLeft> leftLookup = leftSource.ToLookup(leftKeySelector);
ILookup<TKey, TRight> rightLookup = rightSource.ToLookup(rightKeySelector);
foreach(IGrouping<TKey, TLeft> leftGroup in leftLookup)
{
IGrouping<TKey, TRight> rightGroup = rightLookup[leftGroup.Key];
if (!rightGroup.Any()) //no match, items only in left
{
LeftOnly.AddRange(leftGroup);
}
else //matches found, generate tuples
{
IEnumerable<Tuple<TLeft, TRight>> matchedTuples =
from leftItem in leftGroup
from rightItem in rightGroup
select Tuple.Create<TLeft, TRight>(leftItem, rightItem);
Matches.AddRange(matchedTuples);
}
}
foreach(IGrouping<TKey, TRight> rightGroup in rightLookup)
{
IGrouping<TKey, TLeft> leftGroup = leftLookup[rightGroup.Key];
if (!leftGroup.Any()) //no match, items only in right
{
RightOnly.AddRange(rightGroup);
}
}
}
}
对于这个问题,它可以这样使用:
ObservableCollection<Data> listA = GetListA();
ObservableCollection<Data> listB = GetListB();
FullOuterJoiner<Data, Data, string> joiner =
new FullOuterJoiner(listA, listB, a => a.Key, b => b.Key);
foreach(Data a in joiner.LeftOnly)
{
listA.Remove(a); // O(n), sigh
}
foreach(Data b in joiner.RightOnly)
{
listA.Add(b);
}
foreach(Tuple<Data, Data> tup in joiner.Matched)
{
tup.Item1.Value = tup.Item2.Value;
}