【问题标题】:Why does Linq not have Head and Tail?为什么 Linq 没有 Head 和 Tail?
【发布时间】:2013-10-25 08:56:35
【问题描述】:

我经常发现自己想在 IEnumerables 上使用 Head 和 Tail 方法,这在 Linq 中并不存在。虽然我可以很容易地写自己的,但我想知道它们是否被故意排除在外。例如,

var finalCondition = new Sql("WHERE @0 = @1", conditions.Head().Key, conditions.Head().Value);
foreach (var condition in conditions.Tail())
{
  finalCondition.Append("AND @0 = @1", condition.Key, condition.Value);
}

那么,Linq 的最佳实践是什么?我一直在寻找它的用途这一事实是否表明我没有做推荐的事情?如果不是,那么为什么这种常见的功能范式没有在 Linq 中实现?

【问题讨论】:

  • First() 和 Last() 或 FirstOrDefault()
  • Oliver Sturm 的书Functional Programming in C# 有很多可下载的源代码。如果我没记错的话,它还包含 head 和 tail 的扩展方法。
  • @GertArnold 我已经实现了它们,问题更多的是关于这是否被认为是最佳实践。
  • 我明白,这就是为什么我不认为这是一个答案:)。但本书还详细阐述了函数式编程技术的实际使用以及 C# 中的具体限制。它可能会让你走得更远。 (前段时间读过)。
  • @ServéLaurijssen,列表的尾部不是Last()。尾部是没有第一个元素的列表。请参阅herehereherehere

标签: c# linq head tail


【解决方案1】:

从技术上讲,您的 Head 将是 .First(),而您的 Tail 将是 .Skip(1)。但也许你能找到更好的解决方案?喜欢在您的 IEnumerable 上使用 .Aggregate() 吗?

【讨论】:

  • @MatthewWatson 不,tail 不是最后一个元素。
  • @MatthewWatson 是的。头/尾故事的问题是您不要在 before 尾调用头,而是拆分列表的概念。 nvoigt 说的对,OP 的代码相当于调用 first/skip(1),并不是使用 head/tail 的好例子。
  • .Skip(1) 技术上tail 不同,因为它仍然评估第一项然后丢弃它,例如list.Select(mapper).Skip(1) 将运行 mapper(list([0]))
  • 好吧,虽然.Skip(1) 确实评估了第一个元素,但mapper(list([0])) 在您的示例中运行的事实是由于没有按正确的顺序执行它。执行list.Skip(1).Select(mapper) 不会在第一个元素上运行mapper
【解决方案2】:

给定IEnumerable<T>的接口,性能不能总是得到保证。

您注意到大多数函数式编程语言都实现了尾部和头部。但是应该注意的是,这些语言是在内存结构中起作用的。

IEnumerable<T> 没有任何此类约束,因此不能假设这将是有效的。

例如,一种常见的功能模式是递归处理集合的头部,然后递归处理调用的尾部......

例如,如果您使用实体框架执行此操作,您将发送以下(元)调用到 SQL 服务器,紧密循环。

Select * from
(
    Select * from
    (
         Select * from
         (...)
         Skip 1
    )
    Skip 1
);

这将是非常低效的。

编辑:

考虑一下。另一个原因是 C#/VB.Net 不支持尾递归,因此这种模式很容易导致StackOverflow

【讨论】:

  • 此答案不正确且无关紧要。它不应该被接受。 IEnumerable 尾很容易被.Skip(1) 访问,并且无论结构是否在内存中都不会出现性能意外。 IEnumerable设计为一次移动一个元素,这就是Skip(1) 所做的一切。只有当您需要计算 IEnumerable 或跳到任意“索引”中的元素时,才会出现性能惊喜。
  • @kdbanman skip 和 Enumerator.GetNext() 是不同的东西。
  • 一般来说,是的,你是对的。 Skip(N) 最坏的情况是在内部调用 GetNext() N 次。 That's not necessary in all cases,但让我们假装它是。即使在最坏的情况下,这仍然不是问题。 Skip(1) 返回尾部,这是 OP 要求的,并且 Skip(1) 只对 GetNext() 进行一次延迟调用。
  • HereIEnumerable 上的Skip<> 扩展方法的实际.NET 实现。很容易看出我的意思。 Skip(1) 只对 MoveNext() 进行一次延迟调用,这是 GetNext() 的 bool 返回版本。
  • 等一下。 C#/VB.Net 在什么意义上不支持尾递归?我可能在这里遗漏了一些东西,但是尾调用优化已经成为 .NET 的一部分已有一段时间了(也许从 1.0 开始?)。引用大卫布罗曼的话:“Both the source->IL and IL->native compilers understand the tail call optimization”。我可以特别回忆起至少一个调试会话,其中我想“我是如何从那个返回地址到达这个堆栈帧的?哦,是的 - 尾调用省略”)
【解决方案3】:

因为“头尾”概念在函数式编程中用于pattern matching 和递归调用。 由于C#不支持模式匹配,所以不需要实现head()和tail()方法。

let rec sum = function
  | [] -> 0
  | h::t -> h + sum t

至于你的情况 - 你应该使用Aggregate 方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-25
    • 1970-01-01
    • 1970-01-01
    • 2013-11-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多