【问题标题】:Scheme Infix to Postfix方案中缀到后缀
【发布时间】:2010-05-18 13:40:05
【问题描述】:

让我确定这是课堂作业的一部分,所以我绝对不是在寻找完整的代码答案。本质上,我们需要在 Scheme 中编写一个转换器,它接受一个表示中缀格式的数学方程的列表,然后输出一个带有后缀格式的方程的列表。

我们已经获得了执行此操作的算法,非常简单。问题是对使用任何可用的命令式语言功能都有限制。我无法弄清楚如何以纯粹的功能方式做到这一点。这是我们在我的程序中对函数式编程的第一次介绍。

我知道我将像这样使用递归来遍历中缀表达式中的项目列表。

(define (itp ifExpr)
  (
    ; do some processing using cond statement
    (itp (cdr ifExpr))
  ))

我已经实现了所有的处理(至少在不知道其余部分如何做的情况下尽我所能),但是我用来实现它的算法需要将运算符压入堆栈并稍后使用。我的问题是如何在这个函数中实现一个堆栈,该堆栈也可用于所有递归调用?

【问题讨论】:

  • 如果您想将表达式从中缀更改为前缀,那么将列表中的第一个元素与列表中的第二个元素交换那么简单吗?您提到必须强制执行,这是否意味着您被限制使用某些功能?
  • @grettke:可能更长的表达式——比如1 + 2 * 3——也需要处理。
  • 表达式可以是任意长度,包括括号。执行转换的实际过程不是问题,它只是使用我的问题的功能语言。 :) Michal Marczyk 下面的答案似乎正是我所需要的。使用他教给我的东西,我绝对应该能够完成这项工作。

标签: scheme functional-programming


【解决方案1】:

(根据 OP 的评论进行了更新;请参阅原始答案下方的新部分。)


使用堆栈列表并将其设为循环变量之一。例如

(let loop ((stack (list))
           ... ; other loop variables here,
               ; like e.g. what remains of the infix expression
            )
  ... ; loop body
  )

然后,每当您想在下一次迭代中更改堆栈上的内容时,基本上就这样做。

(loop (cons 'foo stack) ...)

另外请注意,如果您需要按顺序进行一堆“更新”,您通常可以使用let* 表单对其进行建模。这实际上不适用于 Scheme 中的向量(尽管它确实适用于 Clojure 的持久向量,如果您愿意查看它们),但它适用于标量值和列表,以及 SRFI 40/41 流。


针对您关于循环被排除为“必要”功能的评论:

(let loop ((foo foo-val)
           (bar bar-val))
  (do-stuff))

是语法糖

(letrec ((loop (lambda (foo bar) (do-stuff))))
  (loop foo-val bar-val))

letrec 然后扩展为let 的形式,它可能在内部使用等效于set! 或本地define 的东西,但被认为是完美的功能。顺便说一句,您可以随意使用其他符号代替loop。此外,这种let 被称为“命名let”(或有时“标记”)。

你可能会记得let的基本形式:

(let ((foo foo-val)
      (bar bar-val))
  (do-stuff))

也是巧妙使用lambda的语法糖:

((lambda (foo bar) (do-stuff)) foo-val bar-val)

所以这一切都归结为程序应用程序,就像在 Scheme 中一样。

命名为let 使自递归更漂亮,仅此而已;并且我相信您已经知道,(自)带有尾调用的递归是在以函数方式对迭代计算过程进行建模时要走的路

很明显,这种特殊的“循环”结构也非常适合命令式编程——如果你想要这样做,只需在循环体中使用set! 或数据结构修改器——但如果你远离破坏性函数调用时,循环遍历递归或标记的let 本身并没有什么本质上的必要性。 事实上,通过递归循环是函数式编程中最基本的技术之一,而这类作业的重点必须是准确地教... :-)

如果您真的不确定是否可以使用它(或者如果您只使用命名let 是否足够清楚以了解所涉及的模式),那么您可以按照上面的说明对其进行脱糖(可能使用本地的define 而不是letrec)。

【讨论】:

  • 我很欣赏这个答案,而且看起来它会起作用,但是作业状态我不能使用任何命令式语言功能,包括循环。 :(
  • 这不是命令式循环;它是某种递归模式的语法糖。我稍后会编辑解释。
  • 太棒了,非常感谢您的帮助。这应该足以让我继续前进!
【解决方案2】:

我不确定我是否正确理解了这一切,但这个更简单的解决方案有什么问题:

第一:

你测试你的论点是否确实是一个列表:

如果是:将函数的 MAP 附加到尾部(map postfixer (cdr lst))到仅包含头部的列表中。 Map 只是将后缀再次应用于尾部的每个顺序元素。

如果不是,则原样返回参数。

我的实现中的三行Scheme,翻译过来:

(postfixer '(= 7 (/ (+ 10 4) 2)))

收件人:

(7 ((10 4 +) 2 /) =)

通过 map 的递归不需要循环,甚至不需要尾循环,不需要突变,并且通过应用 map 显示函数风格。除非我在这里完全误解了您的观点,否则我认为不需要上述所有复杂性。

编辑:哦,现在我读的是中缀,而不是前缀,到后缀。好吧,除了采用第二个元素而不是第一个元素之外,同样的一般想法也适用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-06-14
    • 1970-01-01
    • 2015-06-11
    • 1970-01-01
    • 1970-01-01
    • 2016-07-22
    • 1970-01-01
    相关资源
    最近更新 更多