简体   繁体   English

如何合并两个列表?

[英]How to merge two lists?

I have two lists (ObservableCollections) of following class: 我有以下类别的两个列表(ObservableCollections):

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

One representing old objects ( listA ) and second representing updated ones ( listB ). 一个代表旧对象( listA ),第二个代表更新对象( listB )。 I want to merge new data from listB to listA without breaking any references. 我想将新数据从listB合并到listA而不破坏任何引用。 More precisely I want to do the following: 更准确地说,我想执行以下操作:

  • Remove from listA all objects that do not exist in listB (objects are compared by Key property) 从删除listA不存在中的所有对象的listB (对象由比较Key属性)
  • Add to listA all objects from listB that do not exist in listA 加入listA所有对象的listB不存在中listA
  • Update Value property for all objects in listA that are present in both lists 两个列表中都存在的listA中所有对象的Update Value属性

Can you propose some efficient way to do that? 您可以提出一些有效的方法吗? My solution is big and seems very ineffective. 我的解决方案很大,似乎效果很差。

Update: Current solution is: 更新:当前解决方案是:

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

As is mentioned in another answer, you will need to create a custom comparer and pass it into the two Except methods for them to work properly, or you will need to override the Equals and GetHashCode methods to be based off of just Key . 如另一个答案中所述,您将需要创建一个自定义比较器并将其传递到两个Except方法中,以使其正常工作,或者您需要重写EqualsGetHashCode方法以仅基于Key

With following EqualityComparer , 通过以下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();
    }
}

You can find out elements like following: 您可以找到类似以下内容的元素:

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

Assuming Key is unique, and that replacing the listA ObservableCollection instance is forbidden... 假设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);
  }
}

The dictionaries give O(1) lookups between the collections, and provide a copy to enumerate over (so we don't get the "can't modify collections that are being enumerated" exception). 字典在集合之间进行O(1)查找,并提供一个副本进行枚举(因此,我们不会出现“无法修改正在枚举的集合”的异常)。 This should be O(n) as long as nothing is removed. 只要没有删除任何内容,它就应该是O(n)。 The worst case is O(n^2) if everything is removed. 如果删除所有内容,最坏的情况是O(n ^ 2)。

If the listA ObservableCollection instance isn't required to hold the answer, it's better to create a listC instance and add everything that should be there (Remove is so bad). 如果不需要listA ObservableCollection实例来保存答案,最好创建一个listC实例并添加应该存在的所有内容(删除非常糟糕)。

System.Linq.Enumerable doesn't have a Full Outer join method, but we can build our own. 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);
      }
    }
  }
}

For this question, it can be used this way: 对于这个问题,可以这样使用:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM