为了提高我的 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 构建,但正如我所说,永远不会终止并且不打印任何结果。我错过了什么?输入文件可以在这里找到。
您正在尝试将所有内容放在一个函数中。如果您以模块化方式工作,将问题分解为更小的问题,那就更好了。
这是一个想法,
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表示法,也没有让它计算前两个列表的压缩。我不太确定“融合”两个列表的构建是否值得。它的可读性稍差。使用三种功能,一种用于频率,一种用于集合,一种用于压缩,可能是最好的。