2012-09-04 18 views
6

Już myślałem o tym, jak mam to rozwiązać, przechodząc własnym rozwiązaniem, ale zastanawiałem się, czy .NET ma już funkcjonalność Próbuję się z tym pogodzić - jeśli tak, wolę użyć czegoś wbudowanego.Scal dwie listy w języku C# i scal obiekty z tym samym identyfikatorem w jeden element listy

Załóżmy, że mam dwa wystąpienia obiektu Widget, nazwijmy je: PartA i PartB. Informacje z każdej z nich zostały zebrane z dwóch różnych usług internetowych, ale obie mają pasujące identyfikatory.

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

Chcę połączyć je tworzyć następujące:

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

zauważyć, jak wartość same_same różni się między sobą, ale uważamy parta mistrz, więc wynik zachowuje wartość but different.

Teraz komplikować sprawy:

Załóżmy, że mamy dwie listy:

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

Teraz tutaj jest jakiś pseudokod opisując to, co chcę zrobić:

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

To tylko niesympatyczny sposób robienia tego. Całkowicie ręcznie: przechodź do każdej listy i twórz nową listę scalonych obiektów - które również zostały scalone ręcznie (tzn. Zakodowane na sztywno). Wykonuje zadanie, ale naprawdę jest do bani i będzie irytujące w utrzymaniu. Szukam lepszego sposobu. –

Odpowiedz

13

można napisać własne rozszerzenie metoda (metody), coś podobnego:

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

przypadku ICanMerge jest podobnym

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

zaimplementowanych np jak:

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

Wtedy to tak proste, jak PartA.MergeWith(PartB).ToList().

+0

Droga, aby zrobić więcej, Tim! Spodziewałem się punktu we właściwym kierunku, ale wydaje się, że dostarczyłeś rozwiązanie (i znacznie lepsze niż to, z czego obecnie korzystam). Dziękuję bardzo :).Zamierzam to wypróbować i wrócić do ciebie z "akceptacją". –

+0

Kiedy próbowałem tego, pozwala mi to tylko na MergeWith na obiekcie (np. Widget), a nie na całą listę, co oznaczałoby wykonanie foreach i wykonanie scalenia dla każdego elementu (po znalezieniu dopasowania dla każdego z nich). Jestem pewien, że to nie miało być używane, ale dlaczego Merge nie jest jedną z moich opcji jako rozszerzenie dla list IEnumerable? – mppowe

+0

@mppowe Czy zmieniłeś to na 'to źródło T' zamiast' this IEnumerable source'? To jest jedyna rzecz, o której mogę pomyśleć, co wyjaśniłoby to, co opisujesz. –

2

Jeśli twoje listy są jedno za jedno (tj. Ta sama liczba pozycji i każda pozycja na liście PartA ma dopasowanie na liście PartB), wówczas rozważałbym metodę rozszerzenia Zip. Zauważ, że Zip nie wymaga od każdej listy posiadania tej samej liczby przedmiotów. Jeśli jednak nie możesz polegać na "parowaniu" elementów z pasującymi identyfikatorami, moje uproszczone podejście nie zadziała.

Można zrobić coś takiego:

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

Jeśli chcesz, aby LINQ wyglądać czystsze, można upakować indywidualne widget łączenie logiki w funkcji lub rozszerzeniem metody i zastosowania, które zamiast delegata inline.

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

Jak korzystać?

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