【问题标题】:haskell - hyperoperation (ackermann) function, tetrationhaskell - 超操作(ackermann)函数,四分法
【发布时间】:2011-05-11 16:10:03
【问题描述】:

我正在尝试在haskell中编写一个超操作函数。

通常写为ackermann(a,b,n),但出于部分应用目的,我认为将n 放在首位更有意义。因此我称之为hypOp n a b

我发现最自然的形式使用折叠 ao replicate 列表,如下所示:

Prelude> replicate 3 5
[5,5,5]
Prelude> foldr1 (*) $ replicate 3 5
125

根据折叠的函数参数,可以是加法、乘法、求幂、四乘等。

非正式概述:

hypOp 0 a _ = succ a
hypOp 1 a b = a + b = foldr1 (succ a) (replicate b a) --OFF BY ONE ISSUES, TYPE ISSUES
hypOp 2 a b = a * b = foldr1 (+) $ replicate b a
hypOp 3 a b = a ^ b = foldr1 (*) $ replicate b a
hypOp 4 a b = = foldr1 (^)

出于关联的原因,我的印象是我必须使用右折叠,这很不幸,因为左折叠 (foldl') 提供的严格性会很有用。

左右折叠问题

Prelude> foldl1 (^) $ replicate 4 2 --((2^2)^2)^2 = (4^2)^2 = 16^2 = 256 != 2 tetra 4
256
Prelude> foldr1 (^) $ replicate 4 2 --(2^(2^(2^2))) = 2^16 = 65536 == 2 tetra 4
65536

当我开始使用后继功能时,我遇到了一个问题。所以我使用 (+) 作为我的基本折叠的函数

Prelude> let add a b = foldr1 (\a b -> succ b) $ replicate b a
Prelude> add 5 4
8
Prelude> add 10 5  --always comes out short by one, so i cant build off this
14

前几个 n 值,“手动”完成:

Prelude> let mul a b = foldr1 (+) $ replicate b a
Prelude> let exp a b = foldr1 mul $ replicate b a
Prelude> let tetra a b = foldr1 exp $ replicate b a
Prelude> let pent a b = foldr1 tetra $ replicate b a
Prelude> let sixate a b = foldr1 pent $ replicate b a
Prelude> mul 2 3 --2+2+2
6
Prelude> exp 2 3 --2*2*2
8
Prelude> tetra 2 3 --2^(2^2)
16
Prelude> pent 2 3 --2 tetra (2 tetra 2) 
65536
Prelude> sixate 2 3
*** Exception: stack overflow

我尝试通过上述方法进行正式定义:

hypOp :: Int -> Int -> Int -> Int
hypOp 0 a b = succ a
hypOp 1 a b  =  (+) a b  --necessary only bc off-by-one error described above
hypOp n a b = foldr1 (hypOp $ n-1) (replicate b a)

递归数组的其他尝试(没有任何显着差异):

let arr = array (0,5) ( (0, (+)) : [(i, (\a b -> foldr1 (arr!(i-1)) (replicate b a)) ) | i <- [1..5]])
-- (arr!0) a b makes a + b
-- (arr!1) a b makes a * b, etc.

所以我的问题是......

  1. 任何一般性建议,不同的功能方法?我似乎找不到避免溢出的方法,除了使用非常“命令式”的风格,这不是我在使用 haskell 并尝试以惯用风格​​编码时的意图
  2. 如何处理我的一对一问题,以便我可以使用succ 从最底部“正确”开始
  3. 严格性和左右折叠。有没有办法在seq 工作?我可以使用foldl1' 而不是foldr1 并避免上述问题的某种方式?

【问题讨论】:

    标签: haskell ackermann


    【解决方案1】:
    1. 请参见第 3 点。尽管以这种方式定义这些操作是可行的,并且您可以在没有溢出的情况下进行,但这是一种效率极低的方法。你的运行时间在答案中是线性的,因为你最终会重复加法。

    2. 您之所以选择一个,主要是因为您使用foldr1 f 而不是foldr f 的身份。

      foldr (+) 0 [a, a, a] = a + (a + (a + 0)))
      foldr1 (+) [a, a, a]  = a + (a + a)
      

      注意在foldr1 的情况下,+ 的应用少了一个。

    3. 简单地将参数的顺序更改为(^) 怎么样?这样,您可以使用左折叠:

      Prelude Data.List> foldl1 (flip (^)) $ replicate 4 2
      65536
      

      现在您可以使用严格版本foldl1'。它不再溢出,但它当然是极其低效的。

    【讨论】:

    • 好的,谢谢你的提示。对于 'purity'/education id,喜欢尝试仅将 succ 定义为基本案例,并从中构建其他所有内容,包括加法和向上。我将尝试使论点翻转工作。如果我要尝试根据幂运算制作一个高性能的版本 ID。有没有比折叠更有效的方法?
    • @jon_darkstar:对于一般情况,也许你可以做一些类似于乘法乘法、乘方乘方的事情?我对超操作不太熟悉,所以我不确定。
    • 顺便说一句 - 我喜欢你的回答,我投了赞成票,但我还没有接受,因为我还在玩这个,我想暂时让这个问题悬而未决
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-18
    • 1970-01-01
    • 1970-01-01
    • 2020-04-09
    相关资源
    最近更新 更多