Haskell:列表、数组、向量、序列

2023-12-10

我正在学习 Haskell 并阅读了几篇有关 Haskell 列表和(插入您的语言)数组的性能差异的文章。

作为一名学习者,我显然只是使用列表,甚至没有考虑性能差异。 我最近开始调查并发现 Haskell 中有许多可用的数据结构库。

有人可以在不深入了解数据结构的计算机科学理论的情况下解释列表、数组、向量、序列之间的区别吗?

另外,是否存在一些常见模式,您可以使用一种数据结构而不是另一种数据结构?

是否还有我缺少的但可能有用的其他形式的数据结构?


列出摇滚

到目前为止,Haskell 中顺序数据最友好的数据结构是列表

 data [a] = a:[a] | []

列表为您提供 ϴ (1) 缺点和模式匹配。标准库,以及就此而言的前奏,充满了有用的列表函数,这些函数应该会乱七八糟你的代码(foldr, map, filter)。列表是执着的,又名纯功能性,这非常好。 Haskell 列表并不是真正的“列表”,因为它们是共归纳的(其他语言称这些流),所以像

ones :: [Integer]
ones = 1:ones

twos = map (+1) ones

tenTwos = take 10 twos

工作出色。无限的数据结构摇滚。

Haskell 中的列表提供了一个非常类似于命令式语言中的迭代器的接口(因为惰性)。因此,它们被广泛使用是有道理的。

另一方面

列表的第一个问题是索引它们(!!)需要 ϴ (k) 时间,这很烦人。另外,追加可能会很慢++,但 Haskell 的惰性求值模型意味着,如果这些发生的话,可以将其视为完全摊销。

列表的第二个问题是它们的数据局部性较差。当内存中的对象没有彼此相邻放置时,实际处理器会产生高常数。所以,在 C++ 中std::vector比我所知道的任何纯链表数据结构具有更快的“snoc”(将对象放在末尾),尽管这不是持久数据结构,因此不如 Haskell 列表友好。

列表的第三个问题是空间效率较差。一堆额外的指针会增加你的存储空间(按一个常数因子)。

序列具有功能性

Data.Sequence内部基于手指树(我知道,你不想知道这一点)这意味着它们有一些不错的特性

  1. 纯功能性。Data.Sequence是一个完全持久的数据结构。

  2. 该死的快速访问树的开头和结尾。 ϴ (1)(摊销)获取第一个或最后一个元素,或追加树。在事物列表最快的地方,Data.Sequence至多是一个恒定的慢速。

  3. ϴ (log n) 访问序列的中间部分。这包括插入值以生成新序列

  4. 高品质API

另一方面,Data.Sequence对于数据局部性问题没有多大作用,并且仅适用于有限集合(它比列表更懒)

数组不适合胆小的人

数组是 CS 中最重要的数据结构之一,但它们不太适合惰性纯函数世界。数组提供了对集合中间的 ϴ (1) 访问以及非常好的数据局部性/常数因子。但是,由于它们不太适合 Haskell,所以使用起来很痛苦。当前标准库中实际上有多种不同的数组类型。其中包括完全持久化的数组、IO monad 的可变数组、ST monad 的可变数组以及上述的未装箱版本。欲了解更多请查看哈斯克尔维基

矢量是一个“更好”的数组

The Data.Vector包以更高级别和更简洁的 API 提供了数组的所有优点。除非您真的知道自己在做什么,否则如果您需要类似数组的性能,则应该使用它们。当然,一些警告仍然适用——可变的类似数组的数据结构在纯惰性语言中表现不佳。尽管如此,有时你还是想要 O(1) 的性能,并且Data.Vector将其以可用的包装形式提供给您。

你还有其他选择

如果您只想要能够在末尾有效插入的列表,您可以使用差异清单。列表搞砸性能的最好例子往往来自[Char]前奏曲被别名为String. Char列表很方便,但运行速度往往比 C 字符串慢 20 倍,所以请随意使用Data.Text或非常快Data.ByteString。我确信我现在还没有想到其他面向序列的库。

结论

90+% 的情况下,我需要 Haskell 列表中的顺序集合是正确的数据结构。列表就像迭代器,使用列表的函数可以轻松地与任何其他数据结构一起使用,使用toList他们自带的功能。在一个更好的世界中,前奏将完全参数化它使用的容器类型,但目前[]乱扔标准库。因此,(几乎)在任何地方使用列表绝对是可以的。
您可以获得大多数列表函数的完全参数化版本(并且使用它们是高尚的)

