欧拉计划 No. 14 Haskell

2024-01-18

我正在尝试解决 Project Euler 的问题 14 (http://projecteuler.net/problem=14 http://projecteuler.net/problem=14)并且我使用 Haskell 陷入了死胡同。

现在,我知道这些数字可能足够小,我可以进行暴力破解,但这不是我练习的目的。 我正在尝试记住中间结果Map类型的Map Integer (Bool, Integer)其含义是:

- the first Integer (the key) holds the number
- the Tuple (Bool, Interger) holds either (True, Length) or (False, Number) 
                                           where Length = length of the chain
                                                 Number = the number before him

Ex:

  for 13: the chain is 13 → 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1
  My map should contain : 
  13 - (True, 10)
  40 - (False, 13)
  20 - (False, 40)
  10 - (False, 20)
  5  - (False, 10)
  16 - (False, 5)
  8  - (False, 16)
  4  - (False, 8)
  2  - (False, 4)
  1  - (False, 2)

现在,当我搜索另一个号码时,例如40我知道链条有(10 - 1) length等等。 我现在想要,如果我搜索 10,不仅要告诉我 10 的长度是(10 - 3) length并更新地图,但我也想更新 20、40,以防它们仍然存在(False,_)

My code:

import Data.Map as Map

solve :: [Integer] -> Map Integer (Bool, Integer)
solve xs    = solve' xs Map.empty
    where
        solve' :: [Integer] -> Map Integer (Bool, Integer) -> Map Integer (Bool, Integer)
        solve' []     table = table
        solve' (x:xs) table =
            case Map.lookup x table of
                Nothing     -> countF x 1 (x:xs) table
                Just     (b, _) ->
                    case b of
                        True    -> solve' xs table
                        False   -> {-WRONG-} solve' xs table

        f :: Integer -> Integer
        f x
            | x `mod` 2 == 0    = x `quot` 2
            | otherwise     = 3 * x + 1

        countF :: Integer -> Integer -> [Integer] -> Map Integer (Bool, Integer) -> Map Integer (Bool, Integer)
        countF n cnt (x:xs) table
            | n == 1    = solve' xs (Map.insert x (True, cnt) table)
            | otherwise = countF (f n) (cnt + 1) (x:xs) $ checkMap (f n) n table

        checkMap :: Integer -> Integer -> Map Integer (Bool, Integer) -> Map Integer (Bool, Integer)
        checkMap n rez table    =
            case Map.lookup n table of
                Nothing -> Map.insert n (False, rez) table
                Just _  -> table

在 {-WRONG-} 部分,我们应该更新所有值,如下例所示:

--We are looking for 10:
  10 - (False, 20)
     |
     V                                   {-finally-} update 10 => (True, 10 - 1 - 1 - 1)
  20 - (False, 40)                                      ^
     |                                                  |
     V                                  update 20 => 20 - (True, 10 - 1 - 1)
  40 - (False, 13)                          ^
     |                                      |
     V                      update 40 => 40 - (True, 10 - 1)
  13 - (True, 10)              ^
     |                         |
     ---------------------------

问题是我不知道是否可以在函数中做两件事,例如更新数字并继续重复。在一个C就像语言我可能会做类似的事情(伪代码):

void f(int n, tuple(b,nr), int &length, table)
{
      if(b == False) f (nr, (table lookup nr), 0, table);
      // the bool is true so we got a length
      else
      {
            length = nr;
            return;
      }
      // Since this is a recurence it would work as a stack, producing the right output
      table update(n, --cnt);
}

最后一条指令可以工作,因为我们通过引用发送 cnt。而且我们总是知道它会在某个时刻完成,并且 cnt 不应该


最简单的优化(正如您所确定的)是记忆。您尝试自己创建一个记忆系统,但是遇到了如何存储记忆值的问题。有一些解决方案可以以可维护的方式做到这一点,例如使用 State monad 或STArray http://www.haskell.org/ghc/docs/latest/html/libraries/array/Data-Array-ST.html。然而,有一个更简单的解决方案可以解决您的问题 - 使用 haskell 现有的记忆功能。 Haskell 默认情况下会记住常量值,因此如果您创建一个存储 collat​​z 值的值,它将自动记忆!

下面的斐波那契定义就是一个简单的例子:

fib :: Int -> Integer
fib n = fibValues !! n where
  fibValues = 1 : 1 : zipWith (+) fibValues (tail fibValues)

The fibValues is a [Integer],并且由于它只是一个常数值,因此它被记忆。然而,这并不意味着它会被一次性记住,因为它是一个无限列表,所以它永远不会完成。相反,这些值仅在需要时才计算,因为 haskell 很懒。


因此,如果你对你的问题做了类似的事情,你不需要做很多工作就可以记住。但是,使用上面这样的列表在您的解决方案中效果不佳。这是因为 collat​​z 算法使用许多不同的值来获取给定数字的结果,因此所使用的容器需要随机访问才能有效。显而易见的选择是数组。

collatzMemoized :: Array Integer Int

接下来,我们需要用正确的值填充数组。我会写这个函数假装collatz存在计算任意 n 的 collat​​z 值的函数。另请注意,数组的大小是固定的,因此需要使用一个值来确定要记忆的最大数量。我将使用一百万,但可以使用任何值(这是内存/速度的权衡)。

collatzMemoized = listArray (1, maxNumberToMemoize) $ map collatz [1..maxNumberToMemoize] where
  maxNumberToMemroize = 1000000

这非常简单,listArray给定边界,并给出该范围内所有 collat​​z 值的列表。请记住,这不会立即计算所有 collat​​z 值,因为这些值是惰性的。

现在,可以编写 collat​​z 函数了。最重要的部分是只检查collatzMemoized如果被检查的数字在其范围内,则为数组:

collatz :: Integer -> Int
collatz 1 = 1
collatz n
  | inRange (bounds collatzMemoized) nextValue = 1 + collatzMemoized ! nextValue
  | otherwise = 1 + collatz nextValue
  where
    nextValue = case n of
      1 -> 1
      n | even n -> n `div` 2
        | otherwise -> 3 * n + 1

在 ghci 中,您现在可以看到记忆化的有效性。尝试collatz 200000。大约需要 2 秒才能完成。但是,如果您再次运行它,它将立即完成。

最后可以找到解决方案:

maxCollatzUpTo :: Integer -> (Integer, Int)
maxCollatzUpTo n = maximumBy (compare `on` snd) $ zip [1..n] (map collatz [1..n]) where

然后打印:

main = print $ maxCollatzUpTo 1000000

如果运行main,大约10秒后就会打印结果。

现在,这种方法的一个小问题是它使用了大量的堆栈空间。它在 ghci 中工作得很好(它似乎在堆栈空间方面使用更灵活)。但是,如果您编译它并尝试运行可执行文件,它将崩溃(堆栈空间溢出)。因此,要运行该程序,您必须在编译时指定更多内容。这可以通过添加来完成-with-rtsopts='K64m'到编译选项。这会将堆栈增加到 64mb。

现在程序可以编译并运行:

> ghc -O3 --make -with-rtsopts='-K6m' problem.hs

Running ./problem将在不到一秒的时间内给出结果。

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

欧拉计划 No. 14 Haskell 的相关文章

  • StackOverflowError 计算 BigInteger 的阶乘?

    我正在尝试编写一个Java程序来计算大数的阶乘 它似乎BigInteger无法容纳这么大的数量 下面是我编写的 简单的 代码 public static BigInteger getFactorial BigInteger num if n
  • 处理许多不相关的类型时避免样板

    我正在编写处理以下值的代码语言 扩展 注释 语法 http hackage haskell org packages archive haskell src exts 1 1 4 doc html Language Haskell Exts
  • 如何在单向链表(一次遍历中)中从尾部获取第 n 个节点?

    所以我在一次考试中得到了这个问题 如何从单链表的尾部获取第 n 个节点 每个节点都有一个值和一个下一个 指向下一个值的指针 我们得到这个 getNodeFromTail Node head int x 所以我的做法是通过遍历一次来求出列表的
  • 在 haskell 中处理 IO 与纯代码

    我正在编写一个shell脚本 我在haskell中的第一个非示例 它应该列出一个目录 获取每个文件大小 进行一些字符串操作 纯代码 然后重命名一些文件 我不确定我做错了什么 所以有两个问题 我应该如何安排这样的程序中的代码 我有一个具体问题
  • 更合适地说插入未排序动态数组的摊销 O(1) 与 O(n) ?

    这属于 stackoverflow com help on topic 中的 软件算法 在本例中 是一种将项目添加到动态未排序数组的软件算法 This is chart we made in class about the runtimes
  • 反应性香蕉时间延迟

    我已经查阅了文档反应香蕉 http hackage haskell org package reactive banana 而且我找不到指定明确时间延迟的方法 举例来说 我想采取Event t a并将其所有发生的事件移至未来 1 秒 或获取
  • 带有查询参数的渲染 url

    无法找到简单问题的解决方案 答案应该是显而易见的 如何在 hamlet 模板中使用查询参数渲染 url I e ItemsR 将生成http localhost 3000 items我如何生成类似的东西http localhost 3000
  • 如何处理“恐慌:不可能的事情发生了”并在 Haskell 中继续

    我有以下代码 它使用 GHC API 加载模块并获取表达式的类型 typeObjects String gt String gt IO Type typeObjects modules objects do defaultErrorHand
  • 定点数学比浮点运算快吗?

    多年前 即 20 世纪 90 年代初期 我构建了图形软件包 该软件包基于定点算术和预先计算的 cos sin 表格以及使用牛顿近似方法进行 sqrt 和对数近似的缩放方程来优化计算 这些先进技术似乎已经成为图形和内置数学处理器的一部分 大约
  • 自动跟踪算法

    我正在尝试写一个simple跟踪例程来跟踪电影中的某些点 本质上我有一系列 100 帧长的电影 在黑暗背景上显示一些亮点 我每帧有大约 100 150 个点 它们在电影的过程中移动 我想跟踪它们 所以我正在寻找一些有效的 但可能不会过度实施
  • 数独算法,暴力破解[关闭]

    这个问题不太可能对任何未来的访客有帮助 它只与一个较小的地理区域 一个特定的时间点或一个非常狭窄的情况相关 通常不适用于全世界的互联网受众 为了帮助使这个问题更广泛地适用 访问帮助中心 help reopen questions 我正在尝试
  • 管道中缺少 ResourceT 实例

    我在尝试使用时遇到奇怪的错误ResourceT http hackage haskell org package conduit 1 0 9 1 docs Data Conduit html t 3aResourceT来自管道 1 0 9
  • 使用 Haskell 将函数注入到 Java .class 文件中

    我使用 Haskell 编写了一个 Java 字节码解析器 它工作得很好 然而下一步让我完全难住了 我的 Haskell 程序需要修改 class 文件 以便在执行时 Java 程序打印 输入 此处的方法名称 在执行方法之前 并且 退出 此
  • “包含字符串”的快速索引

    在我的应用程序中 我有多达数百万个短字符串 大部分短于 32 个字符 我想实现一个带有附加列表的搜索框 该列表仅包含包含在搜索框中输入的整个字符串的元素 如何预先建立索引来快速找到此类字符串 所有排序的 STL 容器都会检查整个字符串 对于
  • 地图不是接受一个函数而列表返回一个列表吗?

    map2 List a gt b gt c gt a gt b gt c map2 List f map2 List f a as bs map f a bs map2 List f as bs 这是我的讲座中的一个示例 它尝试将二元函数应
  • 如何求两个地点的经纬度距离?

    我有一组位置的纬度和经度 怎么找distance从集合中的一个位置到另一个位置 有公式吗 半正矢公式假定地球是球形的 然而 地球的形状更为复杂 扁球体模型会给出更好的结果 如果需要这样的精度 你应该更好地使用文森特逆公式 See http
  • 如何在 C# 中以编程方式创建柔和的颜色?

    根据所需的颜色数量均匀分布地生成它们 如果指定的计数为 8 则看起来像这样 List
  • 为什么解析器组合器“seq”用“bind”和“return”定义?

    我正在读这个article http eprints nottingham ac uk 237 1 monparsing pdf关于解析器组合器并且不理解以下内容 他们说使用seq 见下文 导致解析器将嵌套元组作为结果 操作起来很混乱 se
  • 重写修改后的 goto 语义的算法

    我有一大堆使用旧的自行设计的脚本语言编写的遗留代码 我们将它们编译 翻译成 javascript 该语言有条件跳转 跳转到标签 与普通 goto 语句的区别在于 不可能向后跳转 该语言中没有嵌套的 if 语句或循环 由于 javascrip
  • 为什么这个算法的Big-O复杂度是O(n^2)?

    我知道这个算法的大O复杂度是O n 2 但我不明白为什么 int sum 0 int i 1 j n n while i lt j sum 即使我们设定了j n n一开始 我们在每次迭代期间递增 i 并递减 j 因此最终的迭代次数不应该比n

随机推荐