如何加入两个列表?

Posted

技术标签:

【中文标题】如何加入两个列表?【英文标题】:How to join two lists? 【发布时间】:2014-11-22 01:48:03 【问题描述】:

(完整代码见:https://dotnetfiddle.net/tdKNgH)

我有两个由ParentName 关联的列表,我想以特定方式加入它们。

class Parent

    public string ParentName  get; set; 
    public IEnumerable<string> ChildNames  get; set; 


class Child

    public string ParentName  get; set; 
    public string ChildName  get; set; 


var parents = new List<Parent>()

    new Parent() ParentName = "Lee",
    new Parent() ParentName = "Bob",
    new Parent() ParentName = "Tom"
;

var children = new List<Child>()

    new Child() ParentName = "Lee", ChildName = "A",
    new Child() ParentName = "Tom", ChildName = "B",
    new Child() ParentName = "Tom", ChildName = "C"
;

我正在使用 foreach 循环加入,它可以工作,但是有更简洁的方法吗?

foreach (var parent in parents)

    var p = parent; // to avoid foreach closure side-effects
    p.ChildNames = children.Where(c => c.ParentName == p.ParentName)
                           .Select(c => c.ChildName);

生成的父母列表如下所示:

Parent Children
------ --------
Lee    A 
Bob    (empty) 
Tom    B,C

【问题讨论】:

您可能需要考虑使用字典,如***.com/questions/2101069/… +1 表示字典方法(是的,就是你,Emmad),使代码更加不言自明。但是您拥有的代码实际上还可以。我什至认为它比其他“更优雅”的解决方案安全得多。 您可以将foreach 更改为parents.Select...parents.Select (p =&gt; new Parent ParentName = p.ParentName, ChildNames = children.Where (c =&gt; c.ParentName == p.ParentName).Select (c =&gt; c.ChildName) ); 【参考方案1】:

您可以使用ToLookup 获得最佳性能,但内存损失很小:

 var clu = children.ToLookup(x => x.ParentName, x => x.ChildName);
 parents.ForEach(p => p.ChildNames = clu[p.ParentName]);

【讨论】:

+1。啊,我现在明白了,是的,我喜欢这个。不变性纯粹主义者会讨厌它,但这里的关键不是 Foreach 方法,而是使用“ToLookup”执行简洁的 GroupJoin,我同意性能/内存权衡。【参考方案2】:

您可以为枚举添加扩展方法:

public static void Each<T>(this IEnumerable<T> source, Action<T> action)

    if (action == null)
        return;
    foreach (T obj in source)
        action(obj);

然后做:

parents.Each(p => p.ChildNames = children.Where(c => c.ParentName == p.ParentName)
                                         .Select(c => c.ChildName));

【讨论】:

我忘记了已经有一个ForEach 可以使用的扩展名。感谢您的提醒。 :)【参考方案3】:

您可以加入群组。不过,LINQ 并不意味着更新。所以我不确定这是否真的会让你有任何用处。

IEnumerable<Parent> parents = ...;

var parentsWithChildren = parents.GroupJoin(children,
                                            c => c.ParentName,
                                            c => c.ParentName,
                                            (a, b) => new
                                                      
                                                          Parent = a,
                                                          ChildNames = b.Select(x => x.ChildName)
                                                      );

foreach (var v in parentsWithChildren)

    v.Parent.ChildNames = v.ChildNames;

如果你得到的只是父 names 和子对象,而不是完整的 Parent 对象,这肯定会有所帮助,因为那时你可以将该集合分组加入到子名称中,并创建实例我创建匿名类型 ((a, b) =&gt; new ... ) 的父母。但由于我假设您的 Parent 对象实际上不仅仅包含一个名称,而且这只是一个示例,这似乎是您最好的选择。

【讨论】:

+1。是的,这也是我能想到的唯一其他选择。我使用备用 linqy 语法将它包含在我的 DotNetFiddle 示例中,但它与您的代码几乎相同(您很快!)。 :) 如果没有人提出更好的答案,我会标记你的。【参考方案4】:

考虑将父母的名字称为Parent.Name 而不是Parent.ParentName(父母的父母?),Child 有同样的问题...

class Parent

    public string Name  get; set; 
    public IEnumerable<string> ChildrenNames  get; set; 


class Child

    public string ParentName  get; set; 
    public string Name  get; set; 

您可以通过首先创建parentNames 数组来完全避免foreach

var parentNames = new[]  "Lee", "Bob", "Tom" ;
var allChildren = new List<Child>()

    new Child() ParentName = "Lee", Name = "A",
    new Child() ParentName = "Tom", Name = "B",
    new Child() ParentName = "Tom", Name = "C"
;

这样父级完全由LINQ构造,没有任何副作用(不更新任何变量),应该很简单:

var parents =
    from parentName in parentNames
    join child in allChildren on parentName equals child.ParentName into children
    select new Parent  Name = parentName, ChildrenNames = children.Select(c => c.Name) ;

【讨论】:

+1。谢谢肯。您的解决方案称为组加入,这几乎是我能想到的唯一其他选择。 @MatthewHaugen 提出了相同的想法(使用替代语法)。【参考方案5】:

鉴于 LINQ 基于函数式原理,副作用通常是一个很大的禁忌(这也是为什么没有 foreach 方法的原因)。

因此我建议以下解决方案:

var parents = new List<Parent>()

    new Parent()  ParentName = "Lee" ,
    new Parent()  ParentName = "Bob" ,
    new Parent()  ParentName = "Tom" 
;

var children = new List<Child>()

    new Child()  ParentName = "Lee", ChildName = "A" ,
    new Child()  ParentName = "Tom", ChildName = "B" ,
    new Child()  ParentName = "Tom", ChildName = "C" 
;

var parentsWithChildren = parents.Select(x => new Parent 
 
    ParentName = x.ParentName, 
    ChildNames = children
        .Where(c => c.ParentName == x.ParentName)
        .Select(c => c.ChildName) 
);

foreach (var parent in parentsWithChildren)

    var childNamesConcentrated = string.Join(",", parent.ChildNames);

    var childNames = string.IsNullOrWhiteSpace(childNamesConcentrated) 
        ? "(empty)" : childNamesConcentrated;

    Console.WriteLine("Parent = 0, Children = 1", parent.ParentName, childNames);

上述解决方案,通过设置ChildNames 来修改集合parentsParent 对象。相反,它会创建一组新的 Parents 及其各自的 ChildName。

【讨论】:

“也是没有foreach 方法的原因”——我不敢苟同。 IEnumerable 级别没有 .ForEach 方法。但即便如此,如果您对其进行 PLINQ,您也可以致电 ForAll @code4life,请看blogs.msdn.com/b/ericlippert/archive/2009/05/18/… 所以你是说myCollection.Select(x=&gt;something(x)).ToList().AsParallel().ForAll(...) 不存在? @code4life,一点也不。 PLINQ 需要 拥有ForAll 方法,以便并行遍历集合 - 这是一个例外(注意我写了side effects are GENERALLY a big no-no)。我提供的链接只是证实了我的回答:LINQ 是围绕功能原则构建的,因此 ForEach 方法不可用 - 也不应该。 不变性是一种很好的设计模式,我感谢您对它的重视。

以上是关于如何加入两个列表?的主要内容,如果未能解决你的问题,请参考以下文章

我应该如何从一个控制器操作返回两个对象列表?

如何使用单独的键列表在两个 DataFrame 之间执行连接?

如何重复加入 r 中的表?

Python--对两个一一对应的列表进行排序并且绘制出柱状图

加入集合的 LINQ 更新

C#中如何将两个项目合并到一个项目中