我正在编写一个以元组或三元组排序给定列表的函数。它应该按列表两个元素之间的数字排序。
例如,如果给定列表为[1,2,3,6,7]
,则应返回[[1,2,3], [6,7]]
因为在 1,2 和 2,3 之间以及在 6,7
这是我的代码:
import Data.List
check :: [Int] -> [[Int]]
check listCopy@(x:xs) =
let sorted = sort (listCopy)
in if (length sorted > 1)
then if ((sorted !! 1 ) - (sorted !! 0)) == 1 || ((sorted !! 1 ) - (sorted !! 0)) == 0
then [[x] ++ check(xs) !! 0]
else [[x]] ++ check(xs)
else [[x]]
check [] = [[]]
if ((sorted !! 1 ) - (sorted !! 0)) == 1 || ((sorted !! 1 ) - (sorted !! 0)) == 0
正在检查列表的两个元素之间是否有0个数字
如果以上语句为True,则[[x] ++ check(xs) !! 0]
将把该元素添加到列表中,并再次调用该函数,并采用它返回的第一个元素。例如:[1,2,3,6,7] -> [[1 ++ check [2,3,6,7]]] -> [[1,2 ++ check[3,6,7]]]
等等...
但是,if ((sorted !! 1 ) - (sorted !! 0)) == 1 || ((sorted !! 1 ) - (sorted !! 0)) == 0
为False时,else [[x]] ++ check(xs)
应该在列表内的列表内设置元素,然后再次调用函数,并在列表内创建另一个新列表。示例:[[1,2 ++ check[3,6,7]]]
->是6-3 == 0或1(False)返回[[1,2,3]] + check[6,7]
,其结果应为[[1,2,3],[6,7]]
呼叫check[1,2,3,6,7]
会返回[[1,2,3]]
。我没有错,
但是据我所知,[[1,2]] ++ [[3,4]]
应该会得到[[1,2], [3,4]]
,而这正是我在else [[x]] ++ check(xs)
中所做的事情,我的功能到此结束。我在哪里犯了错误,或者我做错了什么?
答案 0 :(得分:5)
这里的问题是,您仅追加了第一个子列表:
then [[x] ++ check(xs) !! 0]
因此,您进行了递归调用,将返回一个子列表列表,但是您“丢弃”了除第一个列表之外的所有列表,然后将第一个列表串联起来。其余子列表将被忽略。
您可以使用以下方法解决此问题:
then [[x] ++ check(xs) !! 0] ++ safeTail check
我们将safeTail
实现为:
safeTail :: [a] -> [a]
safeTail (x:xs) = xs
safeTail [] = []
或者,例如@melpomene says:
safeTail :: [a] -> [a]
safeTail = drop 1
后来发现我们只能使用tail
,但是使用上面的代码,很难看到。
但是实现不是很“ Haskellish”。您的代码使用了许多(!!)
和length
。由于(!!)
在 O(k)中与 k 一起运行,因此我们想要获得元素的索引,而length
在中运行> O(n)且列表的长度为 n ,这也将非常无效。
在对列表进行进一步处理之前先对其进行排序是有意义的。接下来,我们只需要查找当前元素x
,下一个元素n
和其余元素xs
,因此:
go :: (Ord n, Num n) => [n] -> [[n]]
go (x:n:xs) = ...
go other = other
在情况n <= x+1
中,我们知道两个数字之间的差为零或一个,因此在这种情况下,递归检查的 head (第一个元素)应该以{{1}}开头,因此我们可以这样写:
x
否则,我们可以只构建一个单例列表,然后再构建其余列表:
go :: (Ord n, Num n) => [n] -> [[n]]
go (x:n:xs) | n <= x+1 = (x:r) : rs
| otherwise = ...
where (r:rs) = go (n:xs)
go [x] = [[x]]
go [] = []
我们知道go :: (Ord n, Num n) => [n] -> [[n]]
go (x:n:xs) | n <= x+1 = (x:r) : rs
| otherwise = [x]:(r:rs)
where (r:rs) = go (n:xs)
go [x] = [[x]]
go [] = []
至少有一个元素,因为我们用一个元素递归调用列表,并且在列表为非空的所有情况下,我们都返回一个非空的列表。
通过使用 as-pattern ,我们可以使其更加优雅:
go (n:xs)
我们可以将上述内容概括为@chepner says,使其仅需要go :: (Ord n, Num n) => [n] -> [[n]]
go (x:na@(n:xs)) | n <= x+1 = (x:r) : rs
| otherwise = [x]: ra
where ra@(~(r:rs)) = go na
go [x] = [[x]]
go [] = []
和Eq a
:
Ord a
所以现在我们只需要用go :: (Ord n, Enum n) => [n] -> [[n]]
go (x:na@(n:xs)) | succ x >= n = (x:r) : rs
| otherwise = [x]: ra
where ra@(~(r:rs)) = go na
go [x] = [[x]]
go [] = []
来表达check
,就可以:
go
或者我们可以让import Data.List(sort)
check :: (Ord n, Enum n) => [n] -> [[n]]
check = go . sort
where go (x:na@(n:xs)) | succ x >= n = (x:r) : rs
| otherwise = [x]: ra
where ra@(~(r:rs)) = go na
go [x] = [[x]]
go [] = []
函数对check
类型进行操作:
(Eq n, Enum n)
答案 1 :(得分:2)
这是一个有趣的问题,这是一种应用风格的实验方法。是的,它看起来有些令人费解,但实际上非常简单。我唯一不喜欢的是last
函数的用法。也许我们可以找到一种方法以某种方式删除它。
splitConsequtives :: Integral a => [a] -> [[a]]
splitConsequtives xs = foldr id [[last xs]] $ zipWith f <*> tail $ xs
where f x y | y-x == 1 = (:) <$> (x:) . head <*> tail
| otherwise = ([x]:)
*Main> splitConsequtives [1,2,3,6,7]
[[1,2,3],[6,7]]
*Main> splitConsequtives [-1,2,3,6,8,9]
[[-1],[2,3],[6],[8,9]]
这个想法是将构造函数放在一个列表中,这些列表在通过折叠链接时最终将构成我们的整个结果列表。列表构造函数(:)
是正确的关联函数,这就是为什么我使用foldr
。
这一切都始于zipWith
,通过xs
函数(如tail
)将f
放入zipWith f xs (tail xs)
列表中。 f :: a -> a -> [[a]] -> [[a]]
函数是我们将应用程序用作结果列表元素的位置。
好..!现在,我们来关注f
函数,该函数以x
和y
作为参数。
y
在x
之后,则我们的[[a]] -> [[a]]
类型的函数
是(:) <$> (x:) . head <*> tail
,其中包含以下列表的列表:
数字然后将子列表放在head
,将x
附加到该子列表,
将所有内容放回原处。因此,如果我们的结果正在构建
(foldr
的累加器参数)为[[7], [9,10]]
和x
为
6
,我们将得到[[6,7],[9,10]]
。y
不是x
的后继,则我们的[[a]] -> [[a]]
类型
函数是([x]:)
。因此,如果我们的结果正在构建(
foldr
的累加器参数为[[6,7], [9,10]]
,x
为3
那么我们将得到[[3],[6,7],[9,10]]
。需要注意的一个有趣点是我们用于foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b
的迭代函数的类型。您期望它的类型为a -> b -> b
,但是id :: a -> a
看起来有所不同。这怎么可能..?我相信是因为a ~ [[a]] -> [[a]]
的{{1}}中的b ~ [[a]]
和(a -> b -> b)
。