【问题标题】: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

【问题讨论】:

  • 您可能需要考虑使用字典,如stackoverflow.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) });

标签: c# linq


【解决方案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
  • 所以你是说myCollection.Select(x=&gt;something(x)).ToList().AsParallel().ForAll(...) 不存在?
  • @code4life,一点也不。 PLINQ 需要 拥有ForAll 方法,以便并行遍历集合 - 这是一个例外(注意我写了side effects are GENERALLY a big no-no)。我提供的链接只是证实了我的回答:LINQ 是围绕功能原则构建的,因此 ForEach 方法不可用 - 也不应该。
  • 不变性是一种很好的设计模式,我感谢您对它的重视。
猜你喜欢
  • 2023-03-10
  • 2020-03-05
  • 2016-02-25
  • 1970-01-01
  • 1970-01-01
  • 2011-08-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多