【问题标题】:Converting a recursion problem code from Python to Common Lisp将递归问题代码从 Python 转换为 Common Lisp
【发布时间】:2021-09-09 05:01:04
【问题描述】:

我正在尝试将用 Python 编写的递归问题(第 4 个问题 here 参见其 repo 页面了解详细信息)转换为(Common)Lisp

为了便于阅读,我稍微编辑了 Python 代码:

def coin(target,coins,res):
    # Default output to target
    min_coins = target
    # Base Case
    if target in coins:
        res[target] = 1
        return 1
    # Return a known result if it happens to be greater than 1
    elif res[target] > 0:
        return res[target]
    else:
        # for every coin value that is <= than target
        for i in [c for c in coins if c <= target]:
            num_coins = 1 + coin(target-i,coins,res)
            # Reset Minimum if we have a new minimum
            if num_coins < min_coins:
                min_coins = num_coins
                res[target] = min_coins
    return min_coins

target = 14
coins = [1,3,5]
res = [0]*(target+1)
print(coin(target,coins,res))
# => returns 4  ( 1 x 1, 1 x 3, 2 x 5)

这是我编写的 Lisp 代码:

(defun coin (target coins res)
  (let ((min_coins target))  
    (if (some (lambda (x) (= target x)) coins)
        (progn
          (setf (aref res target) 1)
          1)
      (if (> (aref res target) 0)
          (aref res target)
        (dolist (i (remove-if-not (lambda (x) (<= x target)) coins))
          (let ((num_coins (1+ (coin (- target i) coins res))))
            (when (< num_coins min_coins)
              (setf min_coins num_coins)
              (setf (aref res target) min_coins))
            min_coins))))))

