【问题标题】:Haskell: accessing lists from backHaskell:从后面访问列表
【发布时间】:2011-01-22 17:18:56
【问题描述】:

今天我开始学习 Haskell。我是函数式语言的新手,我非常喜欢 Haskell。

但是我有一个关于它的设计的问题,这让我很烦恼:从我目前的理解来看,访问列表后面的元素看起来比访问前面的元素要复杂得多(一些比如xs:x,其中xs::[a]x::a 似乎是不可能的)。

(据我了解)可以将列表附加到另一个列表(xs++[a]),但是在运行时会花费更多(它需要遍历整个列表)并且不能用于模式匹配.

为什么 Haskell 缺少这样的操作?

【问题讨论】:

    标签: list haskell append


    【解决方案1】:

    列表数据类型

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

    定义如上。您只能匹配两种模式:[]x : xs,其中 x 是头部,xs 是尾部。

    添加到列表

    a = 1 : 2 : []
    b = 0 : a
    
    (:)

    只需添加一个新的 cons 单元格并将原始列表重新用作尾部。

    但是,请记住,Haskell 数据结构是不可变的。追加到列表的尾部

    a = 1 : 2 : []
    b = a ++ [3]
    
    (:)

    需要建立一个全新的列表,因为原始结构的任何部分都不能重复使用。

    其实考虑

    a = 0 : a
    b = 0 : [ x+1 | x <- b ]
    
    (:)

    您将如何获取列表的最后一个元素,或追加到末尾?

    还有其他数据结构,例如dequeues,它们更适合基于前端和后端的访问。

    【讨论】:

    • 对!我不认为列表必须是不可变的,因此它们只能在前面或后面进行扩展,但不能在两侧进行。谢谢!
    【解决方案2】:

    Haskell 中的列表数据类型是链表,因此查找使用 O(n) 时间。如果您需要经常访问列表的背面,您可能需要查看Data.Sequence 有 O(1) 添加到开头和结尾。

    要回答为什么 Haskell 使用这种数据结构作为“标准容器”(如 C 和数组),这是因为 Haskell 是一种纯函数式语言,因此偏爱纯函数式数据结构(不可变和持久)。进一步阅读请看this wiki page。要在 Haskell 中使用非功能性数据结构,需要它位于 IOST monad 中。

    【讨论】:

    • 你的第二句话就是我要找的。连同@ehemient 的回答,这让我明白了为什么haskell 列表是这样设计的。谢谢! :-)
    • 我不确定 Data.Sequence 与双向链表具有相似的复杂性是否正确,因为 Data.Sequence 在添加元素时必须做一些平衡工作。 JoinList 有时称为 Monoidial 列表,不做任何平衡,因此应该更有效地构建。不过,不要在 Hackage 上使用 JoinList,它写得非常糟糕,需要更新(我是作者)——实际上,我希望 Data.Sequence 即使在 JoinList 应该获胜的事情上也能表现得更好。跨度>
    • @Stephen,我同意,所以我将其更改为更具体地说明我们在这里感兴趣的运行时间。当我说它类似于双向链表时,我不知道我在想什么,因为显然根本不是这样!
    【解决方案3】:

    这是纯 List 数据结构的问题,而不是 haskell 本身的问题。您可以阅读Purely Functional Data Structures 论文以了解更多关于其他纯数据结构的信息,它们可以在此类操作上具有更好的性能

    【讨论】:

    • 视情况而定。列表是一种 内置 类型,由 Haskell 语言本身描述,不是吗?如果是这样,haskell 列表取决于语言设计。
    • @peoro 你一直在指出这一点,但你被告知的基本上是“你使用了错误的工具”,Haskell 有工具可以满足你的需求,但你似乎并不感兴趣。类似的抱怨是“C 数组不能做 O(1) 缺点”和“Python '列表'不是 O(log(n)) 查找”。显然,这些抱怨是可疑的,因为只是开发人员滥用了特定的数据结构。
    • @TomMD,我不会“一直指出这一点”,我只评论过一次(对你和这个答案)。我同意,如果在 C 中我想要一个列表,那么数组不适合我的目的。对于我的问题(以及这些 cmets),我并不是说 Haskell 列表不好(就像 C 数组也不错),我并不是在抱怨它!我只是想知道为什么它们是这样设计的;它们是 Haskell 语言的一部分:因此列表设计取决于 Haskell 的设计。总而言之,我不需要你和 Yuras 建议的不同列表,我只是想知道为什么内置列表会这样工作。
    【解决方案4】:

    不要害怕在需要时反转()您的列表。在将列表提供给递归函数之前反转列表或反转 fold() 的最终结果并不罕见。

    【讨论】:

      【解决方案5】:

      这些不是语言的问题,只是 List 数据类型,它有一些特殊的语法,但在其他方面并不完全是“Haskell 的组成部分”。您在 C 中也有与单链表相同的问题,但这显然不是 C 编程语言的问题。

      如果你想要一个带有尾指针的双向链表,那么就创建一个这样的数据类型并使用它!您可能想了解更多数据类型(参见containers 包、vectordlist 包的示例)。

      【讨论】:

      • 列表是一种内置类型,由 Haskell 语言本身描述,不是吗?如果 Haskell 中的列表按原样定义,那是因为语言设计,就像 C 内置类型由语言定义一样。 C 中的列表不属于该语言,就像非本地 Haskell 数据类型不属于 Haskell 语言一样。
      • @peoro List 的内置程度纯粹是句法。想象一下如果列表被定义为data List a = Nul | Cons a (List a)。它的内置程度与您的问题正交 - 如果您希望 O(1) 访问尾部,则使用跟踪尾部的数据结构。
      • 今天我学会了如何声明新类型,明白了你告诉我的:-)
      猜你喜欢
      • 1970-01-01
      • 2014-06-30
      • 1970-01-01
      • 1970-01-01
      • 2021-06-07
      • 2017-05-12
      • 2018-05-07
      • 2018-05-24
      • 1970-01-01
      相关资源
      最近更新 更多