2012-09-04 21 views
6

私は自分のソリューションをロールバックすることでこれをどのように解決するかについてすでに考えていましたが、.NETには私は達成するために努力しています - もしそうなら、私はむしろ組み込みの何かを使用したいと思います。C#で2つのリストをマージし、同じIDを持つオブジェクトを1つのリスト項目にマージする

Widgetオブジェクトのインスタンスが2つあり、PartAPartBとしましょう。それぞれの情報は、2つの異なるWebサービスから取得されていますが、どちらも一致するIDを持っています。

PartA 
{ 
    ID: 19, 
    name: "Percy", 
    taste: "", 
    colour: "Blue", 
    shape: "", 
    same_same: "but different" 
} 

PartB 
{ 
    ID: 19, 
    name: "", 
    taste: "Sweet", 
    colour: "", 
    shape: "Hexagon", 
    same_same: "but not the same" 
} 

私は、次の作成するためにこれらをマージしたい:same_sameの値は、それぞれの間で異なりますが、私たちはPartAマスターを検討し、その結果は値but differentを保持する方法

Result 
{ 
    ID: 19, 
    name: "Percy", 
    taste: "Sweet", 
    colour: "Blue", 
    shape: "Hexagon", 
    same_same: "but different" 
} 

お知らせ。

たちは二つのリストがあるとします:今、問題を複雑にする

List<Widget> PartA = getPartA(); 
List<Widget> PartB = getPartB(); 

は今ここに私が何をしたいのか説明し、いくつかの擬似コードです:

List<Widget> Result = PartA.MergeWith(PartB).MergeObjectsOn(Widget.ID).toList(); 
+0

本当にスマートな方法です。完全に手動:各リストをループし、手動でマージした(つまりハードコードされた)マージされたオブジェクトの新しいリストを作成します。それは仕事をしますが、それは本当に吸うと維持する迷惑になる。私はより良い方法を探しています。 –

答えて

13

あなたがあなた自身の拡張を書くことができます方法:このようなもの:

static class Extensions 
{ 
    public static IEnumerable<T> MergeWith<T>(this IEnumerable<T> source, IEnumerable<T> other) where T : ICanMerge 
    { 
     var otherItems = other.ToDictionary(x => x.Key); 
     foreach (var item in source) 
     { 
      yield return (T)item.MergeWith(otherItems[item.Key]); 
     } 
    } 
    public static string AsNullIfEmpty(this string s) 
    { 
     if (string.IsNullOrEmpty(s)) 
      return null; 
     else 
      return s; 
    } 
} 
例えば実装

public interface ICanMerge 
{ 
    object Key { get; } 
    ICanMerge MergeWith(ICanMerge other); 
} 

ICanMergeは次のようである10

like:

public class Widget : ICanMerge 
{ 
    object ICanMerge.Key { get { return this.ID; } } 
    int ID {get;set;} 
    string taste {get;set;} 
    public ICanMerge MergeWith(ICanMerge other) 
    { 
     var merged = new Widget(); 
     var otherWidget = (Widget)other; 
     merged.taste = this.taste.AsNullIfEmpty() ?? otherWidget.taste; 
     //... 
     return merged; 
    } 
} 

次に、PartA.MergeWith(PartB).ToList()のように簡単です。

+0

余分な道を行く道、ティム!私は正しい方向へのポイントを期待していましたが、あなたは解決策を提供したようです(そして、私が現在使っているものよりずっと優れています)。どうもありがとう :)。私はこれを試して "受け入れ"をしてあなたに戻ってきます。 –

+0

私はこれを試してみると、リスト全体に対してではなく、foreachを実行して各アイテムのマージを行うこと(それぞれのマッチを見つけた後)を行うだけで、MergeWithをオブジェクト(例えばウィジェット)に対して行うことができます。これは使用されることを意図したものではありませんが、MergeWithがIEnumerableリストの拡張機能として私のオプションの1つではないのはなぜですか? – mppowe

+0

@mppowe 'this IEnumerable source'の代わりに' this T source'に変更しましたか?それがあなたが何を描写するのかを説明することができると思う唯一のことです。 –

2

リストが1つの場合(つまり、同じ数のアイテムとPartAリストの各アイテムがPartBリストに一致している場合)、私はZip拡張メソッドを検討します。 Zipでは実際には各リストの項目数が同じである必要はありません。しかし、IDと一致するアイテムを「ペアリング」することに頼ることができない場合、私の単純なアプローチは機能しません。

あなたはこのような何かを行うことができます:

var alist = GetPartAWidgets().OrderBy(w => w.ID); 
var blist = GetPartBWidgets().OrderBy(w => w.ID); 
var merged = alist.Zip(blist, (a,b) => new Widget() 
      { 
       ID = a.ID, 
       Name = string.IsNullOrEmpty(a.Name) ? b.Name : a.Name, 
       //etc. 
      }); 

あなたのLINQはきれいに見えるしたい場合は、機能や拡張方法及び使用の代わりに、インラインデリゲートにロジックをマージ個々のウィジェットをカプセル化することができます。

0
public interface IMerge<out T> 
{ 
    IEnumerable<IMergeMatched<T>> Matched(); 

    IEnumerable<IMergeMatched<T>> Matched(Func<T, T, bool> predicate); 

    IEnumerable<T> NotMatchedBySource(); 

    IEnumerable<T> NotMatchedBySource(Func<T, bool> predicate); 

    IEnumerable<T> NotMatchedByTarget(); 

    IEnumerable<T> NotMatchedByTarget(Func<T, bool> predicate); 
} 

public interface IMergeMatched<out T> 
{ 
    T Source { get; } 

    T Target { get; } 
} 

