递归 Haskell 函数似乎不会终止

2024-01-28

为了提高我的 Haskell 技能,我正在尝试解决2018 年代码的到来 https://adventofcode.com/。正如预期的那样,我已经陷入了第一天,特别是第二部分:

- - 第二部分 - -

您注意到设备重复相同的频率更改列表 以及结束。

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

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

当前频率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 https://adventofcode.com/2018/day/1/input.


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

这是一个想法,

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

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

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

if 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

Note find返回一个Maybe (Int, Set Int)。它可能会发现Nothing或返回Just (x,s)对于某些频率x那已经在s。我们用fmap fst转动Just (x,s) into Just x.


EDIT

一旦你的工作顺利进行,如果你愿意的话,可以优化一些东西,或者尝试一下你的风格。以下是一个更简洁、可能更有效的版本。

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

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

因此它可以用于结果函数。我们也可以利用Maybe成为一个 monad 让事情变得更具可读性。

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

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

递归 Haskell 函数似乎不会终止 的相关文章

  • C++ 递归变量

    我想我的问题真的很简单 但我现在尝试解决它几个小时 但我似乎不明白 我有一个 ast 树 用 boost library 创建 并通过递归迭代它 我将所有节点保存在 NodeDescriptions 列表中 其中包含实际节点的编号 实际节点
  • 如何用模板参数包的内容填充数组?

    我嵌套了与 VS 2015 一起使用的部分专用模板代码 直到我发现它不符合标准 https stackoverflow com q 3052579 2747466 我希望如此 所以我扭曲了我的代码来克服前一个问题 并且that one ht
  • 如何获得字符串的所有字谜

    我试图找到一个字符串的所有可能的字谜并仅使用递归将它们存储在数组中 我被困住了 这就是我所拥有的一切 int main const int MAX 10 string a ABCD string arr 10 permute arr a 0
  • 生成所有可能的树

    给定以下数据类型定义 data FormTree Empty Node FormTree FormTree deriving Show 我想编写一个函数 它生成一个无限列表 其中包含按长度排序的所有可能的树 例如节点数量 下面的代码几乎满足
  • Haskell:找不到模块“Data.List.Split”

    我正在尝试在 Haskell 中拆分列表 据我所知 最简单的方法是splitOn 但是这个函数需要Data List Split 所以我尝试运行import Data List Split在前奏曲中 但是 我收到以下错误 Could not
  • 方案中的尾递归幂函数

    我在方案中编写尾递归幂函数时遇到问题 我想使用辅助函数来编写该函数 我知道我需要一个参数来保存累计值 但在那之后我就陷入了困境 我的代码如下 define pow tr a b define pow tr h result if b 0 r
  • 为什么Python有最大递归深度?

    Python有最大递归深度 但没有最大迭代深度 为什么递归受到限制 把递归当成迭代来对待 而不限制递归调用的次数不是更自然吗 我只想说这个问题的根源来自于尝试实现流 参见这个问题 https stackoverflow com questi
  • 使用reduce方法的斐波那契数列

    于是 我看到有人用reduce方法来计算斐波那契数列 这是他的想法 1 0 1 1 2 1 3 2 5 3 对应于 1 1 2 3 5 8 13 21 代码如下所示 def fib reduce n initial 1 0 dummy ra
  • 哪个更快?按引用传递与按值传递 C++

    我认为按引用传递应该比按值传递更快 因为计算机不复制数据 它只是指向数据的地址 但是 请考虑以下 C 代码 include
  • 跟踪 C++ 中递归函数被调用的次数

    我正在尝试编写一个程序 该程序具有一个参数是字符串向量的函数 我想在该函数上使用递归 但每次调用该函数时 我想更改参数 例如 fun stringArray i 其中 i 是调用该函数的次数 因此 以更简单的方式 如下所示 但我需要跟踪函数
  • 无点镜头创建不进行类型检查

    在函数中test 我遍历一个列表 从它的成员生成镜头 然后打印一些数据 当我使用有针对性的呼叫风格时 这会起作用 当我使其成为无点时 它无法进行类型检查 为什么会出现这种情况 我该如何解决这个问题 在我看来 GHC 并没有保留排名较高的信息
  • 搜索重写规则

    有什么办法可以浏览或搜索重写规则吗 当我使用像这样的标志时 ddump rule firings or ddump rule rewrites我只是得到了触发的规则的名称以及它引起的重写 但没有得到实际的规则本身 理想情况下 我想通过 GH
  • 将 num 的签名键入 double?

    我才刚刚开始为你学习 Haskell 以获得伟大的好处 并且我在类型类方面遇到了一些麻烦 我想创建一个接受任何数字类型并强制其为双精度的函数 我的第一个想法是定义 numToDouble Num gt Double 但我认为这不起作用 因为
  • php 删除特定文件夹及其所有内容

    我正在使用 php 删除包含已删除帖子图像的文件夹 我正在使用下面的代码 这是我在网上找到的并且做得很好 我想知道当一个文件夹中有其他文件夹时 如何只删除其中的特定文件夹 当我使用下面的代码时 如何才能做到这一点 使用 dev images
  • “Eta减少”并不总是在Haskell中举行?

    我发现我可以说 LANGUAGE RankNTypes f1 forall b b gt b gt forall c c gt c f1 f id f HLint 告诉我我可以在这里做 Eta 减少 但是 f2 forall b b gt
  • Haskell 泛化问题(涉及列表理解)

    假设我想知道a上的所有要点 x y 矩形内的平面has 我可以使用列表推导式来计算 如下所示 let myFun2D x y x lt 0 2 y lt 0 2 现在 如果我想为一个人完成同样的事情 x y z 空间 我可以采取同样的方式并
  • 在Racket中将结构递归转化为累积递归

    我有一些代码来查找最大高度并将其替换为关联的名称 身高和姓名有单独的列表 每个列表的长度相同且非空 我可以使用结构递归来解决这个问题 但必须将其更改为累积递归 而且我不确定如何做到这一点 我见过的所有例子都让我困惑 有人能够将代码变成使用累
  • 使用一次递归调用实现递归

    给定一个函数如下 f n f n 1 f n 3 f n 4 f 0 1 f 1 2 f 2 3 f 3 4 我知道使用递归来实现它 并在一个函数内进行三个递归调用 但我想在函数内仅使用一次递归调用来完成此操作 怎样才能做到呢 要实现使用
  • 标准的能力

    我发现了一些使用标准的旧例子here http www serpentine com blog 2009 09 29 criterion a new benchmarking library for haskell 看起来好像早在 2009
  • 如何在 Haskell 中制作打勾游戏的图案?

    实现有 2 个参数的函数 ticktick 第一个参数是自然数元组 定义游戏场地的行数和列数 第二个列表包含由玩家 x 和玩家 o 轮流玩的坐标给出的井字游戏比赛的记录 打印游戏的实际状态 其中游戏区域将由字符 和 界定 空方块 以及字符

随机推荐