我正在Haskell中开发一个函数,该函数将获取一个列表,并将其划分为两个大小相等的列表。这是我所拥有的:
split (x:y:xs) = split2 ([((length(x:y:xs) `div` 2)-2) : x ++ y] : [xs])
split2 (x:xs:[y:ys]) = split2 ((x-1) : [xs] ++ y : [ys])
split2 (0:xs:[y:ys]) = (xs:[y:ys])
该函数获取列表的前两个元素,将它们放在列表#2中,并将第一个列表作为第二个元素追加。然后,它得到列表的长度,并将其除以2,以计算运行次数,同时考虑到它已经从第一个列表中删除了两个元素。然后,它获取这两条信息并将其放入split2,split2从第一个列表中获取另一个元素,并将其附加到第一个元素中的第二个列表中,此外,它还从运行次数中倒计时1,然后再次运行。
问题是,当我运行它时,我会得到这个:
Functions.hs:19:49:
Occurs check: cannot construct the infinite type: t0 = [t0]
In the first argument of `(:)', namely `(y)'
19指的是第2行,即第一个split2函数。不完全确定如何修复此错误。有什么想法吗?
很难知道从哪里开始。。。
让我们从split2
中越来越大的表达式块中定义函数。
f1 (x:y:xs) = (length(x:y:xs) `div` 2)-2
f1 :: [a] -> Int
好的,所以这个参数是一些东西的列表,它返回一个Int
f2 (x:y:xs) = ((length(x:y:xs) `div` 2)-2) : x
f2 :: [[Int]] -> [Int]
这里,length
Int
与x
是一致的,所以x
必须是[Int]
,所以(x:y:xs)
必须是[[Int]]
。我们还可以推断出y
与x
具有相同的类型,并且xs
是相同类型的事物的列表;[[Int]]
。因此,x ++ y
也将是[Int]
。
因此,[xs]
将具有类型[[[Int]]]
。现在,我们将结果封装在列表构造函数中,并使用[xs]
:进行比较
f3 (x:y:xs) = [((length(x:y:xs) `div` 2)-2) : x ++ y] : [xs]
f3 :: [[Int]] -> [[[Int]]]
我猜你没想到这个论点是Int
s的列表列表。
现在,如果我们看一下split2
,参数模式(x:xs:[y:ys])
意味着它的类型是:
split2 :: [[a]] -> b
x :: [a]
xs :: [a]
y :: a
ys :: [a]
CCD_ 22的第一个定义的rhs试图通过连接CCD_ 23和CCD_。然而,如果我们将类型替换为y : [ys]
,我们会发现:
y : [ys] :: a : [[a]]
但由于(:) :: a -> [a] -> [a]
,这意味着[[a]]
必须是与[a]
相同的类型,或者a
必须是其自身的列表,这是不可能的。
(x-1)
的类型也很糟糕,因为它试图从列表中减去一个。
我不知道你是想把列表分成偶数和奇数元素,还是分成前半部分和后半部分。
这里有两个版本,分为前半部分和后半部分,如果长度为奇数,则向下取整(RD)或向上取整(RU):
splitRD xs = splitAt (length xs `div` 2) xs
splitRU xs = splitAt ((length xs + 1) `div` 2) xs
以下是一个将列表拆分为偶数和奇数元素的版本:
splitEO [] = ([], [])
splitEO [e] = ([e], [])
splitEO (e:o:xs) = (e:es, o:os) where (es, os) = splitEO xs
的一些建议
-
将类型写入所有函数。它使代码更可读,也有助于捕捉错误。
-
++
的类型是[a] -> [a] -> [a]
,您将添加列表的长度和元素。由于列表必须是统一类型,并且长度返回Int
类型,所以编译器将split
的类型推断为[[Int]] -> t
(假设split2返回类型t
)。 -
将
([((length(x:y:xs)
div2)-2) : x ++ y] : [xs])
传递给split2时。xs
是类型[[Int]]
,这意味着[xs]
属于[[[Int]]]
型,所以编译器将split2
的类型推断为[[[Int]]] -> t
。
现在在split2 的定义中
split2 (x:xs:[y:ys]) = split2 ((x-1) : [xs] ++ y : [ys])
ys
属于[[Int]]
型,因此y
属于[Int]
型。xs
的类型是[[Int]]
,但您正在执行[xs] ++ y
,这意味着[xs]
和y
应该是相同的类型(对于某些a
,[a]
)。
由于您还没有提供任何类型,编译器完全不知道如何推断出这样的类型。
如果你只是想把列表分成两个相等的部分,为什么不做一些更简单的事情,比如
split3 :: [a] -> ([a], [a])
split3 [] = ([],[])
split3 [x] = ([x],[])
split3 (x:y:xs) = let (xs',ys') = split3 xs in (x:xs',y:ys')
您似乎在列表中传递状态,而不是将状态作为值传递给函数,这在编译器看来会产生问题,就好像您在创建一个异构值列表,而Haskell中的列表应该是同构类型的。
代替
split2 (0:xs:[y:ys])
应该像这个一样,将不同的参数/值分别传递给函数
split2 n xs (y:ys)
您正在寻找的功能也在标准库函数中复制。
halveList xs = splitAt (length xs `div` 2) xs
在Haskell中,列表的元素必须都是相同的类型。在函数中,列表包含Ints、原始列表的元素和原始列表的子列表的混合物,所有这些可能都是不同的类型。
对于如何附加列表和元素,您也有一些困惑。x++y只能在x和y本身是列表时使用,而x:y只能在y是列表且x是列表的元素时使用;要生成一个包含x和y作为元素的新列表,请使用[x,y](尽管x:[y]也可以)。类似地,[xs]++y需要改为xs++[y]。
在不改变基本算法的情况下,最简单的解决方案可能是让split2取3个单独的参数。
split (x:y:xs) = split2 ((length(x:y:xs) `div` 2)-2) [x,y] xs
split2 n xs (y:ys) = split2 (n-1) (xs++[y]) ys
split2 0 xs ys = [xs,ys]