public static class Enumerable 
{ 
    public static IMerge<TSource> Merge<TSource>(this IEnumerable<TSource> source, IEnumerable<TSource> target, 
              Func<TSource, TSource, bool> predicate) 
    { 
     return new Merge<TSource>(source, target, predicate); 
    } 
} 

public class Merge<T> : IMerge<T> 
{ 
    private readonly Func<T, T, bool> _predicate; 
    private readonly IEnumerable<T> _source; 
    private readonly IEnumerable<T> _target; 
    private IEnumerable<IMergeMatched<T>> _matcheds; 
    private IEnumerable<T> _notMatchedBySource; 
    private IEnumerable<T> _notMatchedByTarget; 

    public Merge(IEnumerable<T> source, IEnumerable<T> taget, Func<T, T, bool> predicate) 
    { 
     _source = source; 
     _target = taget; 
     _predicate = predicate; 
    } 

    public IEnumerable<IMergeMatched<T>> Matched() 
    { 
     if (_matcheds == null) 
     { 
      Analize(); 
     } 
     return _matcheds; 
    } 

    public IEnumerable<IMergeMatched<T>> Matched(Func<T, T, bool> predicate) 
    { 
     return Matched() 
      .Where(t => predicate.Invoke(t.Source, t.Target)) 
      .ToArray(); 
    } 

    public IEnumerable<T> NotMatchedBySource() 
    { 
     if (_notMatchedBySource == null) 
     { 
      Analize(); 
     } 
     return _notMatchedBySource; 
    } 

    public IEnumerable<T> NotMatchedBySource(Func<T, bool> predicate) 
    { 
     return NotMatchedBySource() 
      .Where(predicate) 
      .ToArray(); 
    } 

    public IEnumerable<T> NotMatchedByTarget() 
    { 
     if (_notMatchedByTarget == null) 
     { 
      Analize(); 
     } 
     return _notMatchedByTarget; 
    } 

    public IEnumerable<T> NotMatchedByTarget(Func<T, bool> predicate) 
    { 
     return NotMatchedByTarget() 
      .Where(predicate) 
      .ToArray(); 
    } 

    private void Analize() 
    { 
     var macheds = new List<MergeMached<T>>(); 
     var notMachedBySource = new List<T>(_source); 
     var notMachedByTarget = new List<T>(_target); 

     foreach (var source in _source) 
     { 
      foreach (var target in _target) 
      { 
       var macth = _predicate.Invoke(source, target); 
       if (!macth) continue; 

       macheds.Add(new MergeMached<T>(source, target)); 
       notMachedBySource.Remove(source); 
       notMachedByTarget.Remove(target); 
      } 
     } 

     _matcheds = macheds.ToArray(); 
     _notMatchedBySource = notMachedBySource.ToArray(); 
     _notMatchedByTarget = notMachedByTarget.ToArray(); 
    } 
} 

public class MergeMached<T> : IMergeMatched<T> 
{ 
    public MergeMached(T source, T target) 
    { 
     Source = source; 
     Target = target; 
    } 

    public T Source { get; private set; } 

    public T Target { get; private set; } 
} 

使用方法

var source = new List<MediaFolder> 
      { 
       new MediaFolder 
        { 
         Id = "Id1", 
         Name = "Name1", 
         Path = "Path1" 
        }, 
       new MediaFolder 
        { 
         Id = "Id2", 
         Name = "Name2", 
         Path = "Path2" 
        }, 
       new MediaFolder 
        { 
         Id = "Id3", 
         Name = "Name3", 
         Path = "Path3" 
        }, 
       new MediaFolder 
        { 
         Id = "Id4", 
         Name = "Name4", 
         Path = "Path4" 
        }, 
       new MediaFolder 
        { 
         Id = "Id5", 
         Name = "Name5", 
         Path = "Path5" 
        }, 
       new MediaFolder 
        { 
         Id = "Id6", 
         Name = "Name6", 
         Path = "Path6" 
        } 
      }; 

     var target = new List<MediaFolder> 
      { 
       new MediaFolder 
        { 
         Id = "Id1", 
         Name = "Actualizado en el objeto", 
         Path = "Path1" 
        }, 
        //Id2 eliminado 
       new MediaFolder 
        { 
         Id = "Id3", 
         Name = "Name3", 
         Path = "Actualizado tambien" 
        }, 
       new MediaFolder 
        { 
         Id = "Id4", 
         Name = "Name4", 
         Path = "Path4" 
        }, 
       new MediaFolder 
        { 
         Id = "Id5", 
         Name = "Name5", 
         Path = "Path5" 
        }, 
       new MediaFolder 
        { 
         Id = "Id6", 
         Name = "Name6", 
         Path = "Path6" 
        }, 
        new MediaFolder 
        { 
         Id = "Id7", 
         Name = "Nuevo Item 7", 
         Path = "Nuevo Item 7" 
        } 
      }; 

     var merge = source.Merge(target, (x, y) => x.Id == y.Id); 

     var toUpdate = merge.Matched((x, y) => x.Name != y.Name | x.Path != y.Path) 
      .ToArray(); 

     var toDelete = merge.NotMatchedBySource(); 
     var toInsert = merge.NotMatchedByTarget(); 

     Assert.AreEqual(2, toUpdate.Count()); 
     Assert.IsTrue(toUpdate.Count(x => x.Source.Id == "Id1" & x.Target.Id == "Id1") > 0); 
     Assert.IsTrue(toUpdate.Count(x => x.Source.Id == "Id3" & x.Target.Id == "Id3") > 0); 

     Assert.AreEqual("Id7", toInsert.First().Id); 
     Assert.AreEqual("Id2", toDelete.First().Id); 
関連する問題