Racket编写二十一点手牌评估函数遇异常结果求排查
问题分析与修正
错误原因
for/list使用错误:for/list的作用是遍历集合,将每个元素处理后的结果收集成列表返回。你的代码会把每张牌的单次计算结果(2和4)组成列表返回,这就是得到(2 4)的核心原因,而非你需要的手牌总和。- 变量未实现累加更新:
let中绑定的numAces和ttl是不可变变量,直接调用add1 numAces或(+ (car i) ttl)仅会返回新值,不会修改原变量的数值,相当于每次计算都基于初始值0操作,没有真正保存累加结果。 - 无效条件判断:
(empty? hand)放在循环内部毫无意义,for/list只会在hand非空时遍历元素,该条件永远不会触发。
修正方案
推荐使用**函数式风格的foldl**实现,符合Racket的设计理念,避免可变状态:
(define (eval-hand hand) (let-values ([(ttl numAces) (foldl (lambda (card acc) (let ([current-ttl (car acc)] [current-aces (cadr acc)] [rank (car card)]) (cond [(equal? rank 'A) (list current-ttl (add1 current-aces))] [(integer? rank) (list (+ current-ttl rank) current-aces)] [else (list (+ current-ttl 10) current-aces)]))) '(0 0) hand)]) ; 调整A的取值(1或11),取不超过21的最大可能值 (let loop ([total ttl] [aces numAces]) (cond [(or (zero? aces) (<= total 21)) total] [else (loop (- total 10) (sub1 aces))])))) (define playerhand '((2 Diamonds) (4 Clubs))) (display (eval-hand playerhand)) ; 输出6
若习惯可变状态写法,可使用let*配合set!:
(define (eval-hand hand) (let* ([numAces 0] [ttl 0]) (for ([card hand]) (let ([rank (car card)]) (cond [(equal? rank 'A) (set! numAces (add1 numAces))] [(integer? rank) (set! ttl (+ ttl rank))] [else (set! ttl (+ ttl 10))]))) ; 调整A的取值 (let loop ([total ttl] [aces numAces]) (cond [(or (zero? aces) (<= total 21)) total] [else (loop (- total 10) (sub1 aces))])))) (define playerhand '((2 Diamonds) (4 Clubs))) (display (eval-hand playerhand)) ; 输出6
说明
两种方案都会先计算基础总和与A的数量,再通过将A的取值从11调整为1,确保最终总和不超过21,符合二十一点的核心规则。函数式的foldl方案更安全易维护,更贴合Racket的函数式编程范式。
内容的提问来源于stack exchange,提问作者spaceman777
相关产品推荐
相关产品推荐

