2012-08-16 62 views
0

我有以下类的两个列表(ObservableCollections):如何合并两个列表?

public class Data 
{ 
    public string Key { get; set; } 
    public string Value { get; set; } 
} 

一个较旧的对象(listA)和第二代表更新的人(listB)。我想合并来自listB的新数据到listA而不会打破任何引用。更确切地说我要做到以下几点:

  • listA删除不listB存在的所有对象(对象由Key财产相比)
  • 添加到listA所有对象从listB不中listA存在
  • 更新Value属性在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(); 
    } 
} 
+10

你能显示您当前的解决方案,使我们可以看到它是如何被imprved? – 2012-08-16 15:14:50

+0

我不知道你的藏品有多大,但(一般),那岂不是更容易改写这样的问题:从数组listB获取所有项目(当前的)和追加的项目listA的在数组listB不存在(的那些没有更新)。在这种情况下,'var listC = listB.Union(listA);'与自定义'IEqualityComparer'应该是OK ... – 2012-08-16 15:20:35

+0

那么我的数据类实际上更复杂。我将删除一些不需要的代码并在5分钟内发布。 //完成。 – Poma 2012-08-16 15:20:53

回答

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方法为他们正常工作,否则你将需要重写EqualsGetHashCode方法是基于下来仅仅Key

+0

你说得对。加入比两个OrderBys和一个Zip更好。加入+1。 – decyclone 2012-08-16 15:53:03

+0

RemoveRange只接受整数参数。 http://msdn.microsoft.com/en-us/library/y33yd2b5 – 2012-08-16 17:20:22

+0

@DavidB对,你现在将转换为'RemoveAll'调用。 – Servy 2012-08-16 17:29:40

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 }); 
+0

你的inobothlists只是给你一个列表上的项目(listA)而不是listA中的项目和listB中相应的项目来更新。 – Servy 2012-08-16 15:26:54

+0

@Servy:它说:“在那个listA的存在于两个列表中的所有对象” – decyclone 2012-08-16 15:28:20

+0

的想法是,你有两个对象,用不同的参考,但同样'Key',所以它们在逻辑上代表了同样的事情。他们的其他(非关键)属性是不同的,他们需要被做成相同。 – Servy 2012-08-16 15:30:54

1

假设的关键是独一无二的,而且更换的ObservableCollection listA的实例是被禁止的......

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实例并添加应该在那里的所有东西(Remove非常糟糕)。

+0

你最坏的情况是O(n * 2)而不是O(n^2),它仍然是O(n)。当你找到所有要删除的项目并使用“RemoveRange”(在我的回答中演示)时,删除也不那么糟糕,因为不是一次只移动一个不在一个位置的所有项目,而是移动所有的移动一次在最后。虽然大O是一样的,但速度更快。 – Servy 2012-08-16 16:20:56

+0

@Servy在这个评论中的一切都是“最坏的情况”。假设RemoveRange如您所说的那样工作,您的解法会枚举a和b多次:a + b + a + b + a + b + b + a + a = 6a + 4b。如果RemoveRange按照文档说的那样工作,那么你的解决方案实际上是:a^2 + 5a + 4b。我的解决办法列举这样的集合:A + B + A * A + B = A^2 + A + 2B – 2012-08-16 17:30:20

+0

产生每个在我的回答3个查询各自O(A + B),产生图3a + 3b中。 'AddRange'增加了b,'RemoveAll'增加了另一个“a”,并且这个foreach为5a + 4b增加了另一个a。你怎么从那里到达^ 2 + 5a + 4b? 'RemoveAll'(和RemoveRange)都是O(n)操作,我只打一个电话,而不是n个电话。我同意你对自己方法的评价。 – Servy 2012-08-16 17:42:10

0

System.Linq.Enumerable没有完全外连接方法,但我们可以构建自己的。

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