如何加入两个列表?
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 => new Parent ParentName = p.ParentName, ChildNames = children.Where (c => c.ParentName == p.ParentName).Select (c => 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) => 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
来修改集合parents
的Parent
对象。相反,它会创建一组新的 Parent
s 及其各自的 ChildName。
【讨论】:
“也是没有foreach
方法的原因”——我不敢苟同。 IEnumerable
级别没有 .ForEach
方法。但即便如此,如果您对其进行 PLINQ,您也可以致电 ForAll
。
@code4life,请看blogs.msdn.com/b/ericlippert/archive/2009/05/18/…
所以你是说myCollection.Select(x=>something(x)).ToList().AsParallel().ForAll(...)
不存在?
@code4life,一点也不。 PLINQ
需要 拥有ForAll
方法,以便并行遍历集合 - 这是一个例外(注意我写了side effects are GENERALLY a big no-no
)。我提供的链接只是证实了我的回答:LINQ
是围绕功能原则构建的,因此 ForEach
方法不可用 - 也不应该。
不变性是一种很好的设计模式,我感谢您对它的重视。以上是关于如何加入两个列表?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用单独的键列表在两个 DataFrame 之间执行连接?