将递归问题代码从 Python 转换为 Common Lisp
Converting a recursion problem code from Python to Common Lisp
我正在尝试将用 Python 编写的递归问题(第 4 个问题 here 请参阅其 repo 页面以获取详细信息)转换为(通用)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>.
正确性
您的代码失败,因为您的 dolist
returns 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)))))))))
是的,我用不同的方式做了,但得出了相似的结论。添加了 depth
参数以弄清楚递归期间它在做什么。然后使用它来遵循 Lisp 脚本中的逻辑。使用此作弊-sheet 供参考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 ) )
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 中所有表达式 return 值(零,一个,或更多)。因此,像 Python 中那样调用 return
或 return-from
通常不是 Lisp 风格。为了适应 Lisp 风格,可以将代码转换为没有 returns 的版本。但如果做得不仔细,可能会引入错误。 return-from
需要 方块的名称 到 return 来自.
Lisp的基本数据结构是单向链表。在 Python 中,它是数组之王。因此可能需要不同的运算符并且性能不同。在 Lisp 中,遍历列表、追加到末尾、随机访问等都是代价高昂的。对于某些用途,这可能无关紧要,但通常首选使用更适合目的的数据结构。所以在这里我使用了 Lisp 列表,将它们替换为向量(一维数组)可能会有用。还有一个类型 sequence 的想法,它为列表和向量提供通用操作。操作是 elt
、remove
、remove-if
、concatenate
、map
和其他一些。
不能在某些表达式序列的中间引入局部变量。需要像 let
这样的特殊结构。
Lisp 中的迭代可以用 LOOP
我正在尝试将用 Python 编写的递归问题(第 4 个问题 here 请参阅其 repo 页面以获取详细信息)转换为(通用)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>.
正确性
您的代码失败,因为您的 dolist
returns 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)))))))))
是的,我用不同的方式做了,但得出了相似的结论。添加了 depth
参数以弄清楚递归期间它在做什么。然后使用它来遵循 Lisp 脚本中的逻辑。使用此作弊-sheet 供参考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 ) )
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 中所有表达式 return 值(零,一个,或更多)。因此,像 Python 中那样调用
return
或return-from
通常不是 Lisp 风格。为了适应 Lisp 风格,可以将代码转换为没有 returns 的版本。但如果做得不仔细,可能会引入错误。return-from
需要 方块的名称 到 return 来自.Lisp的基本数据结构是单向链表。在 Python 中,它是数组之王。因此可能需要不同的运算符并且性能不同。在 Lisp 中,遍历列表、追加到末尾、随机访问等都是代价高昂的。对于某些用途,这可能无关紧要,但通常首选使用更适合目的的数据结构。所以在这里我使用了 Lisp 列表,将它们替换为向量(一维数组)可能会有用。还有一个类型 sequence 的想法,它为列表和向量提供通用操作。操作是
elt
、remove
、remove-if
、concatenate
、map
和其他一些。不能在某些表达式序列的中间引入局部变量。需要像
let
这样的特殊结构。Lisp 中的迭代可以用 LOOP