在 Haskell 中使用递归方案解决变更问题

2024-01-20

我试图从中理解组织形态关于递归方案的博客 https://blog.sumtypeofway.com/posts/recursion-schemes-part-4.html。当我运行示例来解决问题时遇到问题改变问题 https://en.wikipedia.org/wiki/Change-making_problem正如博客中提到的。

找零问题采用货币的面额,并尝试找到创造给定金额所需的最小硬币数量。下面的代码取自博客,应该计算答案。

{-# LANGUAGE DeriveFunctor #-}

module Main where

import Control.Arrow ( (>>>) )
import Data.List ( partition )
import Prelude hiding (lookup)

newtype Term f = In {out :: f (Term f)}

data Attr f a = Attr
  { attribute :: a
  , hole :: f (Attr f a)
  }

type CVAlgebra f a = f (Attr f a) -> a

histo :: Functor f => CVAlgebra f a -> Term f -> a
histo h = out >>> fmap worker >>> h
 where
  worker t = Attr (histo h t) (fmap worker (out t))

type Cent = Int

coins :: [Cent]
coins = [50, 25, 10, 5, 1]

data Nat a
  = Zero
  | Next a
  deriving (Functor)

-- Convert from a natural number to its foldable equivalent, and vice versa.
expand :: Int -> Term Nat
expand 0 = In Zero
expand n = In (Next (expand (n - 1)))

compress :: Nat (Attr Nat a) -> Int
compress Zero = 0
compress (Next (Attr _ x)) = 1 + compress x

change :: Cent -> Int
change amt = histo go (expand amt)
 where
  go :: Nat (Attr Nat Int) -> Int
  go Zero = 1
  go curr@(Next attr) =
    let given = compress curr
        validCoins = filter (<= given) coins
        remaining = map (given -) validCoins
        (zeroes, toProcess) = partition (== 0) remaining
        results = sum (map (lookup attr) toProcess)
     in length zeroes + results

lookup :: Attr Nat a -> Int -> a
lookup cache 0 = attribute cache
lookup cache n = lookup inner (n - 1) where (Next inner) = hole cache

现在如果你评估change 10它会给你3。

这是……不正确的,因为你可以用 1 个面值 10 的硬币赚到 10。

所以我想也许它可以解决硬币找零问题 https://math.stackexchange.com/questions/15521/making-change-for-a-dollar-and-other-number-partitioning-problems,它会找到您赚取给定金额的最大方式。例如你可以用 4 种方法制作 10 个{ 1, 1, ... 10 times }, { 1, 1, 1, 1, 5}, { 5, 5 }, { 10 }.

那么这段代码有什么问题呢?解决问题的过程中哪里出了问题?

TLDR

上面的一段代码来自于此关于递归方案的博客 https://blog.sumtypeofway.com/posts/recursion-schemes-part-4.html没有找到改变一笔钱的最小或最大的方法。为什么它不起作用?


我更多地考虑用递归方案来编码这个问题。也许有一个很好的方法来解决无序问题(即,考虑 5c + 1c 与 1c + 5c 不同),使用组织同态来缓存无向递归调用,但我不知道它是什么。相反,我寻找一种使用递归方案来实现动态编程算法的方法,其中以特定顺序探测搜索树,以便确保您不会多次访问任何节点。

我使用的工具是 hylomorphism,它会在您正在阅读的文章系列的后面部分出现。它由展开(变形)和折叠(变形)组成。 hylomorphism 使用 ana 构建中间结构,然后使用 cata 将其分解为最终结果。在这种情况下,我使用的中间结构描述了一个子问题。它有两个构造函数:要么子问题已经解决,要么还剩下一些钱可以找零,以及可以使用的硬币面额池:

data ChangePuzzle a = Solved Int
                    | Pending {spend, forget :: a}
                    deriving Functor
type Cent = Int
type ChangePuzzleArgs = ([Cent], Cent)

我们需要一个将单个问题转化为子问题的余代数:

divide :: Coalgebra ChangePuzzle ChangePuzzleArgs
divide (_, 0) = Solved 1
divide ([], _) = Solved 0
divide (coins@(x:xs), n) | n < 0 = Solved 0
                         | otherwise = Pending (coins, n - x) (xs, n)

我希望前三种情况是显而易见的。最后一种情况是唯一具有多个子问题的情况。我们可以使用第一个列出的面额的一枚硬币,然后继续找较小金额的硬币,或者我们可以保持金额不变,但减少我们愿意使用的硬币面额列表。

组合子问题结果的代数要简单得多:我们只需将它们相加即可。

conquer :: Algebra ChangePuzzle Int
conquer (Solved n) = n
conquer (Pending a b) = a + b

我最初尝试写conquer = sum(使用适当的 Foldable 实例),但这是不正确的。我们不是在总结a输入子问题;相反,所有有趣的值都在 Solved 构造函数的 Int 字段中,并且sum不看那些,因为它们不属于类型a.

最后,我们让递归方案通过一个简单的方法为我们完成实际的递归hylo call:

waysToMakeChange :: ChangePuzzleArgs -> Int
waysToMakeChange = hylo conquer divide

我们可以确认它在 GHCI 中有效:

*Main> waysToMakeChange (coins, 10)
4
*Main> waysToMakeChange (coins, 100)
292

您是否认为这值得付出努力取决于您。递归方案在这里为我们节省了很少的工作,因为这个问题很容易手工解决。但您可能会发现具体化中间状态使递归结构显式化,而不是隐式地隐含在调用图中。无论如何,如果您想练习递归方案以准备更复杂的任务,那么这是一个有趣的练习。

为了方便起见,完整的工作文件包含在下面。

{-# LANGUAGE DeriveFunctor #-}
import Control.Arrow ( (>>>), (<<<) )

newtype Term f = In {out :: f (Term f)}

type Algebra f a = f a -> a
type Coalgebra f a = a -> f a

cata :: (Functor f) => Algebra f a -> Term f -> a
cata fn = out >>> fmap (cata fn) >>> fn

ana :: (Functor f) => Coalgebra f a -> a -> Term f
ana f = In <<< fmap (ana f) <<< f

hylo :: Functor f => Algebra f b -> Coalgebra f a -> a -> b
hylo alg coalg = ana coalg >>> cata alg

data ChangePuzzle a = Solved Int
                    | Pending {spend, forget :: a}
                    deriving Functor

type Cent = Int
type ChangePuzzleArgs = ([Cent], Cent)
coins :: [Cent]
coins = [50, 25, 10, 5, 1]

divide :: Coalgebra ChangePuzzle ChangePuzzleArgs
divide (_, 0) = Solved 1
divide ([], _) = Solved 0
divide (coins@(x:xs), n) | n < 0 = Solved 0
                         | otherwise = Pending (coins, n - x) (xs, n)

conquer :: Algebra ChangePuzzle Int
conquer (Solved n) = n
conquer (Pending a b) = a + b

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

在 Haskell 中使用递归方案解决变更问题 的相关文章

  • 我该如何实现这个折叠功能呢?

    给出了两种数据类型 颜色 和 植物 data Color Red Pink White Blue Purple Green Yellow deriving Show Eq data Plant Leaf Blossom Color Stal
  • 为什么Racket中foldl的定义方式很奇怪?

    在 Haskell 中 与许多其他函数式语言一样 函数foldl被定义为 例如 foldl 0 1 2 3 4 10 这没关系 因为foldl 0 1 2 3 4 根据定义 0 1 2 3 4 但是 在 球拍 中 foldl 0 1 2 3
  • 在 monad 转换器类型类中使用列表 monad?

    我的目标是创建一个在 ReaderT WriterT 堆栈或 RWS 堆栈中使用列表 monad 的函数 更一般地说 如何在 mtl 类型类 例如 MonadReader MonadWriter 中使用列表 monad 我为什么要尝试这样做
  • 如何通过“cabal build”或“stack build”构建带有图标的项目

    我想构建一个带有图标的可执行文件 通过谷歌搜索 我发现这里的说明 https wiki haskell org Setting an executable icon 但它只能通过编译源文件来工作ghc 如果我想构建一个具有可执行文件的项目c
  • 管道:多个流消费者

    我编写了一个程序来计算语料库中 NGram 的频率 我已经有一个函数 它消耗一串令牌并生成一个订单的 NGram ngram Monad m gt Int gt Conduit t m t trigrams ngram 3 countFre
  • 如何打乱列表?

    如何从一组数字 1 2 3 直到我击中x 我的计划是重新调整列表 1 2 3 并把它砍在x chopAt 3 2 3 1 2 3 chopAt 3 2 1 3 2 1 3 chopAt 3 3 1 2 3 chopAt chopAt x y
  • 类型级别集结合律的证明

    我试图证明类型级函数Union https hackage haskell org package type level sets 0 8 5 0 docs Data Type Set html t Union是关联的 但我不确定应该如何完
  • 合并xml文档

    我遇到的所有关于合并 XML 文档的解决方案都无法实现我的愿望 让我解释 XML 文档 1 a b title Original Section b title Original Child Section b b title Origin
  • Python如何处理无限递归?

    因此 在使用 Python 时 我注意到程序的堆栈大小基本上没有限制 继续对数字执行幂运算 即使在达到数千位之后 精度仍然保持完美 这让我想知道 如果我不小心进入了Python的无限递归循环怎么办 编译器会注意到并抛出堆栈溢出错误吗 或者程
  • 我是否需要采取明确的操作来促进与持久数据结构的共享?

    我来自命令式背景 正在尝试实现一个简单的不相交集 并集查找 数据结构 以获得在 Haskell 中创建和修改 持久 数据结构的一些练习 目标是有一个简单的实现 但我也关心效率 我的问题与此相关 首先 我创建了一个按等级并集的不相交集森林实现
  • 构造微积分中的“Refl”东西?

    在语言中 例如Agda Idris or Haskell对于类型扩展 有一个 键入类似于以下内容的内容 data a b where Refl a a a b意思是a and b是相同的 这样的类型可以定义在结构演算 https en wi
  • 递归检查字符串中的所有字母是否都是大写

    我必须检查递归中所有字母是否都是大写字母 我不知道为什么这不起作用 public static bool IsCapital string str if str Length 1 return int Parse str 0 ToStrin
  • 在 Haskell 中获取玫瑰树的根

    最近我开始学习 Haskell 并在以下练习中遇到困难 Write functions root Rose a gt a and children Rose a gt Rose a that return the value stored
  • Control.Parallel.Strategies 中 Eval 的绑定运算符如何严格评估其参数?

    Control Parallel Strategies 的源代码 http hackage haskell org packages archive parallel 3 1 0 1 doc html src Control Paralle
  • Haskell 处理负参数

    尝试对两个值求和 其中只有一个为负值 例如 1 and 2 soma Float gt Float gt Float soma x1 x2 x1 x2 结果出现错误 为什么
  • 函数式语言中的部分求值和函数内联有什么区别?

    我知道 函数内联就是用函数定义代替函数调用 部分评估是在编译时评估程序的已知 静态 部分 在 C 等命令式语言中 两者之间存在区别 其中运算符与函数不同 但是 在像 Haskell 这样的函数式语言 其中运算符也是函数 中 两者之间有什么区
  • Parsec.Expr 具有不同优先级的重复前缀

    Parsec Expr buildExpressionParser 的文档说 相同优先级的前缀和后缀运算符只能出现一次 即 如果 为前缀否定 则不允许使用 2 但是 我想解析这样的字符串 具体来说 考虑以下语法 sentence ident
  • 递归分割列表函数 LISP

    split list 函数接受一个列表并返回一个由两个列表组成的列表 其中两个列表由输入的交替元素组成 我写了以下内容 defun split list L cond endp L list NIL NIL t let X split li
  • 分组符号最大长度平衡子序列

    将 B 视为分组符号 和 的序列 如果 B 的长度为 0 或 B 具有以下形式之一 则称 B 为平衡序列 X Y 或 X Y 或 X Y 其中 X 和 Y 本身是平衡的 平衡示例 现在的问题是找到一种有效的算法来找到给定输入的最大长度平衡子
  • 求一根棒可以切割的最大片数

    这是完整的问题陈述 给定一根长度为n的绳子 你需要找到最大的绳子数你可以让每一段的长度都在集合 a b c 中给定三个值a b c 我知道可以通过动态规划来实现最优解 但是 我还没有学过这个主题 我需要递归地解决这个问题 对于递归 主要的事

随机推荐

  • Swift:转换到新场景后取消分配 GameScene?

    所以我读过几个关于这个问题的问题 但大多数都是 Objective C 的 我还没有找到任何直接解决 回答这个问题的问题 我是这里编程的新手 所以请彻底解释任何建议 我需要了解如何解除分配游戏结束后我的游戏场景到达 我需要这样做的原因是因为
  • GridBagLayout 网格化不起作用

    我正在尝试创建以下 GUI 但我制作的 GUI 是 我的网格是什么样的 image 网格布局 https i stack imgur com Wpzna png 我不明白为什么我会得到这个输出 因为我已经绘制了一个图表来帮助代码并且它似乎可
  • Java 使用带有域和安全的 RestTemplate 设置安全 cookie

    除了下面的问题之外 我如何设置 Cookie 域并标记为 安全 尝试在 Cookie 上设置其他属性 使用 RestTemplate 设置安全 cookie https stackoverflow com questions 5796078
  • 从 git 存储库进行 pip 安装,由于引用错误而出现错误

    问题描述 在 Windows 10 上使用 Python 3 7 6 我尝试升级直接从 git 存储库安装的包 pip install upgrade git https url of my py package git 然后安装失败 er
  • 如何读取pickle文件?

    我创建了一些数据并将其存储了几次 如下所示 with open filename a as f pickle dump data f 每次文件大小增加 但是当我打开文件时 with open filename rb as f x pickl
  • Android获取颜色作为字符串值

    如果我在资源中定义了一种颜色
  • F#,主格或结构类型

    F 有主格类型系统还是结构类型系统 我知道 OCaml 是结构类型的 尽管 F 似乎并非如此 这是正确的吗 F 是主格 您可以通过一些奇异的机制执行一些结构技巧 但该语言的类型系统主要是主格的
  • 使用 ESC 键清除 Angular / AngularUI 中的输入文本字段

    在我的 Angular 应用程序的几个地方 我需要使用 ESC 键清除用户的输入 问题是 我不知道如何使用文本输入字段 文本区域清除正常 看看这个小提琴 jsFiddle演示问题 http jsfiddle net aGpNf 188 Bi
  • 在textview中显示计时器包含android中的天,小时,分钟和秒

    我正在使用倒计时器在文本视图中显示剩余时间 它工作正常 下面是代码 public class MyCount extends CountDownTimer Context mContext public MyCount long milli
  • 如何在文本框中选择文本,并将插入符号置于所选内容的开头?

    我正在使用一个System Windows Forms TextBox 可以使用键盘来选择文本 将插入符号置于start选择的内容 按住 Shift 并将插入符号向左移动 我想以编程方式做同样的事情 例如 假设我有一个文本框 其中包含文本
  • 如何在调试器中使用 Perl 5.10 功能?

    我无法在 Perl 调试器中评估 现代 Perl 代码 在调试文件中的代码时它可以正常工作 但在提示符下却不行 最小的例子 Activating 5 10 features with E it works perl E say x x Ca
  • 为什么要为请求缓存控制 HTTP 标头?

    我最近经历了this https developer mozilla org en US docs Web HTTP Headers Cache Control文章 它说不仅是响应 请求还可以包括cache control选项 虽然我理解
  • 低基数字段的索引效率

    例如 postgres 数据库中有一个字段 可以为空 它存储枚举值 并且该枚举只有两个值 A B 现在我的所有选择查询在该字段上都有 where 子句 我有一个问题 向该字段添加索引将是一个好方法 否则它不会提高任何性能 因为每行包含 A
  • 如何在 php 上打印非空值[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我对 PHP 很陌生 现在我正在尝试打印非空值 我有以下 php 代码 它会抛出所有值 包括 null 和非 null 在我的网站中 我只
  • 如何检查 JBoss 是否正在 Unix 服务器上运行?

    我下面有一个脚本 我想根据它是否可以在进程列表中找到 jboss 进程来回显 jboss 未运行 或 jboss 正在运行 但是 当我关闭 Jboss 时 它仍然执行 Else 条件并显示 jboss 正在运行 如果我手动执行 pgrep
  • 无法在 C# 中打开 Excel 文件

    我的项目中有以下 C 函数 该函数应该打开并返回现有的 Excel 工作簿对象 Application excelApp private Workbook OpenXL string path string filename try if
  • Rails 4.1.2 - to_param 转义斜杠(并破坏应用程序)

    我在我的应用程序中使用to param创建自定义 URL 此自定义路径包含斜杠 class Machine lt ActiveRecord Base def to param MachinePrettyPath show path self
  • NHibernate二级缓存在没有缓存配置的情况下缓存实体

    我已经在会话工厂上配置了二级缓存 但是对于 POCO 实体 我没有启用缓存 我使用 Fluent NHibernate 来配置 SessionFactory 和 POCO 实体 这是会话工厂的配置 var cfg Fluently Conf
  • GWT 中的简单超链接?

    这应该很简单 但不知怎的 我找不到在 GWT 中创建简单超链接的方法 基本上 我想在用户单击某些内容时加载另一个页面 超级链接 http google web toolkit googlecode com svn javadoc 1 6 c
  • 在 Haskell 中使用递归方案解决变更问题

    我试图从中理解组织形态关于递归方案的博客 https blog sumtypeofway com posts recursion schemes part 4 html 当我运行示例来解决问题时遇到问题改变问题 https en wikip