简体   繁体   English

递归Haskell函数似乎不会终止

[英]Recursive Haskell function seemingly doesn't terminate

To improve my Haskell skills, I'm trying to solve the Advent of Code 2018 . 为了提高我的Haskell技能,我正在努力解决2018年代码问世 As expected, I am already stuck on day 1, specifically on part 2: 正如预期的那样,我已经停留在第1天,特别是在第2部分:

--- Part Two --- - - 第二部分 - -

You notice that the device repeats the same frequency change list over and over. 您注意到设备反复重复相同的频率更改列表。

To calibrate the device, you need to find the first frequency it reaches twice. 要校准设备,您需要找到它达到两倍的第一个频率。

For example, using the same list of changes above, the device would loop as follows: 例如,使用上面相同的更改列表,设备将循环如下:

Current frequency 0, change of +1; 电流频率0,变化+1; resulting frequency 1. 结果频率为1。

Current frequency 1, change of -2; 电流频率1,变化-2; resulting frequency -1. 结果频率为-1。

Current frequency -1, change of +3; 电流频率-1,变化+3; resulting frequency 2. 结果频率2。

Current frequency 2, change of +1; 当前频率2,变化+1; resulting frequency 3. 结果频率3。

(At this point, the device continues from the start of the list.) (此时,设备从列表的开头继续。)

Current frequency 3, change of +1; 当前频率3,变化+1; resulting frequency 4. 结果频率为4。

Current frequency 4, change of -2; 电流频率4,变化-2; resulting frequency 2, which has already been seen. 结果频率2,已经见过。

In this example, the first frequency reached twice is 2 . 在此示例中,达到两次的第一个频率是2 Note that your device might need to repeat its list of frequency changes many times before a duplicate frequency is found, and that duplicates might be found while in the middle of processing the list. 请注意,您的设备可能需要在找到重复频率之前多次重复其频率更改列表,并且在处理列表的过程中可能会找到重复项。

Here are other examples: 以下是其他示例:

+1, -1 first reaches 0 twice. +1,-1首先达到0两次。

+3, +3, +4, -2, -4 first reaches 10 twice. + 3,+ 3,+ 4,-2,-4首先达到10两次。

-6, +3, +8, +5, -6 first reaches 5 twice. -6,+ 3,+ 8,+ 5,-6首先达到5两次。

+7, +7, -2, -7, -4 first reaches 14 twice. + 7,+ 7,-2,-7,-4首先达到14两次。

What is the first frequency your device reaches twice? 您的设备第一次达到两倍的频率是多少?

Basically, I have a very large list vals::[Int] that includes all the frequency changes mentioned above. 基本上,我有一个非常大的列表vals::[Int] ,包括上面提到的所有频率变化。

Here is the function I wrote for solving this problem: 这是我为解决这个问题而编写的函数:

-- [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

I test this function with the values provided in the description in ghci : 我使用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> 

All result are correct, so I assume my function works correctly. 所有结果都是正确的,所以我认为我的功能正常。 The problem now is, when I compile this into a program that reads the input list from a file, the program never terminates. 现在的问题是,当我将其编译成一个从文件中读取输入列表的程序时,程序永远不会终止。 Here's the code: 这是代码:

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

This builds with GHC correctly, but as I said, never terminates and prints no result. 这正确地构建了GHC,但正如我所说,永远不会终止并打印没有结果。 What am I missing? 我错过了什么? The input file can be found here . 输入文件可以在这里找到。

You're trying to put everything together in a single function. 你试图将所有东西放在一个单一的功能中。 It's much better if you work in a modular fashion, breaking the problem into smaller ones. 如果你以模块化的方式工作,将问题分解成更小的问题会好得多。

Here's an idea, 这是一个想法,

  • generate the sequence of frequencies, 生成频率序列,
    f0, f1, f2...
  • generate the sequence of cumulative sets of frequencies 生成累积频率组的序列
    {}, {f0}, {f0,f1}, {f0,f1,f2}...
  • check repeated insertions, ie 检查重复插入,即
    fi such that fi ∈ {f0..fi-1} fi这样fi ∈ {f0..fi-1}

To make things clearer regarding the last point consider, 为了使关于最后一点考虑的事情更清楚,

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

if f3 were a repetition then f3 ∈ {f0,f1,f2} 如果f3是重复,那么f3 ∈ {f0,f1,f2}

This may seem terribly inefficient but because Haskell is lazy, these lists will be generated as needed. 这可能看起来非常低效但是因为Haskell很懒,所以这些列表将根据需要生成。

We'll need to import modules to work with sets and maybes, 我们需要导入模块来处理集合和maybes,

import Data.Set
import Data.Maybe

Generating the frequencies from the first frequency and a list of frequency changes can be done via scanl (+) . 从第一频率和频率变化列表生成频率可以通过scanl (+) The function scanl (+) x xs operates the elements of xs with the operator + , starting at x , generating the cumulative list of sums. 功能scanl (+) x xs操作的元素xs与运营商+ ,起始于x ,生成和的累积列表。

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

Now we can generate the list of sets. 现在我们可以生成集合列表。 Here too we use a scanl . 我们也在这里使用scanl In each step we insert a new frequency, and we start with the empty set. 在每个步骤中,我们插入一个新频率,然后从空集开始。

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

Once we have the frequencies and the sets we are pretty much done.The main function just puts everything together. 一旦我们有频率和集合,我们就已经完成了。主要功能只是将所有内容放在一起。 It combines both lists and finds the first pair (fi , {f0,...,fi-1}) such that fi ∈ {f0,...,fi-1} , and returns the corresponding fi 它结合了两个列表并找到第一对(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

Note find returns a Maybe (Int, Set Int) . 注意find返回Maybe (Int, Set Int) It may find Nothing or return Just (x,s) for some frequency x that was already in s . 它可能会发现Nothing或返回Just (x,s)对某些频率x ,这是已经在s We use fmap fst to turn Just (x,s) into Just x . 我们使用fmap fstJust (x,s)转换为Just x


EDIT 编辑

Once you've got things working if you wish to, may optimize a few things, or play around with your style. 一旦你有了工作,如果你愿意,可以优化一些事情,或玩你的风格。 The following is a more succinct, and possibly a little bit more efficient version. 以下是更简洁,可能更高效的版本。

The list of frequencies and sets can be built together in one go. 频率和集合列表可以一起构建。

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

And so it's ready to use for the result function. 因此它可以用于结果函数。 Also we can take advantage of Maybe being a monad to make things a bit more readable. 我们也可以利用Maybe作为monad来使事情更具可读性。

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

And there you have it, a rather short solution. 你有它,一个相当简短的解决方案。 I like the change in the result function. 我喜欢结果函数的变化。 I like the do notation, as well as not having it calculate the zipping of the two previous lists. 我喜欢do符号,并且没有计算前两个列表的压缩率。 I'm not so sure if "fusing" the building of both lists is worth it. 如果“融合”两个列表的构建是值得的,我不太确定。 It's a bit less readable. 它的可读性有点差。 Using three functions, one for frequencies, one for sets, and one for zipping, might be best. 使用三个函数,一个用于频率,一个用于集合,一个用于压缩,可能是最好的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM