我是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'
答案 0 :(得分:11)
我也是Haskell的新手,所以我的答案不具有权威性。无论如何,我会使用last
和init
:
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]
答案 1 :(得分:10)
简短的回答是:模式匹配不可能,你必须使用一个函数。
答案很长:它不是标准的Haskell,但是如果你愿意使用名为View Patterns的扩展,并且如果你的模式匹配没有问题,最终花费的时间超过了常数时间。
原因是模式匹配基于结构的构建方式。列表是抽象类型,具有以下结构:
data List a = Empty | Cons a (List a)
deriving (Show) -- this is just so you can print the List
当您声明类似的类型时,您会生成三个对象:类型构造函数List
和两个数据构造函数:Empty
和Cons
。类型构造函数接受类型并将它们转换为其他类型,即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
匹配,则表示您没有空列表。如果匹配Const x xs
,那么x
必须具有a
类型,并且必须是列表的头部,并且xs
必须具有类型List a
,并且是Cons :: a -> List a -> List a
的尾部list,导致这是数据构造函数的类型:
Cons x xs
如果List a
类型为x
,则a
必须为xs
且List a
必须为> :t (:)
(:) :: a -> [a] -> [a]
。 (x:xs)也是如此。如果你在GHCi中查看(:)的类型:
[a]
因此,如果(x:xs)的类型为x
,则a
必须为xs
且[a]
必须为(xs:x)
。您尝试执行xs
然后将(:)
视为列表时获得的错误消息正是因为这一点。通过使用xs
编译器推断a
具有类型++
,并使用
xs
,它推断[a]
必须是a
。然后它就吓坏了因为没有a = [a]
的有限类型last
- 这就是他试图用错误信息告诉你的。
如果您需要以与数据构造函数构建结构的方式不匹配的其他方式反汇编结构,那么您必须编写自己的函数。标准库中有两个函数可以执行您想要的操作:init
返回列表的最后一个元素,O(N)
返回列表中所有但最后的元素。
但请注意,模式匹配会在恒定时间内发生。要找出列表的头部和尾部,列表的长度无关紧要,您只需要查看最外层的数据构造函数。找到最后一个元素是Cons
:你必须挖掘直到找到最里面的(:)
或最里面的Data.Sequence
,这需要你“剥离”结构N次,其中N是列表的大小。
如果您经常需要查找长列表中的最后一个元素,您可能会考虑使用列表是否是一个好主意。你可以追踪Data.Map
(对第一个和最后一个元素的恒定时间访问),log(N)
(Data.Array
时间访问任何元素,如果你知道它的密钥),Data.Vector
(常量)对元素进行时间访问(如果您知道其索引),{-# Language ViewPatterns #-}
或其他与列表匹配的需求的数据结构。
确定。这是简短的答案(:P)。你必须自己查找一个长的,但这是一个介绍。
通过使用视图模式,您可以使用非常接近模式匹配的语法。视图模式是一种扩展,您可以将其作为代码的第一行使用:
view :: [a] -> (a, [a])
view xs = (last xs, init xs)
someFunction :: [a] -> ...
someFunction (view -> (x,xs)) = ...
如何使用它的说明如下:http://hackage.haskell.org/trac/ghc/wiki/ViewPatterns
使用视图模式,您可以执行以下操作:
x
比xs
和last
绑定到您init
列表的someFunction
和last
。从语法上来说,它感觉就像模式匹配,但实际上只是将init
和{{1}}应用于给定列表。
答案 2 :(得分:7)
如果您愿意使用与普通列表不同的内容,您可以查看容器包中的Seq
类型,如文档here所示。它有O(1)缺点(前面的元素)和snoc(后面的元素),并允许通过使用视图来匹配元素从正面和背面。
答案 3 :(得分:4)
“是否存在(xs:x)(与元素连接的列表,即x是列表中的最后一个元素),以便将最后一个列表元素放在列表的前面?”< / p>
不,不是你的意思。函数定义左侧的这些“模式”反映了程序员如何定义数据结构并将其存储在内存中。 Haskell的内置列表实现是一个单链接列表,从头开始排序 - 因此可用于函数定义的模式恰好反映了这一点,暴露了第一个元素加上列表的其余部分(或者空列表)。 / p>
对于以这种方式构造的列表,最后一个元素不能立即作为列表最顶层节点的存储组件之一。因此,在函数定义左侧的模式中不存在该值,而是由右侧的函数体计算。
当然,您可以定义新的数据结构,因此如果您想要一个新的列表,通过模式匹配使最后一个元素可用,您可以构建它。但是有一些成本:也许你只是向后存储列表,所以它现在是模式匹配不可用的第一个元素,需要计算。也许你要在结构中存储第一个和最后一个值,这需要额外的存储空间和簿记。
考虑单个数据结构概念的多个实现是完全合理的 - 稍微向前看,这是Haskell的类/实例定义的一种用法。
答案 4 :(得分:1)
按照您的建议进行逆转可能效率低得多。最后不是O(1)操作,而是O(N),这意味着按照你的建议旋转成为O(N ^ 2)alghorhim。
来源: 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
答案 5 :(得分:0)
在后一个示例中,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)
N.B。我根本没有测试过这段代码,因为我目前没有可用的Haskell环境。