如何合并两个列表?

时间:2012-08-16 15:13:12

标签: c# .net

我有以下类的两个列表(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();
    }
}

4 个答案:

答案 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;
}