haskell:如何在起始列表中获取高于其邻居的数字列表

时间:2018-02-08 18:51:17

标签: list haskell

我正在尝试学习Haskell,我想解决一个任务。我有一个整数列表,如果它们比它们的邻居都大,我需要将它们添加到另一个列表中。例如: 我有一个[0,1,5,2,3,7,8,4]的起始列表,我需要打印出一个[5,8]的列表

这是我提出的代码,但它返回一个空列表:

largest :: [Integer]->[Integer]
largest n
   | head n > head (tail n) = head n : largest (tail n)
   | otherwise = largest (tail n)

3 个答案:

答案 0 :(得分:3)

我会按照Thomas M. DuBuisson的说明解决这个问题。由于我们希望列表的末尾“计数”,因此在创建三元组之前,我们会在每个末尾添加负无穷大。 monoid-extras包为此提供了合适的类型。

import Data.Monoid.Inf

pad :: [a] -> [NegInf a]
pad xs = [negInfty] ++ map negFinite xs ++ [negInfty]

triples :: [a] -> [(a, a, a)]
triples (x:rest@(y:z:_)) = (x,y,z) : triples rest
triples _ = []

isBig :: Ord a => (a,a,a) -> Bool
isBig (x,y,z) = y > x && y > z

scnd :: (a, b, c) -> b
scnd (a, b, c) = b

finites :: [Inf p a] -> [a]
finites xs = [x | Finite x <- xs]

largest :: Ord a => [a] -> [a]
largest = id
    . finites
    . map scnd
    . filter isBig
    . triples
    . pad

似乎工作正常;在ghci:

> largest [0,1,5,2,3,7,8,4]
[5,8]
> largest [10,1,10]
[10,10]
> largest [3]
[3]
> largest []
[]

您还可以考虑在单个列表理解中合并finitesmap scndfilter isBig(然后取消finitesscnd的定义,以及isBig):

largest :: Ord a => [a] -> [a]
largest xs = [x | (a, b@(Finite x), c) <- triples (pad xs), a < b, c < b]

但我更喜欢分解版本; finitesscndisBig函数可能会在您的开发中的其他位置变得有用,特别是如果您计划针对不同的需求构建一些此类变体。

答案 1 :(得分:2)

你可能尝试的一件事就是前瞻。 (托马斯·M·杜布森提出了一个不同的方法,如果你正确处理最后一个或两个元素也会有效。)因为这听起来像是一个你想要自己解决的问题作为一个学习练习,我会写一个您可以根据需要将骨架作为起点:

largest :: [Integer] -> [Integer]
largest [] = _
largest [x] = _ -- What should this return?
largest [x1,x2] | x1 > x2   = _
                | x1 < x2   = _
                | otherwise = _
largest [x1,x2,x3] | x2 > x1 && x2 > x3 = _
                   | x3 > x2 = _
                   | otherwise = _
largest (x1:x2:x3:xs) | x2 > x1 && x2 > x3 = _
                      | otherwise          = _

除了[x1,x2,x3]之外,我们还需要(x1:x2:x3:[])的特殊情况,因为根据您的评论中的说明,largest [3,3,2]应该返回[]。但是largest [3,2]应该返回[3]。因此,最后三个元素需要特殊处理,不能简单地在最后两个元素上进行递归。

如果你还希望结果包含列表的头部,如果它大于第二个元素,你可以将它作为辅助函数,largest就像largest (x1:x2:xs) = (if x1>x2 then [x1] else []) ++ largest' (x1:x2:xs)。也就是说,您需要对原始列表的第一个元素进行一些特殊处理,当您递归时,您不希望将这些元素应用于所有子列表。

答案 2 :(得分:0)

正如评论中所建议的,一种方法是首先使用前置zip3tail将列表分组为长度为3的元组:

*Main> let xs = [0,1,5,2,3,7,8,4]
*Main> zip3 xs (tail xs) (tail (tail xs))
[(0,1,5),(1,5,2),(5,2,3),(2,3,7),(3,7,8),(7,8,4)]

分别为[a] -> [b] -> [c] -> [(a, b, c)][a] -> [a]类型。

接下来,您需要找到一种方法来过滤掉中间元素大于第一个和最后一个元素的元组。一种方法是使用Prelude filter函数:

*Main> let xs = [(0,1,5),(1,5,2),(5,2,3),(2,3,7),(3,7,8),(7,8,4)]
*Main> filter (\(a, b, c) -> b > a && b > c) xs
[(1,5,2),(7,8,4)]

哪种类型:(a -> Bool) -> [a] -> [a]。这将根据从传递的谓词返回的布尔值过滤掉列表的元素。

现在,对于最后一部分,您需要从上面过滤的元组中提取中间元素。您可以使用Prelude map功能

轻松完成此操作
*Main> let xs = [(1,5,2),(7,8,4)]
*Main> map (\(_, x, _) -> x) xs
[5,8]

哪种类型:(a -> b) -> [a] -> [b]。此函数将元素从a类型的列表映射到b

上面拼凑的代码看起来像这样:

largest :: (Ord a) => [a] -> [a]
largest xs = map (\(_, x, _) -> x) $ filter (\(a, b, c) -> b > a && b > c) $ zip3 xs (tail xs) (tail (tail xs))

请注意,我使用了类型Ord,因为上面的代码需要与><进行比较。尽管如此,将Integer保留在这里也没关系。