在LINQ中合并两个列表

7

我有两个自定义对象的列表:

List1: Year, Month, ValueA
List2: Year, Month, ValueB

我想要获得第三个列表,其中包含两个列表的合并:

List3: Year, Month, ValueA, ValueB

有没有更优雅的方法在LINQ VB.Net中执行这个操作?
例如:
List1:
2010 - 6 - 2
2010 - 7 - 5
2010 - 10 - 3

List2:
2010 - 7 - 2
2010 - 8 - 1
2010 - 10 - 2

List3 (result):
2010 - 6 - 2 - 0
2010 - 7 - 5 - 2
2010 - 8 - 0 - 1
2010 - 10 - 3 - 2

提前感谢您。

解决方案 该解决方案的VB.Net翻译:

Dim ListA = From a In List1
    Group Join b In List2
    On a.Year Equals b.Year And a.Month Equals b.Month Into bGroup = Group
    From b In bGroup.DefaultIfEmpty()
    Select a.Year, a.Month, a.Value1, Value2 = If(b Is Nothing, 0, b.Value2)
Dim ListB = From b In List2
    Group Join a In List1
    On b.Year Equals a.Year And b.Month Equals a.Month Into aGroup = Group
    From a In aGroup.DefaultIfEmpty()
    Select b.Year, b.Month, Value1 = If(a Is Nothing, 0, a.Value1), b.Value2
Dim List3 = ListA.Union(ListB)
2个回答

8

当然,你想在数据上执行一个完全的外连接(Full Outer Join),这在LINQ中并不存在,所以我们使用两个联合的左外连接进行模拟:

var A = from a in List1
    join b in List2 on new { a.Year, a.Month } equals new { b.Year, b.Month }
        into bgroup
    from b in bgroup.DefaultIfEmpty()
    select new { a.Year, a.Month, a.ValueA, ValueB = (b == null ? 0 : b.ValueB) };

var B = from b in List2
    join a in List1 on new { b.Year, b.Month } equals new { a.Year, a.Month } 
        into agroup
    from a in agroup.DefaultIfEmpty()
    select new { b.Year, b.Month, ValueA = (a == null ? 0 : a.ValueA), b.ValueB };

var List3 = A.Union(B);

非常抱歉,C#的示例无法正常工作,我无论如何都无法让我的VB.Net示例正常工作。你需要将两个左连接联合起来才能得出正确的答案。我尝试过的所有代码转换器都没有成功。
下面是VB.Net代码,在LINQPad上运行会出错,但是每个示例都应该是正确的:
Dim A = From a In List1 _
    Group Join b In List2 _
        On New With { a.Year, a.Month } Equals New With { b.Year, b.Month} _
        Into bGroup = Group _
    From b In bGroup.DefaultIfEmpty() _
    Select a.Year, a.Month, a.ValueA, ValueB = If(b Is Nothing, 0, b.ValueB)

Dim B = From b In List2 _
    Group Join a In List1 _
        On New With { b.Year, b.Month } Equals New With { a.Year, a.Month} _
        Into aGroup = Group _
    From a In aGroup.DefaultIfEmpty() _
    Select b.Year, b.Month, ValueA = If(a Is Nothing, 0, a.ValueA), b.ValueB

Dim List3 = A.Union(B)

请稍等,这可能无法正常工作,因为它排除了在另一个集合中没有匹配项的项目。 - user7116
谢谢您的建议,但是这个解决方案没有考虑到List2中没有与List1对应的值:在我的例子中,List2中的2010年8月1日将会丢失。 - Joss57
@Joss57:似乎就在你发表这条评论的时候我得出了这个结论。我已经更新了我的示例,使用了一个完全外连接(Full Outer Join)。 - user7116
它有效,我编辑了初始帖子,以便将来有人需要VB.Net版本。谢谢! - Joss57

0

尝试使用这个LINQ扩展

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<CustomObject>
            {
                new CustomObject
                    {
                        Year = 2010,
                        Month = 6,
                        Value = 2
                    },
                new CustomObject
                    {
                        Year = 2010,
                        Month = 7,
                        Value = 5
                    },
                new CustomObject
                    {
                        Year = 2010,
                        Month = 10,
                        Value = 3
                    }
            };

        var target = new List<CustomObject>
            {
                new CustomObject
                    {
                        Year = 2010,
                        Month = 7,
                        Value = 2
                    },
                new CustomObject
                    {
                        Year = 2010,
                        Month = 8,
                        Value = 1
                    },
                new CustomObject
                    {
                        Year = 2010,
                        Month = 10,
                        Value = 2
                    }
            };

        var merge = source.Merge(target, (x, y) => x.Year == y.Year && x.Month == y.Month);

        var toUpdate = merge.Matched((x, y) => x.Value != y.Value)
            .ToArray();

        var inSourceButNotInTarget = merge.NotMatchedBySource();

        var inTargetButNotInSource = merge.NotMatchedByTarget();

        Console.WriteLine("Objects to Update");
        foreach (var mergeMatched in toUpdate)
        {
            Console.WriteLine("Source[{0} -{1} - {2} - {3}]",
                mergeMatched.Source.Year,
                mergeMatched.Source.Month,
                mergeMatched.Source.Value,
                mergeMatched.Target.Value);
        }

        Console.WriteLine("In source but not in target");
        foreach (var customObject in inSourceButNotInTarget)
        {
            Console.WriteLine("Source[{0} -{1} - {2} - 0]",
                              customObject.Year,
                              customObject.Month,
                              customObject.Value);
        }

        Console.WriteLine("In target but not in source");
        foreach (var customObject in inTargetButNotInSource)
        {
            Console.WriteLine("Source[{0} -{1} - 0 - {2}]",
                              customObject.Year,
                              customObject.Month,
                              customObject.Value);
        }

结果:

要更新的对象
2010年7月5日2时
2010年10月3日2时
在源中但不在目标中
2010年6月2日0时
在目标中但不在源中
2010年8月0时1分


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接