递归 Haskell 函数似乎不会终止

geo*_*gjz 3 recursion haskell

为了提高我的 Haskell 技能,我正在尝试解决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
Run Code Online (Sandbox Code Playgroud)

我使用以下描述中提供的值测试此函数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> 
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

这正确地使用 GHC 构建,但正如我所说,永远不会终止并且不打印任何结果。我错过了什么?输入文件可以在这里找到。

Jor*_*ano 5

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

这是一个想法,

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

为了使关于最后一点的事情更清楚,请考虑,

 f0, f1,   f2,      f3...
 {}, {f0}, {f0,f1}, {f0,f1,f2}...`
Run Code Online (Sandbox Code Playgroud)

如果f3是重复,那么f3 ? {f0,f1,f2}

这可能看起来非常低效,但因为 Haskell 是惰性的,这些列表将根据需要生成。

我们需要导入模块来处理集合和可能,

import Data.Set
import Data.Maybe
Run Code Online (Sandbox Code Playgroud)

可以通过第一个频率和频率变化列表生成频率scanl (+)。该函数使用 operatorscanl (+) x xs操作 的元素,从 开始,生成总和的累积列表。 xs+x

freqs :: Int -> [Int] -> [Int]
freqs = scanl (+)  
Run Code Online (Sandbox Code Playgroud)

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

sets  :: [Int] -> [Set Int]
sets  = scanl (\s x -> insert x s) (empty) 
Run Code Online (Sandbox Code Playgroud)

一旦我们有了频率和集合,我们就大功告成了。主要功能只是把所有东西放在一起。它组合两个列表并找到第一对(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
Run Code Online (Sandbox Code Playgroud)

注意find返回一个Maybe (Int, Set Int). 它可能会发现Nothing或返回Just (x,s)一些频率x,这是已经在s。我们fmap fst用来Just (x,s)变成Just x.


编辑

如果你愿意的话,一旦你把事情做好了,可以优化一些事情,或者玩弄你的风格。下面是一个更简洁、可能更高效的版本。

频率和集合列表可以一次性构建在一起。

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

因此它已准备好用于结果函数。我们也可以利用Maybe作为一个 monad 来使事情更具可读性。

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

你有它,一个相当短的解决方案。我喜欢结果函数的变化。我喜欢这种do表示法,也没有让它计算前两个列表的压缩。我不太确定“融合”两个列表的构建是否值得。它的可读性稍差。使用三种功能,一种用于频率,一种用于集合,一种用于压缩,可能是最好的。