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();
    }
}
4

4 回答 4

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方法以使它们正常工作,或者您需要覆盖EqualsandGetHashCode方法以基于 just Key

于 2012-08-16T15:28:52.410 回答
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 });
于 2012-08-16T15:25:15.360 回答
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 实例并添加应该存在的所有内容(删除太糟糕了)。

于 2012-08-16T15:44:16.513 回答
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;
}
于 2012-08-17T15:08:01.933 回答