使用fold检查Haskell列表中的连续True

时间:2015-09-02 21:34:03

标签: haskell

我是Haskell的新手。

对于Project Euler问题,我已经生成了一个bool列表。

[True, False, False, True, False. . .True]

我想要的是写一个函数找到前四个连续的True,然后在列表中返回它们的索引。

直观地说,我知道我应该通过折叠和某种模式匹配来做到这一点。你可以帮帮我吗?折叠的东西是这样的吗? (我不知道如何检索元素的索引。)

consecutiveFourTrues (w:x:y:z):xs = if (w && x && y && z) then w else consecutiveFourTrues (x:y:z):xs

感谢您的帮助!

5 个答案:

答案 0 :(得分:4)

<强> tldr

findIndices (\x -> (x == True)) $ map (\x -> all (== True) x) $ sliding 2 bs

定义一个类似于Scala中的sliding函数。

sliding :: Int -> [a] -> [[a]]
sliding size [] = []
sliding size ls@(x:xs) = if length ls >= size then (take size ls) : sliding size xs else sliding size xs

sliding 2[True, False, False, True, True, False, True, False, True, True]一起使用

[[True,False],[False,False],[False,True],[True,True],[True,False],[False,True],[True,False],[False,True],[True,True]]

然后我们可以map查看每个子列表是否包含all True个值,并获取与findIndices相关的索引的索引。

findIndices (\x -> (x == True)) $ map (\x -> all (== True) x) $ sliding 2 bs

[3,8]

答案 1 :(得分:3)

最简单的方法是拉链然后使用专业折叠。首先看看每组四个连续元素,看看它们是否都是真的:

import Data.List (zipWith4, findIndex)

consec4 :: [Bool] -> [Bool]
consec4 xs = zipWith4 (\x y z w -> x && y && z && w) xs (drop 1 xs) (drop 2 xs) (drop 3 xs)

现在你需要

fstConsec4 :: [Bool] -> Maybe Int
fstConsec4 = findIndex id . consec4

现在这不太可能是你能做到的最快的事情,而且对于更大的窗户来说它并不能很好地概括。

我们可以更直接地跳到折叠中,采用不同的方法。请注意,此特定版本在GHC 7.10或更高版本中的行为可能比早期版本更好。为了清晰起见,我使用了爆炸模式,但如果您愿意,可以使用seq$!来实现可移植性。

让我们从Data.List明确地忽略一个函数开始:

{-# INLINE foldrWithIndex #-}
foldrWithIndex :: (Int -> a -> b -> b) -> b -> [a] -> b
foldrWithIndex c n xs = foldr go (`seq` n) xs 0
  where
    go x r !i = c i x (r $ i + 1)

使用此函数,我们可以轻松定义一个函数,该函数查找第一组n个连续True值的开头索引。

consec :: Int -> [Bool] -> Maybe Int
consec n xs = foldrWithIndex go (`seq` Nothing) xs n
  where
    go _ix False r !_ = r n
    go ix True _r 1 = Just (ix - n + 1)
    go _ix True r need = r (need - 1)

快速浏览一下这产生的GHC核心表明它可能非常有效(这是使用ghc -O2 -ddump-simpl -dsuppress-all -dno-suppress-type-signatures生成的):

$wconsec :: Int# -> [Bool] -> Maybe Int
$wconsec =
  \ (ww_s1BQ :: Int#) (w_s1BN :: [Bool]) ->
    letrec {
      $wgo_s1BL :: [Bool] -> Int# -> Int# -> Maybe Int
      $wgo_s1BL =
        \ (w1_s1BA :: [Bool]) (ww1_s1BF :: Int#) (ww2_s1BJ :: Int#) ->
          case w1_s1BA of _ {
            [] -> Nothing;
            : y_a1y5 ys_a1y6 ->
              case y_a1y5 of _ {
                False -> $wgo_s1BL ys_a1y6 (+# ww1_s1BF 1) ww_s1BQ;
                True ->
                  case ww2_s1BJ of ds_X16V {
                    __DEFAULT -> $wgo_s1BL ys_a1y6 (+# ww1_s1BF 1) (-# ds_X16V 1);
                    1 -> Just (I# (+# (-# ww1_s1BF ww_s1BQ) 1))
                  }
              }
          }; } in
    $wgo_s1BL w_s1BN 0 ww_s1BQ

consec :: Int -> [Bool] -> Maybe Int
consec =
  \ (w_s1BM :: Int) (w1_s1BN :: [Bool]) ->
    case w_s1BM of _ { I# ww1_s1BQ -> $wconsec ww1_s1BQ w1_s1BN }

极端性能黑客攻击

理论上,应该可以做得更好,特别是在查看长名单时。特别是,如果BoolTrue值都很常见,则False值上的分支可能会导致许多错误预测的分支。在这种情况下,将值推入一个小位向量可能更好,然后在它上面做一些小技巧。不幸的是,说服GHC做这类事情可能很难,但我可能会稍后再试。

答案 2 :(得分:2)

是否要求使用fold? 因为它可以使用Data.List

中的标准函数直接表达
consec :: Int -> [Bool] -> [Int]
consec n = findIndices (isPrefixOf (replicate n True)) . tails

只需将其读作英语:findIndices列表的所有tails replicate - nTrue isPrefixOf

答案 3 :(得分:1)

这是另一种解决方案:

consec :: Int -> [Bool] -> [Int]
consec n = findIndices and . foldr (zipWith (:)) (repeat []) . take n . tails

它基于其他答案的想法。 take n . tails返回一个矩阵,其中包含长度为<= n的连续元素列表。 foldr (zipWith (:)) (repeat [])转置矩阵并丢弃长度为< n的列表。 findIndices找到索引。

答案 4 :(得分:0)

也许我们应该只计算连续的True而不是制作它们的子列表?

consec :: Int -> [Bool] -> [Int]
consec m = map (subtract m) . findIndices (>= m) . scanl (\ c x -> if x then c + 1 else 0) 0

ghci> consec 4 [False, True, True, True, True, True, False, True, True, True, True]
[1,2,7]