我有一个函数,它将获取一个列表并返回指定长度的子列表的列表:
*Main> neighbors 3 [1,2,3,4]
[[1,2,3],[2,3,4]]
neighbors :: Int -> [a] -> [[a]]
neighbors n xs =
let ys = take n xs
zs = drop 1 xs
in if (length ys < n)
then []
else ys : neighbors n zs
虽然它可以工作,但对我来说看起来并不特别“ Haskell-y”,因此我认为可能会有模式匹配来完成相同的事情。在那儿?或者,也许我正在错过一种不同的默认方法?我研究了各种拆分库,https://wiki.haskell.org/Let_vs._Where为我提供了使该功能完全起作用的工具。
答案 0 :(得分:3)
由于可以在运行时传递值,所以此时编写模式将很奇怪:通常将模式转换为某些程序逻辑。除非您编写某种首先生成这种模式的解释程序,否则实际上不可能基于 value 构造模式。
但是我们确实可以改善程序,例如:
neighbors :: Int -> [a] -> [[a]]
neighbors n l = go l (drop (n-1) l)
where go l@(_:lt) (_:xs) = take n l : go lt xs
go _ _ = []
工作原理如下,我们定义了一个辅助函数go
,该函数在 same 列表上进行迭代,但是在两个位置:第二个列表是 n-1 在第一个位置之前。如果仍有足够的元素来执行take
,则此“ 检查”。因此,我们将go
和l
称为drop (n-1) l
。
如果第二个列表为非空,则我们知道存在take n
的“最后”项(并且该位置位于n-1
的前面)。因此,在这种情况下,我们返回take n l
,并且前进两个指针。从第二个列表用尽的那一刻起,我们知道切片不再可行,因此我们停止发射元素。
优点是,它将在无限列表上运行,因为我们从不调用列表上的length
(或任何其他需要运行到列表末尾的函数),而发出下一个切片本身由于take n
导致 O(n),但是检查是否必须发射下一个切片仅花费 O(1)。因此,如果我们仅对第 k 个元素感兴趣,则需要花费 O(k + n)时间。
不使用显式递归,我们可以使用zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
和tails :: [a] -> [[a]]
:
neighbors :: Int -> [a] -> [[a]]
neighbors n l = zipWith (const . take n) (tails l) (drop (n-1) l)
答案 1 :(得分:2)
您不能直接在长度上进行图案匹配,但是可以对其进行后处理。没错,您的“如果长度”位不是非常Haskell-y。如果我是你,我只会产生最多 个N元素的所有尾巴,然后最后过滤掉散乱的人群。
neighbors :: Int -> [a] -> [[a]]
neighbors n xs = filter (\x -> length x == n) $ go xs
where go [] = []
go (y:ys) = take n (y:ys) : go ys
这里的我的go
基本上是您的neighbors
,没有“ if length”位。然后,我过滤掉所有太短的元素。现在,这更好,但是我们仍然可以做得更好。 Data.List
模块提供了tails
,这将使我们在这种情况下可以消除所有的显式递归。
import Data.List
neighbors :: Int -> [a] -> [[a]]
neighbors n = filter (\x -> length x == n) . map (take n) . tails
tails
返回给定列表的所有尾部的列表。让我们来看一个例子。
-- neighbors 3 [1, 2, 3, 4]
tails <==> [[1, 2, 3, 4], [2, 3, 4], [3, 4], [4], []]
map (take n) . tails <==> [[1, 2, 3] , [2, 3, 4], [3, 4], [4], []]
filter (...) . map (take n) . tails <==> [[1, 2, 3] , [2, 3, 4]]
现在,我们将问题分为三个漂亮的子问题(产生尾巴,取前N个元素,然后消除散乱子),每个子问题都由几个标准库函数解决。