当前位置: 首页 > 知识库问答 >
问题:

纯函数语言如何处理基于索引的算法?

陆宝
2023-03-14

我一直在努力学习函数式编程,但我仍然难以像函数式程序员那样思考。其中一个问题是如何实现索引密集型操作,这些操作强烈依赖于循环/执行顺序。

例如,考虑下面的java代码

public class Main {
    public static void main(String[] args) {
        List<Integer> nums = Arrays.asList(1,2,3,4,5,6,7,8,9);
        System.out.println("Nums:\t"+ nums);
        System.out.println("Prefix:\t"+prefixList(nums));
    }
  
    private static List<Integer> prefixList(List<Integer> nums){
      List<Integer> prefix = new ArrayList<>(nums);
      for(int i = 1; i < prefix.size(); ++i)
        prefix.set(i, prefix.get(i) + prefix.get(i-1));
      return prefix;
    }
}
/*
System.out: 
Nums:   [1, 2, 3, 4, 5, 6, 7, 8, 9]
Prefix: [1, 3, 6, 10, 15, 21, 28, 36, 45]
*/

这里,在prefixList函数中,nums列表首先被克隆,但随后对其执行迭代操作,其中索引i上的值依赖于索引i-1(即需要执行顺序)。然后返回这个值。

这在函数式语言(Haskell、Lisp等)中是什么样子的?我一直在学习单子,并认为它们可能与这里有关,但我的理解仍然不是很好。

共有3个答案

山乐生
2023-03-14

这不是Haskell的答案,但您标记了问题lisp,所以这里是Racket中的一个答案:这完全是功能性的,并且表明这个问题不需要索引

这个函数做的是取一个数字流,并为它返回前缀流。这都是懒惰地完成的。

(define (prefix-stream s)
  (let ps-loop ([st s]
                [p 0])
    (if (stream-empty? st)
        empty-stream
        (let ([np (+ p (stream-first st))])
          (stream-cons np (ps-loop (stream-rest st) np))))))

所以现在

> (stream->list (prefix-stream (in-range 1 10)))
'(1 3 6 10 15 21 28 36 45)

但是当然你也可以这样做:

> (prefix-stream (in-naturals))
#<stream>

这显然不是一个可以转换为列表的流,但你可以查看其中的一部分:

(stream->list (stream-take (prefix-stream (in-naturals)) 10))
'(0 1 3 6 10 15 21 28 36 45)
> (stream->list (stream-take (stream-tail (prefix-stream (in-naturals)) 1000) 10))
'(500500 501501 502503 503506 504510 505515 506521 507528 508536 509545)

(请注意,in-naturals认为Naturals从0开始,这是正确的。)

曾奇略
2023-03-14

这不是一个索引繁重的操作,事实上,您可以使用一个带有scanl1::(a-

prefixList = scanl1 (+)

事实上,对于NUM列表,我们得到:

Prelude> prefixList [1 .. 9]
[1,3,6,10,15,21,28,36,45]

scanl1将原始列表中的第一项作为累加器的初始值,并生成该初始值。然后每次它都将累加器和给定列表中的下一项相加为新累加器,并生成新的累加器值。

通常不需要索引,但对列表进行枚举就足够了。命令式编程语言通常使用带有索引的for循环,但在许多情况下,这些循环可以被不考虑索引的foreach循环所取代。在Haskell中,这通常也有助于使算法更懒惰。

如果确实需要随机访问查找,可以使用数组向量包中定义的数据结构。

越安翔
2023-03-14

信不信由你,这个函数实际上是Haskell内置的。

> scanl (+) 0 [1..9]
[0,1,3,6,10,15,21,28,36,45]

因此,大致的答案通常是:有一些方便的与列表相关的原语,我们可以用它们来构建,而不是手工编写循环。人们喜欢说递归在FP中与命令式编程中的“for循环”最相似。虽然这可能是真的,但与命令式程序用于循环的平均递归相比,普通函数式程序使用的显式递归要少得多。我们所做的大部分工作(尤其是列表)都是由mapfilterfoldl,以及数据中所有其他(高度优化的)好东西组成的。列出数据。可折叠的数据。可遍历的,以及基础的其余部分

至于我们如何实现这些函数,您可以查看scanl的源代码。出于效率原因,GHC上的一个写得有点不同,但基本要点是

scanl :: (b -> a -> b) -> b -> [a] -> [b]
scanl _ q [] = [q]
scanl f a (b:xs) = a : scanl f (f a b) xs

您不需要索引。您需要在构建列表时跟踪单个前一值,我们可以用函数参数来实现这一点。如果您真的有一个算法需要随机访问各种索引,我们有Data. Vector。但是99%的情况下,答案是“停止思考索引”。

 类似资料:
  • 什么是纯函数式语言?什么是纯函数式数据结构?我知道什么是函数式语言,但我不知道“纯”是什么意思。有人知道吗?有人能给我解释一下吗?谢谢!

  • 主要内容:字符串连接函数 strcat(),字符串复制函数 strcpy(),字符串比较函数 strcmp()C语言提供了丰富的字符串处理函数,可以对字符串进行输入、输出、合并、修改、比较、转换、复制、搜索等操作,使用这些现成的函数可以大大减轻我们的编程负担。 用于输入输出的字符串函数,例如 、 、 、 等,使用时要包含头文件 ,而使用其它字符串函数要包含头文件 。 是一个专门用来处理字符串的头文件,它包含了很多字符串处理函数,由于篇幅限制,本节只能讲解几个常用的,有兴趣的读者请 猛击这里查阅所

  • 自然语言处理之序列模型 - 小象学院 解决 NLP 问题的一般思路 这个问题人类可以做好么? - 可以 -> 记录自己的思路 -> 设计流程让机器完成你的思路 - 很难 -> 尝试从计算机的角度来思考问题 NLP 的历史进程 规则系统 正则表达式/自动机 规则是固定的 搜索引擎 “豆瓣酱用英语怎么说?” 规则:“xx用英语怎么说?” => translate(XX, English)

  • 本文向大家介绍c语言基于stdarg.h的可变参数函数的用法,包括了c语言基于stdarg.h的可变参数函数的用法的使用技巧和注意事项,需要的朋友参考一下 C语言编程中有时会遇到一些参数个数可变的函数,本文详细讲解了可变参数函数的实现原理,分享给大家 在开始学习C语言的函数的时候,我们就知道函数的参数个数应该是在函数声明的时候就指定的,这一点我们没有任何疑问。但是不知道大家有没有注意到我们的pri

  • 问题内容: 这是我的情况: 我有一个包含用户列表的页面。我通过Web界面创建一个新用户,并将其保存到服务器。服务器在elasticsearch中为文档建立索引并成功返回。然后,我被重定向到不包含新用户的列表页面,因为它可能需要1秒钟的时间才能使文档在Elasticsearch中可供搜索 elasticsearch中的近实时搜索。 elasticsearch指南说您可以手动刷新索引,但说在生产中不要

  • 我有这样的场景,当点击一个按钮时,它打开了一个基于PDF文件的窗口: 我使用的是Gecko驱动程序版本-21.0Firefox版本-61.0.1 Selenium独立服务器-3.13 我无法切换到基于PDF文件的窗口获取错误: 我想用最新的壁虎驱动程序-21.0来处理它