递归Haskell函数似乎没有终止

时间:2018-12-14 00:05:01

标签: haskell recursion

为了提高我的Haskell技能,我正在尝试解决Advent of Code 2018。不出所料,我已经停留在第1天,特别是第2部分:

  

---第二部分---

     

您注意到设备重复了相同的频率更改列表   以上。

     

要校准设备,您需要找到它的第一个频率   到达两次。

     

例如,使用上面相同的更改列表,设备将   循环如下:

     

当前频率0,变化+1;结果频率1。

     

当前频率1,变化为-2;结果频率为-1。

     

当前频率-1,变化+3;结果频率2。

     

当前频率2,变化+1;结果频率3。

     

(此时,设备将从列表的开头继续。)

     

当前频率3,变化+1;结果频率4。

     

当前频率4,变化为-2;结果频率2   已经被看到。

     

在此示例中,达到两次的第一个频率是2。注意   您的设备可能需要重复其频率变化列表,其中许多   找到重复频率之前的时间,并且可能重复   可以在处理列表的中间找到。

     

还有其他示例:

     

+ 1,-1首先到达0两次。

     

+ 3,+ 3,+ 4,-2,-4首先达到10两次。

     

-6,+ 3,+ 8,+ 5,-6首先达到5两次。

     

+ 7,+ 7,-2,-7,-4首先两次达到14。

     

您的设备达到两次的第一个频率是什么?

基本上,我有一个很大的列表vals::[Int],其中包括上述所有频率变化。

这是我为解决此问题而编写的功能:

-- [1] The list of frequency changes
-- [2] The first repeat frequency
--             [1]      [2]
part2helper :: [Int] -> Int
part2helper ds = go ds []
    where go ds [] = go ds [0]
          go (d:ds) [f] = go ds $ (f+d):[f]
          go (d:ds) (f:fs) =
              if f `elem` fs then f
              else go ds $ (d+f):f:fs

我使用ghci中的描述中提供的值测试此功能:

*Main> part2helper (cycle [1, -2, 3, 1])
2
*Main> part2helper (cycle [1, -1])
0
*Main> part2helper (cycle [3, 3, 4, -2, -4])
10
*Main> part2helper (cycle [7, 7, -2, -7, -4])
14
*Main> 

所有结果都是正确的,所以我认为我的函数可以正常工作。现在的问题是,当我将其编译为从文件读取输入列表的程序时,该程序永远不会终止。这是代码:

module Main where

import System.Environment

main = do 
    [input] <- getArgs
    s       <- readFile input
    let n    = lines $ s
        vals = map (read::String->Int) $ fmap (filter (/='+')) n
        sol  = part2helper (cycle vals)
    print sol

-- [1] The list of frequency changes
-- [2] The first repeat frequency
--             [1]      [2]
part2helper :: [Int] -> Int
part2helper ds = go ds []
    where go ds     []     = go ds [0]
          go (d:ds) [f]    = go ds $ (f+d):[f]
          go (d:ds) (f:fs) =
              if f `elem` fs then f
              else go ds $ (d+f):f:fs

这将正确构建GHC,但正如我所说,它永远不会终止并且不会显示任何结果。我想念什么?可以找到输入文件here

1 个答案:

答案 0 :(得分:4)

您试图将所有内容放到一个函数中。如果您以模块化的方式工作,将问题分解成更小的问题,那就更好了。

这是个主意,

  • 生成频率序列,
    f0, f1, f2...
  • 生成频率累积集的序列
    {}, {f0}, {f0,f1}, {f0,f1,f2}...
  • 检查重复插入的内容,即
    fi这样fi ∈ {f0..fi-1}

为了使最后一点更清楚,请考虑

 f0, f1,   f2,      f3...
 {}, {f0}, {f0,f1}, {f0,f1,f2}...`

如果f3是重复项,则f3 ∈ {f0,f1,f2}

这似乎效率很低,但是由于Haskell懒惰,因此将根据需要生成这些列表。

我们将需要导入模块以使用集合和也许,

import Data.Set
import Data.Maybe

可以通过scanl (+)从第一个频率生成频率并列出频率变化列表。函数scanl (+) x xsxs开始,用运算符+操作x的元素,从而生成总和列表。

freqs :: Int -> [Int] -> [Int]
freqs = scanl (+)  

现在我们可以生成集合列表。这里我们也使用scanl。在每个步骤中,我们都会插入一个新频率,然后从空集开始。

sets  :: [Int] -> [Set Int]
sets  = scanl (\s x -> insert x s) (empty) 

一旦我们有了频率和集合,我们就完成了大部分工作。main函数将所有内容放在一起。它组合两个列表并找到第一对(fi , {f0,...,fi-1}),使得fi ∈ {f0,...,fi-1},并返回相应的fi

result :: Int -> [Int] -> Maybe Int
result x xs = let fs = freqs x xs
                  ss = sets fs                  
                  r  = find (\(y,s) -> y `elem` s) (zip fs ss)
              in fmap fst r

注释find返回一个Maybe (Int, Set Int)。它可能会找到Nothing或以Just (x,s)的某个频率x返回s。我们使用fmap fstJust (x,s)变成Just x


编辑

一旦您愿意的话,可以优化一些东西,或者尝试自己的风格。以下是一个更简洁的版本,可能是更有效的版本。

可以一次性建立频率和频率列表。

freqsets :: Int -> [Int] -> [(Int, Set Int)]
freqsets f0 = scanl (\(f,s) x -> (f+x,insert f s)) (f0,empty) 

因此可以随时将其用于result函数。同样,我们可以利用Maybe作为monad来使事情更具可读性。

result :: Int -> [Int] -> Maybe Int
result f0 xs = do (f,_) <- find(\(y,s)->y `elem` s) (freqsets f0 xs)
                  return f 

在那里,这是一个相当短的解决方案。我喜欢结果函数中的更改。我喜欢do表示法,也没有让它计算前两个列表的压缩率。我不确定将两个列表的构建“融合”是否值得。可读性较差。最好使用三种功能,一种用于频率,一种用于组,另一种用于拉链。