【问题标题】:Joining Xs, Ys into Points with Linq用 Linq 将 Xs、Ys 连接成点
【发布时间】:2010-06-22 04:00:46
【问题描述】:

我似乎有点坚持这一点,但我在 Linq 的经验并不是很好。基本上,我有这样的代码:

public class Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

public class B
{
    public List<Point> Points { get; set; }
    public B(IEnumerable<int> Xs, IEnumerable<int> Ys)
    {
        // How to best combine Xs and Ys into Points ?
    }
}

现在,我如何填写该构造函数以正确连接这些集合?我通常会使用.Join(),但没有内部键可以加入。它还应该能够处理一个数组的元素比另一个数组少或为空的可能性(永远不会发生,但这是可能的)。

【问题讨论】:

  • 您不知道,但您正在寻找“zip”功能。这是我最喜欢的链接:How to do Python's zip in C#?
  • 这就是微软在 .NET 4.0 中添加Zip 扩展方法的原因。

标签: c# .net linq join


【解决方案1】:

在 C# 4 中,您可以使用 Zip operator:

var result = Xs.Zip( Ys, (a,b) => new Point(a,b) );

在 C# 3 中,您可以在带有 select 的序列之一上使用 ElementAt 来做同样的事情:

var result = Xs.Select( (x,i) => new Point( x, Ys.ElementAt(i) );

第二个选项的主要问题是,如果IEnumerable 集合本身是一个投影(与实现本机索引操作的东西,如数组或列表相反),ElementAt 可能会非常昂贵。你可以通过首先强制第二个集合成为一个列表来解决这个问题:

var YsAsList = Ys.ToList();
var result = Xs.Select( (x,i) => new Point( x, YsAsList.ElementAt(i) );

您必须处理的另一个问题(如果您不使用 Zip)是如何处理不平衡的集合。如果一个序列比另一个序列长,您必须决定正确的分辨率应该是什么。您的选择是:

  1. 不支持。尝试失败或中止。
  2. 压缩尽可能多的项目在较短的序列中。
  3. 压缩较长序列中存在的尽可能多的项目,用默认值替换短序列。

如果您使用Zip(),您会自动选择第二个选项,如文档所示:

该方法将第一个序列的每个元素与一个元素合并 在第二个中具有相同的索引 序列。如果序列没有 相同数量的元素, 方法合并序列,直到它 到达其中之一的尽头。为了 例如,如果一个序列有三个 元素,另一个有四个, 结果序列将只有 三要素。

所有这一切的最后一步是,您需要将项目结果转换为列表以将其分配给您的Points 对象。这部分很简单,使用ToList() 方法:

Points = Xs.Select( (x,i) => new Point( x, Ys.ElementAt(i) ).ToList();

或在 C# 4 中:

Points = Xs.Zip( Ys, (a,b) => new Point(a,b) ).ToList();

【讨论】:

  • 请注意,这只是 .Net 4.0。
  • 非常感谢。这似乎正是我想要的。我对 Zip 会做什么(不正确地处理大小不等)有一个错误的假设。我使用的是 C# 4,所以效果很好!
  • 您可以在 c# 3.5 及更低版本中自己编写 Zip。
【解决方案2】:

如果两个集合的大小不同或者一个或两个为空,则可能会出错。

public B(IEnumerable<int> Xs, IEnumerable<int> Ys) 
{ 
    if (Xs == null || Ys == null)
       throw new ArgumentNullException( "Both collections need to be non-null" );

    if (Xs.Count() != Ys.Count())
       throw new ArgumentException( "Collections must be of the same size" );

    this.Points = Xs.Zip( (x,y) => new Point { X = x, Y = y } ).ToList();
}

如果您需要 .NET 3.5 的内容,请将最后一行替换为。

this.Points = Xs.Select( (x,i) => new Point { X = x, Y = Ys.ElementAt(i) } ).ToList();

【讨论】:

  • 是的,我将手动检查空值和空值。如果它们不相等,我希望它把所有数据带到那个不相等的空间。
  • @drharris -- 我认为 Zip 会做你想做的事。如果您需要它与 3.5 一起使用,请使用 Xs.Take( Ys.Count() ).Select( ... ) - 这样,如果 X 多于 Y,则仅使用 X 中的第一个 Ys-count 元素。如果 Y 多于 X,则 take 将简单地返回少于请求的元素。
猜你喜欢
  • 1970-01-01
  • 2022-12-01
  • 1970-01-01
  • 2017-02-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-23
  • 2010-10-11
相关资源
最近更新 更多