(coin 14 '(1 3 5) (make-array 15 :initial-element 0) )

当它被执行时,它会因为这个错误而停止:

The value
  NIL
is not of type
  NUMBER

如何安排它才能正常运行?

更新:

(trace coin) 之后是输出:

CL-USER> (coin 14 '(1 3 5) (make-array 15 :initial-element 0) )
  0: (COIN 14 (1 3 5) #(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0))
    1: (COIN 13 (1 3 5) #(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0))
      2: (COIN 12 (1 3 5) #(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0))
        3: (COIN 11 (1 3 5) #(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0))
          4: (COIN 10 (1 3 5) #(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0))
            5: (COIN 9 (1 3 5) #(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0))
              6: (COIN 8 (1 3 5) #(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0))
                7: (COIN 7 (1 3 5) #(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0))
                  8: (COIN 6 (1 3 5) #(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0))
                    9: (COIN 5 (1 3 5) #(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0))
                    9: COIN returned 1
                    9: (COIN 3 (1 3 5) #(0 0 0 0 0 1 2 0 0 0 0 0 0 0 0))
                    9: COIN returned 1
                    9: (COIN 1 (1 3 5) #(0 0 0 1 0 1 2 0 0 0 0 0 0 0 0))
                    9: COIN returned 1
                  8: COIN returned NIL
; Evaluation aborted on #<TYPE-ERROR expected-type: NUMBER datum: NIL>.

【问题讨论】:

  • 你可以用(member target coins)代替some
  • 错误发生在哪一行?
  • 一系列if/elif/else应该翻译成cond而不是嵌套if
  • 更新了 OP。添加了trace 输出,如果有帮助的话。
  • 是的,应该使用cond 而不是if/elif/else 我知道但是当我开始编写代码时,我已经忘记了:)

标签: python recursion common-lisp coin-change


【解决方案1】:

正确性

您的代码失败,因为您的 dolist 返回 nil

你需要更换

(dolist (i (remove-if-not (lambda (x) (<= x target)) coins)) ...)

(dolist (i (remove-if-not (lambda (x) (<= x target)) coins) min_coins) ...)

效率

您正在 Python 中通过在 for 循环中使用 [] 和在 Lisp 中通过在 dolist 中使用 remove-if-not 创建免费列表。 众所周知的“足够聪明的编译器”应该能够消除它,但 Python 3 不够聪明,SBCL 也不够聪明。

风格

代码可读性很重要。我建议修改你的代码:

def coin(target,coins,res):
    # Default output to target
    # Base Case
    if target in coins:
        res[target] = 1
        return 1
    # Return a known result if it happens to be greater than 1
    if res[target] > 0:
        return res[target]
    # for every coin value that is <= than target
    min_coins = target
    for i in target:
        if i <= target:
            num_coins = 1 + coin(target-i,coins,res)
            # Reset Minimum if we have a new minimum
            if num_coins < min_coins:
                min_coins = num_coins
                res[target] = min_coins
    return min_coins

(defun coin (target coins res)
  (cond ((find target coins)
         (setf (aref res target) 1))
        ((plusp (aref res target))
         (aref res target))
        (t
         (let ((min-coins target))
           (dolist (i coins min-coins)
             (when (<= i target)
               (let ((num-coins (1+ (coin (- target i) coins res))))
                 (when (< num-coins min-coins)
                   (setf min-coins num-coins)
                   (setf (aref res target) min-coins)))))))))

【讨论】:

  • 或者只是将min_coins移出if,就像在Python中一样。
  • 我试过了,效果很好。正如@Barmar 建议的那样,我已经尝试将min_coins 移出if,它停止给出错误但给出了错误的结果。所以我认为最实用的解决方案是将min_coins 作为第三个参数添加到dolist
  • 根据PS1 Inefficiency 的评论,关于通过在 for 循环中使用 [] 在 Python 中创建免费列表:是的,乍一看似乎效率低下,但它是算法的必要部分。实际上,起初我试图想出一种更有效的算法并在 Lisp 中实现它,但失败了,我决定将它原样转换为 Lisp。
  • @LarsMalmsteen:我的代码不会创建额外的列表,而是在循环内使用检查,无论是在 Python 还是在 Lisp 中。工作正常。
【解决方案2】:

Common Lisp 中一个比较相似的版本是这样的:

(defun coin (target coins res)
  (let ((min-coins target))
    (cond ((member target coins)
           (setf (elt res target) 1)
           (return-from coin 1))
          ((plusp (elt res target))
           (return-from coin (elt res target)))
          (t (loop for i in (loop for c in coins when (<= c target) collect c)
                   for num-coins = (1+ (coin (- target i) coins res))
                   if (< num-coins min-coins)
                     do (setf min-coins num-coins)
                   else
                     do (setf (elt res target) min-coins))))
    (return-from coin min-coins)))

(let* ((target 14)
       (coins  '(1 3 5))
       (res    (make-list (1+ target) :initial-element 0)))
  (print (coin target coins res)))

Lisp 和 Python 之间有一些基本的区别。其中四个是:

  • 在 Lisp 中,所有表达式都返回值(零、一个 或更多)。因此,调用returnreturn-from 通常不是Lisp 风格,就像在Python 中那样。为了适应 Lisp 风格,可以将代码转换为没有返回的版本。但这可能会引入错误,如果不小心处理的话。 return-from 需要 block 的名称才能返回。

  • 在 Lisp 中,基本的数据结构是单链表。在 Python 中,它是数组之王。因此,可能需要不同的运算符并且性能不同。在 Lisp 中,诸如遍历列表、追加到末尾、随机访问等操作成本很高。对于某些用途,这可能无关紧要,但通常首选使用更适合该目的的数据结构。所以在这里我使用了 Lisp 列表,用向量(一维数组)替换它们可能会很有用。还有一种 sequence 类型的想法,它为列表和向量提供通用操作。操作有eltremoveremove-ifconcatenatemap 等等。

  • 不能在某些表达式序列的中间引入局部变量。需要像 let 这样的特殊构造。

  • Lisp 中的迭代可以通过 LOOP 完成

【讨论】:

  • 即使在 Lisp 中,在进行随机访问时也会使用向量!
【解决方案3】:

是的,我用了不同的方法,但得出了相似的结论。添加了depth 参数来确定它在递归期间做了什么。然后用它来遵循 Lisp 脚本中的逻辑。使用此备忘单作为参考https://jtra.cz/stuff/lisp/sclr/index.html

#! /usr/bin/env python3

def coin( target, coins, result, depth ):
    min_coins = target  ##  Default output to target
    print( depth, result )
    if target in coins:  ##  Base Case
        result[ target ] = 1
        return 1
    # Return a known result if it happens to be greater than 1
    elif result[ target ] > 0:
        return result[ target ]
    else:
        # for every coin value that is <= than target
        for i in [ c for c in coins if c <= target ]:
            num_coins = 1 +coin( target -i,  coins,  result,  depth +1 )
            # reset Minimum if we have a new minimum
            if num_coins < min_coins:
                min_coins = num_coins
                result[ target ] = min_coins
    return min_coins

target = 14
coins = [ 1, 3, 5 ]
result = [0] *( target +1 )

print( coin( target, coins, result, 0 ) )
# => returns 4  ( 1 x 1, 1 x 3, 2 x 5)


#! /usr/bin/sbcl --script

(defun coin (target coins result depth)
    "recursive coin count"
    (let ((min_coins target))  ;;  min_coins = target

        (cond ((member target coins)  ;;  if target in coins:

            (progn 
                (setf (aref result target) 1)  ;;  result[ target ] = 1
                1))  ;;  return 1

            ((> (aref result target) 0)  ;;  elif result[ target ] > 0:
                (aref result target))  ;;  return result[ target ]

            ((progn  ;;  for every coin value that is <= target

                (dolist (i (remove-if-not (lambda (x) (<= x target)) coins))

                    ;;  num_coins = 1 +coin( target -i,  coins,  result,  depth +1 )
                    (let ((num_coins (1+ (coin (- target i) coins result (+ depth 1)))))
 
                      (when (< num_coins min_coins)  ;;  if num_coins < min_coins:

                        (setf min_coins num_coins)  ;;  min_coins = num_coins

                        (setf (aref result target) min_coins))))  ;;  result[ target ] = min_coins

                min_coins))  ;;  return min_coins
        )
    )
)

(defvar c 14)
(print (coin c '(1 3 5) (make-array (+ c 1) :initial-element 0) 0 ) )

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-15
    • 2015-08-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-09
    相关资源
    最近更新 更多