为了提高我的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。
答案 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 xs
从xs
开始,用运算符+
操作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 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)
因此可以随时将其用于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
表示法,也没有让它计算前两个列表的压缩率。我不确定将两个列表的构建“融合”是否值得。可读性较差。最好使用三种功能,一种用于频率,一种用于组,另一种用于拉链。