使用中缀表示法的算术计算



以下函数用于计算带括号的中缀表示法:

(define (mycalc ll)
(cond
[(= 1 (length ll))
(first ll)]
[(empty? (flatten(rest ll)))
(first ll)]
[(= 2 (length ll))
(printf "Error: only 2 elements: ~a" ll)
(exit)]
[(list? (first ll))
(mycalc (append (list (mycalc (first ll)) (second ll) (third ll)) (rest(rest(rest ll)))))]
[(list? (third ll))
(mycalc (append (list (first ll) (second ll) (mycalc (third ll)) (rest(rest(rest ll))))))]
[(= 3 (length ll))
((second ll) (first ll) (third ll))]
[else
(mycalc (append (list ((second ll) (first ll) (third ll))) (rest(rest(rest ll)))))]))

测试:

(define L (list (list 3 * 6) + 5 + (list 2 - (list 2 * (list 21 / 3)))))
(mycalc L)

输出:

11

然而,它不适用于以下版本的列表:

(define L '((3 * 6) + 5 + (2 - (2 * (21 / 3)))))
(mycalc L)

以下是错误:

application: not a procedure;
expected a procedure that can be applied to arguments
given: '*
arguments...:

如何将'*识别为纠正此错误的函数?

编辑:

我用'match'表示'*etc,现在这个简短的函数运行良好:

(define (mycalc ll)
(define (solve a b c)
(match b
['+ (+ a c)]
['- (- a c)]
['* (* a c)]
['/ (/ a c)]))
(cond
[(= 1 (length ll))
(first ll)]
[(list? (first ll))
(mycalc (cons (mycalc (first ll))
(rest ll)))]
[(list? (third ll))
(mycalc (append (list (first ll)
(second ll)
(mycalc (third ll)))
(rest(rest(rest ll)))))]
[else
(mycalc (cons (solve (first ll) (second ll) (third ll))
(rest(rest(rest ll)))))]))

测试:

(define L '((3 * 6) + 5 + (2 - (2 * (21 / 3)))))
(mycalc L)

输出:

11

请注意,此函数不处理优先级,如果不使用括号,它将严格从左到右求解。

尝试的主要问题是您使用的是quote,正如您所知,它将自己"分布"在列表的元素上,因此您最终会将'(2 * x)转换为(list '2 '* 'x)

#lang racket
(define (f x)
(my-calc '(2 * x))) ; this is completely broken because of quote

这有两个问题,'*'x。这里'*不是函数;它是一个符号,类似于字符串,因为它与函数*没有关系,只是它的字符恰好与绑定到*函数的标识符名称匹配。'x也是如此,只是它甚至不是一个已知的东西:你甚至无法查找它!

两者的根源:quote破坏了词汇范围。

如果您想要在不遇到这些问题的情况下使用中缀语法,有几个选项,并且所有选项都涉及在编译时转换的语法。这样就保留了词法范围。

1:#lang sweet-exp

sweet-exp语言修改阅读器,以便每当它看到{ ... }时,它都会将里面的表达式读取为中缀。您可以选择编写{{3 * 6} + 5}而不是(+ (* 3 6) 5),这将是等效的。

#lang sweet-exp racket
(define (f x)
{2 * x})
(define (p x)
{{3 * (sqr x)} + {6 * x} + 5})

2:(require infix)

infix库提供$作为宏,这样@${ ... }将被解释为中缀,因此您可以选择写入@${(3 * 6) + 5}而不是(+ (* 3 6) 5)

#lang at-exp racket
(require infix)
(define (f x)
@${2 * x})
(define (p x)
@${(3 * x^2) + (6 * x) + 5})

3:定义自己的宏

您可以定义一个简单的中缀宏,如下所示:

#lang racket
(require syntax/parse/define)
(define-syntax-parser infix
#:literals [+ *]
#:datum-literals [^]
[(_ n:number) #'n]
[(_ x:id) #'x]
[(_ (a:expr + b:expr)) #'(+ (infix a) (infix b))]
[(_ (a:expr * b:expr)) #'(* (infix a) (infix b))]
[(_ (a:expr ^ b:expr)) #'(expt (infix a) (infix b))])

如果希望+*能够像(x + y + z)一样多次链接在一起,则可以使用...+扩展宏以重复一次或多次,使用~seq扩展宏以将模式分组在一起。

(define-syntax-parser infix
#:literals [+ *]
#:datum-literals [^]
[(_ n:number) #'n]
[(_ x:id) #'x]
[(_ (a:expr (~seq + b:expr) ...+)) #'(+ (infix a) (infix b) ...)]
[(_ (a:expr (~seq * b:expr) ...+)) #'(* (infix a) (infix b) ...)]
[(_ (a:expr ^ b:expr)) #'(expt (infix a) (infix b))])
(define (f x)
(infix (2 * x)))
(define (p x)
(infix ((3 * (x ^ 2)) + (6 * x) + 5)))

为了详细说明@Alex Knauth的答案:在第一种情况下,使用list来构建列表,单个列表元素没有被引用,因此被评估。(display L)应该或多或少地产生这样的输出(细节因实现而异):

((3 #<procedure * (#:optional _ _ . _)> 6) #<procedure + (#:optional _ _ . _)>
5 #<procedure + (#:optional _ _ . _)> (2 #<procedure - (#:optional _ _ . _)> 
(2 #<procedure * (#:optional _ _ . _)> (21 #<procedure / (#:optional _ _ . _)> 3))))

您可以清楚地看到,该列表包含各种程序。然而,在第二种情况下,它引用了整个列表,不计算单个列表元素。因此,该列表不包含任何过程,只包含符号,(display L)生成以下内容:

((3 * 6) + 5 + (2 - (2 * (21 / 3))))

最新更新