【问题标题】:Does there exist something like (xs:x)是否存在类似 (xs:x)
【发布时间】:2011-04-28 22:43:55
【问题描述】:

我是 Haskell 的新手。我知道我可以通过这样做来创建reverse 函数:

reverse :: [a] -> [a]
reverse [] = []
reverse (x:xs) = (Main.reverse xs) ++ [x]

是否有(xs:x)(与元素连接的列表,即x是列表中的最后一个元素)这样的东西,以便我将最后一个列表元素放在列表的前面?

rotate :: [a] -> [a]
rotate [] = []
rotate (xs:x) = [x] ++ xs

当我尝试编译包含此函数的程序时出现以下错误:

Occurs check: cannot construct the infinite type: a = [a]
When generalising the type(s) for `rotate'

【问题讨论】:

  • 如果您需要列表的尾部,那么您使用了错误的数据结构来解决您的问题。考虑一个树、dlist、序列或其他结构。

标签: haskell


【解决方案1】:

我也是 Haskell 的新手,所以我的回答并不权威。无论如何,我会使用lastinit

Prelude> last [1..10] : init [1..10]
[10,1,2,3,4,5,6,7,8,9]

Prelude> [ last [1..10] ] ++ init [1..10]
[10,1,2,3,4,5,6,7,8,9]

【讨论】:

    【解决方案2】:

    简短的回答是:模式匹配无法做到这一点,您必须使用函数。

    长答案是:它不在标准 Haskell 中,但如果您愿意使用名为 View Patterns 的扩展,并且如果您的模式匹配最终花费比恒定时间更长的时间没有问题。

    原因是模式匹配首先基于结构的构造方式。列表是一种抽象类型,其结构如下:

    data List a = Empty | Cons a (List a)
               deriving (Show) -- this is just so you can print the List 
    

    当您声明这样的类型时,您会生成三个对象:一个类型构造函数List,以及两个数据构造函数:EmptyCons。类型构造函数接受类型并将它们转换为其他类型,即List 接受类型a 并创建另一个类型List a。数据构造函数的工作方式类似于返回 List a 类型的函数。在这种情况下,您有:

    Empty :: List a
    

    代表一个空列表和

    Cons :: a -> List a -> List a
    

    它接受a 类型的值和一个列表,并将该值附加到列表的头部,返回另一个列表。所以你可以像这样构建你的列表:

    empty = Empty         -- similar to []
    list1 = Cons 1 Empty  -- similar to 1:[] = [1]
    list2 = Cons 2 list1  -- similar to 2:(1:[]) = 2:[1] = [2,1]
    

    这或多或少是列表的工作方式,但是在 Empty 的位置上你有 []Cons 的位置上你有 (:)。当您键入 [1,2,3] 之类的内容时,这只是 1:2:3:[]Cons 1 (Cons 2 (Cons 3 Empty)) 的语法糖。

    当您进行模式匹配时,您正在“解构”该类型。了解类型的结构可以让您以独特的方式反汇编它。考虑函数:

    head :: List a -> a
    head (Empty) = error " the empty list have no head"
    head (Cons x xs) = x
    

    类型匹配发生的情况是数据构造函数与您提供的某些结构相匹配。如果它匹配Empty,那么你有一个空列表。如果 if 匹配 Const x xsx 必须具有类型 a 并且必须是列表的头部,xs 必须具有类型 List a 并且是列表的尾部,因为这是数据构造函数的类型:

    Cons  :: a -> List a -> List a
    

    如果Cons x xsList a 类型,则x 必须是a 并且xs 必须是List a。 (x:xs) 也是如此。如果您查看 GHCi 中 (:) 的类型:

    > :t (:) 
     (:) :: a -> [a] -> [a]
    

    因此,如果 (x:xs) 的类型为 [a],则 x 必须是 a 并且 xs 必须是 [a] 。当您尝试执行(xs:x) 然后将xs 视为列表时收到的错误消息正是因为这个原因。通过使用(:),编译器推断xs 具有a 类型,并且通过使用 ++,它推断xs必须是[a]。然后它吓坏了,因为没有有限类型a 用于哪个a = [a] - 这就是他试图通过该错误消息告诉你的内容。

    如果您需要以与数据构造器构建结构的方式不匹配的其他方式分解结构,则必须编写自己的函数。标准库中有两个函数可以满足您的需求:last 返回列表的最后一个元素,init 返回列表的最后一个元素。

    但请注意,模式匹配发生在恒定时间内。要找出列表的头部和尾部,无论列表有多长,您只需要查看最外层的数据构造函数即可。找到最后一个元素是O(N):您必须挖掘直到找到最里面的Cons 或最里面的(:),这需要您“剥离”结构N 次,其中N 是列表的大小。

    如果您经常需要在长列表中查找最后一个元素,您可能会考虑使用列表是否是一个好主意。你可以去Data.Sequence(恒定时间访问第一个和最后一个元素),Data.Maplog(N) 时间访问任何元素,如果你知道它的键),Data.Array(恒定时间访问一个元素,如果你知道它的索引)、Data.Vector 或其他比列表更符合您需求的数据结构。

    好的。那是简短的答案(:P)。很长的,您需要自己查找一下,但这里有一个介绍。

    通过使用视图模式,您可以使用非常接近模式匹配的语法来实现这一点。 View Patterns 是一个扩展,您可以通过将其作为代码的第一行来使用它:

    {-# Language ViewPatterns #-}
    

    使用说明在这里:http://hackage.haskell.org/trac/ghc/wiki/ViewPatterns

    使用视图模式,您可以执行以下操作:

    view :: [a] -> (a, [a])
    view xs = (last xs, init xs)
    
    someFunction :: [a] -> ...
    someFunction (view -> (x,xs)) = ...
    

    xxs 将绑定到您提供给 someFunction 的列表的 lastinit。从语法上讲,它感觉就像模式匹配,但实际上只是将lastinit 应用于给定列表。

    【讨论】:

      【解决方案3】:

      如果您愿意使用与普通列表不同的东西,您可以查看容器包中的 Seq 类型,如文档中的 here 所示。这有 O(1) 个 cons(前面的元素)和 snoc(后面的元素),并允许通过使用 Views 从前面和后面对元素进行模式匹配。

      【讨论】:

        【解决方案4】:

        “是否有 (xs:x) (与一个元素连接的列表,即 x 是列表中的最后一个元素)这样的东西,以便我将最后一个列表元素放在列表的前面?”

        不,不是你的意思。函数定义左侧的这些“模式”反映了程序员如何定义数据结构并存储在内存中。 Haskell 的内置列表实现是一个单链表,从头开始排序 - 因此可用于函数定义的模式正好反映了这一点,暴露了第一个元素加上列表的其余部分(或者,空列表)。

        对于以这种方式构造的列表,最后一个元素不能立即作为列表最顶层节点的存储组件之一使用。因此,该值不是出现在函数定义左侧的模式中,而是由右侧的函数体计算。

        当然,您可以定义新的数据结构,因此如果您想要一个新列表,通过模式匹配使最后一个元素可用,您可以构建它。但是有一些成本:也许您只是将列表向后存储,因此它现在是模式匹配不可用的第一个元素,并且需要计算。也许您将第一个值和最后一个值都存储在结构中,这将需要额外的存储空间和簿记。

        考虑单个数据结构概念的多种实现是完全合理的——稍微向前看,这是 Haskell 的类/实例定义的一种用法。

        【讨论】:

          【解决方案5】:

          按照您的建议倒车可能效率低得多。最后不是 O(1) 操作,而是 O(N),这意味着按照您的建议旋转变成 O(N^2) 算法。

          来源: http://www.haskell.org/ghc/docs/6.12.2/html/libraries/base-4.2.0.1/src/GHC-List.html#last

          你的第一个版本有 O(n) 复杂度。其实不是,因为 ++ 也是 O(N) 操作

          你应该这样做

          rotate l =  rev l []
             where
                rev []     a = a
                rev (x:xs) a = rev xs (x:a)
          

          来源:http://www.haskell.org/ghc/docs/6.12.2/html/libraries/base-4.2.0.1/src/GHC-List.html#reverse

          【讨论】:

          • 您的rotate 不是轮换。
          【解决方案6】:

          在您的后一个示例中,x 实际上是一个列表。 [x] 成为列表列表,例如[[1,2], [3,4]].

          (++) 想要两边都具有相同类型的列表。当你使用它时,你正在做[[a]] ++ [a] 这就是编译器抱怨的原因。根据您的代码a[a] 的类型相同,这是不可能的。

          (x:xs) 中,x 是列表的第一项(头部),xs 是除了头部(即尾部)之外的所有内容。名字在这里无关紧要,你不妨叫他们(head:tail)

          如果您真的想取出输入列表的最后一项并将其放在结果列表的前面,您可以执行以下操作:

          rotate :: [a] -> [a]
          rotate [] = []
          rotate lst = (last lst):(rotate $ init lst)
          

          注意我根本没有测试过这段代码,因为我目前没有可用的 Haskell 环境。

          【讨论】:

          • (last lst):(rotate lst) 变得越来越长,因为您没有删除最后一个元素。 ideone.com/ix6yb
          • 您可以在该页面上自己尝试。只需点击克隆,编辑源代码并点击提交。顺便说一句,是的,这修复了错误。
          • 注意:您可以通过省略虚假括号来简化最后一行 rotate lst = last lst : rotate (init lst)
          猜你喜欢
          • 2016-02-24
          • 1970-01-01
          • 1970-01-01
          • 2010-12-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-03-28
          相关资源
          最近更新 更多