2012-08-16 91 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; 
}