Prelude.map                --->  Prelude.fmap (works for every Functor)
Prelude.foldr/foldl/etc    --->  Data.Foldable.foldr/foldl/etc
Prelude.sequence           --->  Data.Traversable.sequence
etc

实际上,Data.Traversable定义了一个在任何“类似列表”中或多或少通用的 API。

尽管如此,尽管你可以很好地只编写完全参数化的代码,但我们大多数人都不是,并且到处都使用列表。如果你正在学习,我强烈建议你也学习。


根据评论,我意识到我从未解释过何时使用Data.Vector vs Data.Sequence。数组和向量提供极快的索引和切片操作,但本质上是瞬态(命令式)数据结构。纯函数式数据结构,例如Data.Sequence and []让高效生产new旧值的值,就像您修改了旧值一样。

newList oldList = 7 : drop 5 oldList

不修改旧列表,也不必复制它。所以即使oldList长得不可思议,这个“修改”会非常快。相似地

newSequence newValue oldSequence = Sequence.update 3000 newValue oldSequence 

将产生一个新序列newValue代替其 3000 个元素。同样,它不会破坏旧的序列,它只是创建一个新的序列。但是,它的效率非常高,采用 O (log (min (k, k-n)),其中 n 是序列的长度,k 是您修改的索引。

你不能轻易做到这一点Vectors and Arrays。他们可以modified但这是真正的命令式修改,因此无法在常规 Haskell 代码中完成。这意味着运营Vector进行修改的包,例如snoc and cons必须复制整个向量,所以需要O(n)时间。唯一的例外是您可以使用可变版本(Vector.Mutable) 在 - 的里面ST单子(或IO)并像使用命令式语言一样进行所有修改。完成后,您可以“冻结”向量,将其转换为您想要与纯代码一起使用的不可变结构。

我的感觉是你应该默认使用Data.Sequence如果列表不合适。使用Data.Vector仅当您的使用模式不涉及进行大量修改,或者您需要 ST/IO monad 内的极高性能时。

如果这一切都在谈论STmonad 让你感到困惑:更有理由坚持纯粹、快速和美丽Data.Sequence.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Haskell:列表、数组、向量、序列 的相关文章

  • 在 Haskell 命令行应用程序中提示输入密码

    以下 Haskell 程序提示用户在终端中输入密码 如果输入正确则继续 main do putStrLn Password password lt getLine case hash password member database of
  • Haskell 和 Idris 之间的区别:类型宇宙中运行时/编译时的反映

    因此 在 Idris 中 编写以下内容是完全有效的 item b Bool gt if b then Nat else List Nat item True 42 item False 1 2 3 cf https www youtube
  • 简单的秒差距示例会产生类型错误

    我正在尝试编译这个简单的秒差距代码 import Text Parsec simple letter 但我不断收到此错误 No instance for Stream s0 m0 Char arising from a use of let
  • 如何使用 Haskell 提交 html 表单

    我知道如何使用http 管道 http hackage haskell org package http conduit 2 1 0包的 simplehttp 从 URL 检索页面 现在如果那样的话怎么办 网页有一个输入文本字段和一个提交按
  • Haskell 中实例声明的参数顺序切换

    我想进行实例声明 但自由类型变量不是最后一个变量 例如 我有一个类声明 class Poppable m where tryPop m a gt Maybe a m a 现在我想让 Q PSQ 优先级队列 成为 Poppable 的实例 具
  • Haskell - 翻转具有两个参数的类型类的参数

    我有一个多参数类型类 它提供了一个可以交换其参数的函数 class Swappable a b where swappable a gt b gt Bool So if a and b form Swappable a b then b a
  • 数据类型变体之间的转换

    假设我想创建一种数据类型的两种变体 一种具有特定的构造函数 另一种没有它 否则它们是相同的 我想出了这个 LANGUAGE KindSignatures LANGUAGE DataKinds LANGUAGE GADTs data Foo
  • 为什么 Haskell (Hugs) 中的 Show 实例会导致堆栈溢出错误?

    下面是 Haskell 中的多态数据类型 由 Hugs 解释 我正在尝试创建一个 Show for Equality 的实例 实例声明表示 如果类型 a 在 Show 中 则相等 a 在 Show 中 它应该以 a b 的形式打印构造函数
  • 作为应用函子(Haskell / LYAH)

    第11章向你学习 Haskell引入以下定义 instance Applicative gt r where pure x gt x f lt gt g x gt f x g x 在这里 作者进行了一些不寻常的挥手 的实例实现有点神秘 所以
  • 有 Haskell 日期库吗?

    Haskell 中是否有一个函数允许我输入日期的组成部分 如字符串表示形式或日月年组成部分 我可以从中获取信息 如星期几 一个月中的天等 我在网上查了一下 看起来有很多自定义库 但我希望 ghci 10 6 4 的标准前奏库中有一个没有很好
  • 系统地将函数应用于 haskell 记录的所有字段

    我有一条包含不同类型字段的记录 以及一个适用于所有这些类型的函数 举一个小 愚蠢 的例子 data Rec Rec flnum Float intnum Int deriving Show 比如说 我想定义一个为每个字段添加两条记录的函数
  • 如何计算函数被调用的次数,FP方式

    我目前正在通过SICP http mitpress mit edu sicp 与哈斯克尔 练习 1 15 询问一个函数被调用了多少次 这个想法可能是您应该使用替换方法 但我想知道如何在代码中执行此操作 在命令式语言中 我们可以保留一个全局变
  • 了解函数类型

    我在尝试理解 Haskell 如何确定函数类型时感到有点困惑 这是一个例子 boolFcn x y x 3 y 4 当我检查上述函数的类型时 它给出了结果 Num a1 Num a Eq a1 Eq a gt a gt a1 gt Bool
  • 对参数进行排序以利用柯里化

    我最近两次重构代码以更改参数的顺序 因为代码太多 黑客喜欢flip or x gt foo bar x 42正在发生 在设计函数签名时 哪些原则可以帮助我充分利用柯里化 对于轻松支持柯里化和部分应用的语言 有一系列令人信服的论点 最初来自
  • 为连续可测量的现象创建行为

    我想创建一个Behavior t a从一个IO a 其预期语义是每次行为发生时都会运行 IO 操作sampled language FlexibleContexts import Reflex Dom import Control Mona
  • 关注点分离:什么时候最好将语义与语法分离?

    Choices 类型类的出色之处在于它们允许我们将额外的结构连接到现有类型 从而使我们能够推迟一些设计决策 而不是在构思时匆忙做出决定 另一方面 例如 在面向对象编程中 我们被迫考虑类型需要立即执行什么操作 以及稍后出现的或需要的任何附加结
  • 为什么阴谋集团重新安装“总是危险的”?

    使用 Cabal 重新安装软件包时 通常会看到以下警告 警告 请注意 重新安装总是很危险的 无论如何继续 此消息背后的一些原因是什么 目前 重新安装软件包意味着破坏性地覆盖已安装的软件包 如果旧包对系统有任何反向依赖性 它们将不再工作 为了
  • 如何编写将布尔值返回到一个函数的函数

    我在这里发现了一个类似的问题 它问了几乎相同的问题 但又不完全一样 我的问题是如何将 a gt Bool 类型的函数列表组合成一个也是 a gt Bool 的函数 Ex compose a gt Bool gt a gt Bool comp
  • Haskell 中的纯函数是否有可能改变变量的本地副本?

    Haskell 中的纯函数是否有可能改变变量的本地副本 就像 clojure 中提到的那样函数式编程是一个骗局 http swannodette github io 2013 06 10 porting notchs minecraft d
  • 如何在 Haskell 中处理这个简单的 IO 异常

    全部处理 在下面的代码中 getDirectoryContents dir可能会失败 例如dir可能不存在 如何捕获这个并向用户抛出有意义的消息 我知道 IO 异常处理已经被问过很多次了 但我仍然找不到一个简单的方法来做到这一点 walk

随机推荐

  • Laravel 5 中如何允许应用程序使用用户名或手机号码登录用户?

    我在 Laravel 5 应用程序中使用默认的 Eloquent 身份验证驱动程序 我想允许用户使用用户名或手机号码登录 对于那些输入这两个的人也是如此 有没有什么方法可以在不创建手动身份验证的情况下包含它 我在 laracasts 上发现
  • java StAX - StartDocument 的独立属性

    我想读取 操作和写入 xml 文件 我想从读写开始 然后再进行操作 我使用 StAX Parser 并想使用 EventReader 和 EventWriter 当我想要读取和写入 StartDocument 元素时 我遇到了第一个问题 I
  • Matlab 使用什么算法动态调整向量和矩阵的大小?

    运行这段代码 n 5 x zeros n 1 for ix 1 10 x ix rand disp getfield whos x bytes end 输出这个 40 40 40 40 40 48 56 64 72 80 这似乎表明 当 M
  • 在XML标签中表示空格和制表符?

    如何在 XML 标记中表示空格和制表符 有什么特殊字符可以代表它们吗 我认为您可以直接在 XML 文档中使用实际的空格或制表符 但如果您正在寻找特殊字符来表示它们 以便文本处理器不会弄乱它们 那么它是 space 032 tab 009
  • C# 或 .NET 中的程序集到底是什么?

    您能否解释一下 C 或 NET 中的程序集是什么 它从哪里开始又在哪里结束 关于装配我应该了解哪些重要信息 程序集是代码的编译输出 通常是 DLL 但 EXE 也是程序集 它是任何 NET 项目的最小部署单元 该程序集通常包含 MSIL M
  • 序列化继承自 List 的对象

    当我尝试序列化此集合时 名称属性未序列化 public class BCollection
  • 生成PDF时如何添加外部CSS?

    目前我正在使用以下代码在 JSP 文件中生成 PDF response setContentType application force download response setHeader Content Disposition att
  • 如何使用nodejs获取mongodb的slave状态?

    我想使用节点js获取mongodb服务器的slavestatus 这是代码 var Db require mongodb Db Server require mongodb Server var db new Db admin new Se
  • Azure 中的 VM 映像和快照有什么区别?

    我已经阅读了 Azure Docs 中的多个文档 但是 没有得到它们在实现 目的等方面的确切区别 需要我可以实现这一点的场景 建议之一 先感谢您 如上所述https github com MicrosoftDocs azure docs i
  • wso2 中的 xml 到 json 转换

    当我尝试使用 wso2 中的 XSLT 中介器将 XML 转换为 Json 时 我收到 有效负载无法写为 JSON 错误 谁能帮我解决这个问题 提前致谢 Answer recommended by WSO2 Collective 为什么不使
  • 启用 Windows Aero 主题时如何在标题栏上绘制位图图标

    我正在开发一个 MFC 应用程序 DWM 库不可用 我想在标题栏上绘制一个用作按钮的位图 但是 在 Windows 7 中启用 Aero 主题时 位图不会显示 禁用 Aero 主题时没有问题 但我的应用程序仍然可以通过单击位图的位置进行反应
  • MS Access 2010 中的“查询太复杂”异常

    以下查询生成异常 我怎样才能简化它 UPDATE Word SET CorrectnessCount CorrectnessCount WHERE GroupNo GroupNo AND Name Adduce OR Name Assuag
  • Django - 在管理 list_display 函数中包含来自外键的数据

    我有两个模型和一个管理模型 class Person models Model firstname models CharField maxlength 50 surname models CharField maxlength 50 cl
  • 如何从 Phonegap 打开 Google Play 商店

    我想从我的phonegap应用程序打开google play商店来安装另一个通知应用程序 怎样才能做到这一点呢 我想 ios 通过 URL 导航方案很容易 但google并不支持所有的url导航方案 我查了一下只有 Twitter 可以使用
  • browser.cookies.getAll() 总是不返回任何内容 - Firefox 扩展

    我一直在尝试列出我的扩展中的所有浏览器 cookiebrowser cookies getAll 起初 我以为是权限问题 但我的权限似乎设置正确 这是我的代码 清单 json manifest version 2 name CookieEx
  • 如何更新/重新加载 DataGridView BindingSource?

    我是 C Windows 窗体和 datagridviews 的新手 我有一个选项卡式表单 选项卡 1 显示练习表的数据网格视图 选项卡 2 用于向表中添加新练习 练习表通过 test ExercisesDataSet vwexercise
  • 将解决方案文件夹添加到 Visual Studio 项目模板

    是否可以使用项目模板添加解决方案文件夹 如果它不是内置功能 是否可以为此创建自定义任务 这有点棘手 您无法使用简单的项目模板来做到这一点 项目模板只能在单个项目级别上运行 要实现更高级的逻辑 您需要实现向导扩展并在其中注册 vstempla
  • 标题大小写是一个包含一个或多个姓氏的字符串,同时处理带有撇号的姓名

    我想标准化用户提供的字符串 我希望姓名的第一个字母大写 如果他们输入了两个姓氏 则将名字和第二个名字大写 例如 如果有人输入 marriedname maidenname 它会将其转换为Marriedname Maidenname如果有两个
  • 关于根据年份生成年龄变量的思考

    多年来我一直试图创建一个虚拟变量 目前 我的数据有每个观察的出生日期和程序开始日期 我已经能够创建一个以天为单位测量个人年龄的变量 但我实际上正在寻找的是一个变量 age join date 它告诉我以下内容 Individual birt
  • Haskell:列表、数组、向量、序列

    我正在学习 Haskell 并阅读了几篇有关 Haskell 列表和 插入您的语言 数组的性能差异的文章 作为一名学习者 我显然只是使用列表 甚至没有考虑性能差异 我最近开始调查并发现 Haskell 中有许多可用的数据结构库 有人可以在不