长整数到字符串,反之亦然,数字运算
Long integer to string and vice versa, operation with digits
解决Euler项目问题我得到我需要对一个长数字的数字进行操作,通常作为一个字符串。我在 linux、emacs、slime 和 sbcl 中工作。
比如求2^1⁰⁰⁰这个幂的位数之和,我是这样算的,
1) 获得力量
CL-USER> (defparameter *answer-as-integer* (expt 2 1000))
*ANSWER-AS-INTEGER*
CL-USER> *ANSWER-AS-INTEGER*
10715086071862673209484250490600018105614048117055336074437503883703510511249361224931983788156958581275946729175531468251871452856923140435984577574698574803934567774824230985421074605062371141877954182153046474983581941267398767559165543946077062914571196477686542167660429831652624386837205668069376
感谢 Common Lisp,这非常容易。现在我认为一个好的方法应该是对这个数字序列应用reduce。
2) 获取字符串
CL-USER> (defparameter *answer-as-string* (write-to-string *ANSWER-AS-INTEGER*))
*ANSWER-AS-STRING*
CL-USER> *ANSWER-AS-STRING*
"10715086071862673209484250490600018105614048117055336074437503883703510511249361224931983788156958581275946729175531468251871452856923140435984577574698574803934567774824230985421074605062371141877954182153046474983581941267398767559165543946077062914571196477686542167660429831652624386837205668069376"
现在我有了序列,所以应用 reduce,但我得到了错误的东西:这是一个字符,所以我将转换字符应用到整数:
CL-USER> (reduce #'(lambda (x y) (+ (digit-char-p x) (digit-char-p y))) *ANSWER-AS-string*)
但我得到一个错误:
The value 1 is not of type CHARACTER.
[Condition of type TYPE-ERROR]
Restarts:
0: [RETRY] Retry SLIME REPL evaluation request.
1: [*ABORT] Return to SLIME's top level.
2: [ABORT] Abort thread (#<THREAD "repl-thread" RUNNING {1005DE80B3}>)
Backtrace:
0: (DIGIT-CHAR-P 1) [optional]
1: ((LAMBDA (X Y)) 1 #)
2: (REDUCE #<FUNCTION (LAMBDA (X Y)) {100523C79B}> "1071508607186267320948425049060001810561404811705533607443750388370351051124936122493198378815695858127594672917553146825187145285692314043598457757469..
3: (SB-INT:SIMPLE-EVAL-IN-LEXENV (REDUCE (FUNCTION (LAMBDA # #)) *ANSWER-AS-STRING*) #<NULL-LEXENV>)
4: (EVAL (REDUCE (FUNCTION (LAMBDA # #)) *ANSWER-AS-STRING*))
5: (SWANK::EVAL-REGION "(reduce #'(lambda (x y) (+ (digit-char-p x) (digit-char-p y))) *ANSWER-AS-string*) ..)
Locals:
SB-DEBUG::ARG-0 = "(reduce #'(lambda (x y) (+ (digit-char-p x) (digit-char-p y))) *ANSWER-AS-string*)\n"
6: ((LAMBDA NIL :IN SWANK-REPL::REPL-EVAL))
7: (SWANK-REPL::TRACK-PACKAGE #<CLOSURE (LAMBDA NIL :IN SWANK-REPL::REPL-EVAL) {10051F065B}>)
8: (SWANK::CALL-WITH-RETRY-RESTART "Retry SLIME REPL evaluation request." #<CLOSURE (LAMBDA NIL :IN SWANK-REPL::REPL-EVAL) {10051F059B}>)
9: (SWANK::CALL-WITH-BUFFER-SYNTAX NIL #<CLOSURE (LAMBDA NIL :IN SWANK-REPL::REPL-EVAL) {10051F057B}>)
10: (SWANK-REPL::REPL-EVAL "(reduce #'(lambda (x y) (+ (digit-char-p x) (digit-char-p y))) *ANSWER-AS-string*) ..)
11: (SB-INT:SIMPLE-EVAL-IN-LEXENV (SWANK-REPL:LISTENER-EVAL "(reduce #'(lambda (x y) (+ (digit-char-p x) (digit-char-p y))) *ANSWER-AS-string*) ..)
12: (EVAL (SWANK-REPL:LISTENER-EVAL "(reduce #'(lambda (x y) (+ (digit-char-p x) (digit-char-p y))) *ANSWER-AS-string*) ..)
13: (SWANK:EVAL-FOR-EMACS (SWANK-REPL:LISTENER-EVAL "(reduce #'(lambda (x y) (+ (digit-char-p x) (digit-char-p y))) *ANSWER-AS-string*) ..)
14: (SWANK::PROCESS-REQUESTS NIL)
15: ((LAMBDA NIL :IN SWANK::HANDLE-REQUESTS))
16: ((LAMBDA NIL :IN SWANK::HANDLE-REQUESTS))
17: (SWANK/SBCL::CALL-WITH-BREAK-HOOK #<FUNCTION SWANK:SWANK-DEBUGGER-HOOK> #<CLOSURE (LAMBDA NIL :IN SWANK::HANDLE-REQUESTS) {1005DF00DB}>)
18: ((FLET SWANK/BACKEND:CALL-WITH-DEBUGGER-HOOK :IN "/home/anquegi/quicklisp/dists/quicklisp/software/slime-2.13/swank/sbcl.lisp") #<FUNCTION SWANK:SWANK-DEBUGGER-HOOK> #<CLOSURE (LAMBDA NIL :IN SWANK::H..
19: (SWANK::CALL-WITH-BINDINGS ((*STANDARD-OUTPUT* . #1=#<SWANK/GRAY::SLIME-OUTPUT-STREAM {1005DCF343}>) (*STANDARD-INPUT* . #2=#<SWANK/GRAY::SLIME-INPUT-STREAM {1006160003}>) (*TRACE-OUTPUT* . #1#) (*ERR..
20: (SWANK::HANDLE-REQUESTS #<SWANK::MULTITHREADED-CONNECTION {1005078BE3}> NIL)
21: ((FLET #:WITHOUT-INTERRUPTS-BODY-1226 :IN SB-THREAD::INITIAL-THREAD-FUNCTION-TRAMPOLINE))
22: ((FLET SB-THREAD::WITH-MUTEX-THUNK :IN SB-THREAD::INITIAL-THREAD-FUNCTION-TRAMPOLINE))
23: ((FLET #:WITHOUT-INTERRUPTS-BODY-647 :IN SB-THREAD::CALL-WITH-MUTEX))
24: (SB-THREAD::CALL-WITH-MUTEX #<CLOSURE (FLET SB-THREAD::WITH-MUTEX-THUNK :IN SB-THREAD::INITIAL-THREAD-FUNCTION-TRAMPOLINE) {7FFFEA81ED1B}> #<SB-THREAD:MUTEX "thread result lock" owner: #<SB-THREAD:THR..
25: (SB-THREAD::INITIAL-THREAD-FUNCTION-TRAMPOLINE #<SB-THREAD:THREAD "repl-thread" RUNNING {1005DE80B3}> #S(SB-THREAD:SEMAPHORE :NAME "Thread setup semaphore" :%COUNT 0 :WAITCOUNT 0 :MUTEX #<SB-THREAD:MU..
26: ("foreign function: call_into_lisp")
27: ("foreign function: new_thread_trampoline")
如果我尝试将其用作数字而不进行转换,解释器会说这不是整数,所以我会发疯,因为这是正确的,但上面的代码不是:
(reduce #'+ *ANSWER-AS-string*)
The value # is not of type NUMBER.
[Condition of type TYPE-ERROR]
Restarts:
0: [RETRY] Retry SLIME REPL evaluation request.
1: [*ABORT] Return to SLIME's top level.
2: [ABORT] Abort thread (#<THREAD "repl-thread" RUNNING {1005DE80B3}>)
Backtrace:
0: (+ # #[=14=])
1: (REDUCE #<FUNCTION +> "107150860718626732094842504906000181056140481170553360744375038837035105112493612249319837881569585812759467291755314682518714528569231404359845775746985748039345677748242309854..
2: (SB-INT:SIMPLE-EVAL-IN-LEXENV (REDUCE (FUNCTION +) *ANSWER-AS-STRING*) #<NULL-LEXENV>)
3: (EVAL (REDUCE (FUNCTION +) *ANSWER-AS-STRING*))
4: (SWANK::EVAL-REGION "(reduce #'+ *ANSWER-AS-string*) ..)
Locals:
SB-DEBUG::ARG-0 = "(reduce #'+ *ANSWER-AS-string*)\n"
5: ((LAMBDA NIL :IN SWANK-REPL::REPL-EVAL))
6: (SWANK-REPL::TRACK-PACKAGE #<CLOSURE (LAMBDA NIL :IN SWANK-REPL::REPL-EVAL) {100566384B}>)
7: (SWANK::CALL-WITH-RETRY-RESTART "Retry SLIME REPL evaluation request." #<CLOSURE (LAMBDA NIL :IN SWANK-REPL::REPL-EVAL) {100566378B}>)
8: (SWANK::CALL-WITH-BUFFER-SYNTAX NIL #<CLOSURE (LAMBDA NIL :IN SWANK-REPL::REPL-EVAL) {100566376B}>)
9: (SWANK-REPL::REPL-EVAL "(reduce #'+ *ANSWER-AS-string*) ..)
10: (SB-INT:SIMPLE-EVAL-IN-LEXENV (SWANK-REPL:LISTENER-EVAL "(reduce #'+ *ANSWER-AS-string*) ..)
11: (EVAL (SWANK-REPL:LISTENER-EVAL "(reduce #'+ *ANSWER-AS-string*) ..)
12: (SWANK:EVAL-FOR-EMACS (SWANK-REPL:LISTENER-EVAL "(reduce #'+ *ANSWER-AS-string*) ..)
13: (SWANK::PROCESS-REQUESTS NIL)
14: ((LAMBDA NIL :IN SWANK::HANDLE-REQUESTS))
15: ((LAMBDA NIL :IN SWANK::HANDLE-REQUESTS))
16: (SWANK/SBCL::CALL-WITH-BREAK-HOOK #<FUNCTION SWANK:SWANK-DEBUGGER-HOOK> #<CLOSURE (LAMBDA NIL :IN SWANK::HANDLE-REQUESTS) {1005DF00DB}>)
17: ((FLET SWANK/BACKEND:CALL-WITH-DEBUGGER-HOOK :IN "/home/anquegi/quicklisp/dists/quicklisp/software/slime-2.13/swank/sbcl.lisp") #<FUNCTION SWANK:SWANK-DEBUGGER-HOOK> #<CLOSURE (LAMBDA NIL :IN SWANK::H..
18: (SWANK::CALL-WITH-BINDINGS ((*STANDARD-OUTPUT* . #1=#<SWANK/GRAY::SLIME-OUTPUT-STREAM {1005DCF343}>) (*STANDARD-INPUT* . #2=#<SWANK/GRAY::SLIME-INPUT-STREAM {1006160003}>) (*TRACE-OUTPUT* . #1#) (*ERR..
19: (SWANK::HANDLE-REQUESTS #<SWANK::MULTITHREADED-CONNECTION {1005078BE3}> NIL)
20: ((FLET #:WITHOUT-INTERRUPTS-BODY-1226 :IN SB-THREAD::INITIAL-THREAD-FUNCTION-TRAMPOLINE))
21: ((FLET SB-THREAD::WITH-MUTEX-THUNK :IN SB-THREAD::INITIAL-THREAD-FUNCTION-TRAMPOLINE))
22: ((FLET #:WITHOUT-INTERRUPTS-BODY-647 :IN SB-THREAD::CALL-WITH-MUTEX))
23: (SB-THREAD::CALL-WITH-MUTEX #<CLOSURE (FLET SB-THREAD::WITH-MUTEX-THUNK :IN SB-THREAD::INITIAL-THREAD-FUNCTION-TRAMPOLINE) {7FFFEA81ED1B}> #<SB-THREAD:MUTEX "thread result lock" owner: #<SB-THREAD:THR..
24: (SB-THREAD::INITIAL-THREAD-FUNCTION-TRAMPOLINE #<SB-THREAD:THREAD "repl-thread" RUNNING {1005DE80B3}> #S(SB-THREAD:SEMAPHORE :NAME "Thread setup semaphore" :%COUNT 0 :WAITCOUNT 0 :MUTEX #<SB-THREAD:MU..
25: ("foreign function: call_into_lisp")
26: ("foreign function: new_thread_trampoline")
我通过这样做解决了这个问题:
CL-USER> (defun sum-digits-in-a-string (str)
(reduce #'+ (map 'list #'digit-char-p str)))
SUM-DIGITS-IN-A-STRING
CL-USER> (sum-digits-in-a-string *ANSWER-AS-STRING*)
1366
所以我的问题是为什么我首先得到一个整数然后是一个字符,以及处理长整数的数字的更好方法是什么。如果我的近似值很好:long-integer -> string -> list of integers -> apply reduce.
这不是 Common Lisp 特有的,但我认为如果您首先向您的函数添加一些调试输出,它可能会提供一般帮助。例如,在这种情况下,如果在添加和调用 digit-char-p 之前打印 x 和 y,您可以看到在处理前两个元素之后,第三个元素将使用前一个添加的结果进行处理,即一个数字,而不是一个字符:
CL-USER> (reduce (lambda (x y)
(write (list x y))
(+ (digit-char-p x)
(digit-char-p y)))
"1234")
(# #)(3 #)
; Evaluation aborted on #<TYPE-ERROR expected-type: CHARACTER datum: 3>.
你可以想想手动展开会是什么样子:
(+ (digit-char-p (+ (digit-char-p (+ (digit-char-p #)
(digit-char-p #)))
(digit-char-p #)))
(digit-char-p #))
在那些中间调用中,您调用 digit-char-p 的对象是数字,而不是字符。
你的最终解决方案很好:
(defun sum-digits-in-a-string (str)
(reduce #'+ (map 'list #'digit-char-p str)))
但这有一个不幸的问题,即它创建了一个全新的序列来包含数字,然后对它们进行归约。另一个常见的反模式是 (apply '+ (map …))。你的要好一些,因为它使用了 reduce,但 reduce 实际上采用了一个关键参数来消除对 map 的需求。你可以使用 key 参数来 reduce 来指定应该如何从序列元素中提取值,你可以使用一个初始值(如果你的序列是空的,或者只有一个元素,这很重要):
CL-USER> (reduce '+ "1234" :key 'digit-char-p :initial-value 0)
;=> 10
(reduce f "125")
执行的计算是
(f (f # #) #)
在你的例子中,这意味着它将计算
(+ (digit-char-p (+ (digit-char-p #) (digit-char-p #)))
(digit-char-p #))
所以它要评估
(+ (digit-char-p 3) (digit-char-p #))
因类型错误而中断。
最简单的解决方案是编写一个适用于整数的 digit-char-p
变体:
(defun digit-to-int (x)
(etypecase x
(integer x)
(character (digit-char-p x))))
(reduce #'(lambda (x y) (+ (digit-to-int x) (digit-to-int y))) *ANSWER-AS-string*)
正如 Joshua Taylor 所指出的,更简洁的解决方案是将 :key
参数用于 reduce
,这大致等同于使用 map
,但无需生成中间序列:
(reduce #'+ *ANSWER-AS-string* :key #'digit-char-p)
可以避免将数字打印为字符串并直接生成十进制数字列表。
爆炸和内爆
通常这个操作叫做explode。通常 explode 操作可以处理符号、整数等。它创建一个组件列表。逆运算叫做implode.
这对于正整数会爆炸:
(defun explode-integer (integer)
"Explode a positve integer."
(labels ((aux-explode-integer (integer)
(nreverse
(loop with i = integer and r = 0
while (> i 0)
do (multiple-value-setq (i r) (floor i 10))
collect r))))
(cond ((plusp integer)
(aux-explode-integer integer))
((zerop integer) (list 0)))))
示例:
CL-USER 31 > (explode-integer 572304975029345020734)
(5 7 2 3 0 4 9 7 5 0 2 9 3 4 5 0 2 0 7 3 4)
解决Euler项目问题我得到我需要对一个长数字的数字进行操作,通常作为一个字符串。我在 linux、emacs、slime 和 sbcl 中工作。
比如求2^1⁰⁰⁰这个幂的位数之和,我是这样算的,
1) 获得力量
CL-USER> (defparameter *answer-as-integer* (expt 2 1000))
*ANSWER-AS-INTEGER*
CL-USER> *ANSWER-AS-INTEGER*
10715086071862673209484250490600018105614048117055336074437503883703510511249361224931983788156958581275946729175531468251871452856923140435984577574698574803934567774824230985421074605062371141877954182153046474983581941267398767559165543946077062914571196477686542167660429831652624386837205668069376
感谢 Common Lisp,这非常容易。现在我认为一个好的方法应该是对这个数字序列应用reduce。
2) 获取字符串
CL-USER> (defparameter *answer-as-string* (write-to-string *ANSWER-AS-INTEGER*))
*ANSWER-AS-STRING*
CL-USER> *ANSWER-AS-STRING*
"10715086071862673209484250490600018105614048117055336074437503883703510511249361224931983788156958581275946729175531468251871452856923140435984577574698574803934567774824230985421074605062371141877954182153046474983581941267398767559165543946077062914571196477686542167660429831652624386837205668069376"
现在我有了序列,所以应用 reduce,但我得到了错误的东西:这是一个字符,所以我将转换字符应用到整数:
CL-USER> (reduce #'(lambda (x y) (+ (digit-char-p x) (digit-char-p y))) *ANSWER-AS-string*)
但我得到一个错误:
The value 1 is not of type CHARACTER.
[Condition of type TYPE-ERROR]
Restarts:
0: [RETRY] Retry SLIME REPL evaluation request.
1: [*ABORT] Return to SLIME's top level.
2: [ABORT] Abort thread (#<THREAD "repl-thread" RUNNING {1005DE80B3}>)
Backtrace:
0: (DIGIT-CHAR-P 1) [optional]
1: ((LAMBDA (X Y)) 1 #)
2: (REDUCE #<FUNCTION (LAMBDA (X Y)) {100523C79B}> "1071508607186267320948425049060001810561404811705533607443750388370351051124936122493198378815695858127594672917553146825187145285692314043598457757469..
3: (SB-INT:SIMPLE-EVAL-IN-LEXENV (REDUCE (FUNCTION (LAMBDA # #)) *ANSWER-AS-STRING*) #<NULL-LEXENV>)
4: (EVAL (REDUCE (FUNCTION (LAMBDA # #)) *ANSWER-AS-STRING*))
5: (SWANK::EVAL-REGION "(reduce #'(lambda (x y) (+ (digit-char-p x) (digit-char-p y))) *ANSWER-AS-string*) ..)
Locals:
SB-DEBUG::ARG-0 = "(reduce #'(lambda (x y) (+ (digit-char-p x) (digit-char-p y))) *ANSWER-AS-string*)\n"
6: ((LAMBDA NIL :IN SWANK-REPL::REPL-EVAL))
7: (SWANK-REPL::TRACK-PACKAGE #<CLOSURE (LAMBDA NIL :IN SWANK-REPL::REPL-EVAL) {10051F065B}>)
8: (SWANK::CALL-WITH-RETRY-RESTART "Retry SLIME REPL evaluation request." #<CLOSURE (LAMBDA NIL :IN SWANK-REPL::REPL-EVAL) {10051F059B}>)
9: (SWANK::CALL-WITH-BUFFER-SYNTAX NIL #<CLOSURE (LAMBDA NIL :IN SWANK-REPL::REPL-EVAL) {10051F057B}>)
10: (SWANK-REPL::REPL-EVAL "(reduce #'(lambda (x y) (+ (digit-char-p x) (digit-char-p y))) *ANSWER-AS-string*) ..)
11: (SB-INT:SIMPLE-EVAL-IN-LEXENV (SWANK-REPL:LISTENER-EVAL "(reduce #'(lambda (x y) (+ (digit-char-p x) (digit-char-p y))) *ANSWER-AS-string*) ..)
12: (EVAL (SWANK-REPL:LISTENER-EVAL "(reduce #'(lambda (x y) (+ (digit-char-p x) (digit-char-p y))) *ANSWER-AS-string*) ..)
13: (SWANK:EVAL-FOR-EMACS (SWANK-REPL:LISTENER-EVAL "(reduce #'(lambda (x y) (+ (digit-char-p x) (digit-char-p y))) *ANSWER-AS-string*) ..)
14: (SWANK::PROCESS-REQUESTS NIL)
15: ((LAMBDA NIL :IN SWANK::HANDLE-REQUESTS))
16: ((LAMBDA NIL :IN SWANK::HANDLE-REQUESTS))
17: (SWANK/SBCL::CALL-WITH-BREAK-HOOK #<FUNCTION SWANK:SWANK-DEBUGGER-HOOK> #<CLOSURE (LAMBDA NIL :IN SWANK::HANDLE-REQUESTS) {1005DF00DB}>)
18: ((FLET SWANK/BACKEND:CALL-WITH-DEBUGGER-HOOK :IN "/home/anquegi/quicklisp/dists/quicklisp/software/slime-2.13/swank/sbcl.lisp") #<FUNCTION SWANK:SWANK-DEBUGGER-HOOK> #<CLOSURE (LAMBDA NIL :IN SWANK::H..
19: (SWANK::CALL-WITH-BINDINGS ((*STANDARD-OUTPUT* . #1=#<SWANK/GRAY::SLIME-OUTPUT-STREAM {1005DCF343}>) (*STANDARD-INPUT* . #2=#<SWANK/GRAY::SLIME-INPUT-STREAM {1006160003}>) (*TRACE-OUTPUT* . #1#) (*ERR..
20: (SWANK::HANDLE-REQUESTS #<SWANK::MULTITHREADED-CONNECTION {1005078BE3}> NIL)
21: ((FLET #:WITHOUT-INTERRUPTS-BODY-1226 :IN SB-THREAD::INITIAL-THREAD-FUNCTION-TRAMPOLINE))
22: ((FLET SB-THREAD::WITH-MUTEX-THUNK :IN SB-THREAD::INITIAL-THREAD-FUNCTION-TRAMPOLINE))
23: ((FLET #:WITHOUT-INTERRUPTS-BODY-647 :IN SB-THREAD::CALL-WITH-MUTEX))
24: (SB-THREAD::CALL-WITH-MUTEX #<CLOSURE (FLET SB-THREAD::WITH-MUTEX-THUNK :IN SB-THREAD::INITIAL-THREAD-FUNCTION-TRAMPOLINE) {7FFFEA81ED1B}> #<SB-THREAD:MUTEX "thread result lock" owner: #<SB-THREAD:THR..
25: (SB-THREAD::INITIAL-THREAD-FUNCTION-TRAMPOLINE #<SB-THREAD:THREAD "repl-thread" RUNNING {1005DE80B3}> #S(SB-THREAD:SEMAPHORE :NAME "Thread setup semaphore" :%COUNT 0 :WAITCOUNT 0 :MUTEX #<SB-THREAD:MU..
26: ("foreign function: call_into_lisp")
27: ("foreign function: new_thread_trampoline")
如果我尝试将其用作数字而不进行转换,解释器会说这不是整数,所以我会发疯,因为这是正确的,但上面的代码不是:
(reduce #'+ *ANSWER-AS-string*)
The value # is not of type NUMBER.
[Condition of type TYPE-ERROR]
Restarts:
0: [RETRY] Retry SLIME REPL evaluation request.
1: [*ABORT] Return to SLIME's top level.
2: [ABORT] Abort thread (#<THREAD "repl-thread" RUNNING {1005DE80B3}>)
Backtrace:
0: (+ # #[=14=])
1: (REDUCE #<FUNCTION +> "107150860718626732094842504906000181056140481170553360744375038837035105112493612249319837881569585812759467291755314682518714528569231404359845775746985748039345677748242309854..
2: (SB-INT:SIMPLE-EVAL-IN-LEXENV (REDUCE (FUNCTION +) *ANSWER-AS-STRING*) #<NULL-LEXENV>)
3: (EVAL (REDUCE (FUNCTION +) *ANSWER-AS-STRING*))
4: (SWANK::EVAL-REGION "(reduce #'+ *ANSWER-AS-string*) ..)
Locals:
SB-DEBUG::ARG-0 = "(reduce #'+ *ANSWER-AS-string*)\n"
5: ((LAMBDA NIL :IN SWANK-REPL::REPL-EVAL))
6: (SWANK-REPL::TRACK-PACKAGE #<CLOSURE (LAMBDA NIL :IN SWANK-REPL::REPL-EVAL) {100566384B}>)
7: (SWANK::CALL-WITH-RETRY-RESTART "Retry SLIME REPL evaluation request." #<CLOSURE (LAMBDA NIL :IN SWANK-REPL::REPL-EVAL) {100566378B}>)
8: (SWANK::CALL-WITH-BUFFER-SYNTAX NIL #<CLOSURE (LAMBDA NIL :IN SWANK-REPL::REPL-EVAL) {100566376B}>)
9: (SWANK-REPL::REPL-EVAL "(reduce #'+ *ANSWER-AS-string*) ..)
10: (SB-INT:SIMPLE-EVAL-IN-LEXENV (SWANK-REPL:LISTENER-EVAL "(reduce #'+ *ANSWER-AS-string*) ..)
11: (EVAL (SWANK-REPL:LISTENER-EVAL "(reduce #'+ *ANSWER-AS-string*) ..)
12: (SWANK:EVAL-FOR-EMACS (SWANK-REPL:LISTENER-EVAL "(reduce #'+ *ANSWER-AS-string*) ..)
13: (SWANK::PROCESS-REQUESTS NIL)
14: ((LAMBDA NIL :IN SWANK::HANDLE-REQUESTS))
15: ((LAMBDA NIL :IN SWANK::HANDLE-REQUESTS))
16: (SWANK/SBCL::CALL-WITH-BREAK-HOOK #<FUNCTION SWANK:SWANK-DEBUGGER-HOOK> #<CLOSURE (LAMBDA NIL :IN SWANK::HANDLE-REQUESTS) {1005DF00DB}>)
17: ((FLET SWANK/BACKEND:CALL-WITH-DEBUGGER-HOOK :IN "/home/anquegi/quicklisp/dists/quicklisp/software/slime-2.13/swank/sbcl.lisp") #<FUNCTION SWANK:SWANK-DEBUGGER-HOOK> #<CLOSURE (LAMBDA NIL :IN SWANK::H..
18: (SWANK::CALL-WITH-BINDINGS ((*STANDARD-OUTPUT* . #1=#<SWANK/GRAY::SLIME-OUTPUT-STREAM {1005DCF343}>) (*STANDARD-INPUT* . #2=#<SWANK/GRAY::SLIME-INPUT-STREAM {1006160003}>) (*TRACE-OUTPUT* . #1#) (*ERR..
19: (SWANK::HANDLE-REQUESTS #<SWANK::MULTITHREADED-CONNECTION {1005078BE3}> NIL)
20: ((FLET #:WITHOUT-INTERRUPTS-BODY-1226 :IN SB-THREAD::INITIAL-THREAD-FUNCTION-TRAMPOLINE))
21: ((FLET SB-THREAD::WITH-MUTEX-THUNK :IN SB-THREAD::INITIAL-THREAD-FUNCTION-TRAMPOLINE))
22: ((FLET #:WITHOUT-INTERRUPTS-BODY-647 :IN SB-THREAD::CALL-WITH-MUTEX))
23: (SB-THREAD::CALL-WITH-MUTEX #<CLOSURE (FLET SB-THREAD::WITH-MUTEX-THUNK :IN SB-THREAD::INITIAL-THREAD-FUNCTION-TRAMPOLINE) {7FFFEA81ED1B}> #<SB-THREAD:MUTEX "thread result lock" owner: #<SB-THREAD:THR..
24: (SB-THREAD::INITIAL-THREAD-FUNCTION-TRAMPOLINE #<SB-THREAD:THREAD "repl-thread" RUNNING {1005DE80B3}> #S(SB-THREAD:SEMAPHORE :NAME "Thread setup semaphore" :%COUNT 0 :WAITCOUNT 0 :MUTEX #<SB-THREAD:MU..
25: ("foreign function: call_into_lisp")
26: ("foreign function: new_thread_trampoline")
我通过这样做解决了这个问题:
CL-USER> (defun sum-digits-in-a-string (str)
(reduce #'+ (map 'list #'digit-char-p str)))
SUM-DIGITS-IN-A-STRING
CL-USER> (sum-digits-in-a-string *ANSWER-AS-STRING*)
1366
所以我的问题是为什么我首先得到一个整数然后是一个字符,以及处理长整数的数字的更好方法是什么。如果我的近似值很好:long-integer -> string -> list of integers -> apply reduce.
这不是 Common Lisp 特有的,但我认为如果您首先向您的函数添加一些调试输出,它可能会提供一般帮助。例如,在这种情况下,如果在添加和调用 digit-char-p 之前打印 x 和 y,您可以看到在处理前两个元素之后,第三个元素将使用前一个添加的结果进行处理,即一个数字,而不是一个字符:
CL-USER> (reduce (lambda (x y)
(write (list x y))
(+ (digit-char-p x)
(digit-char-p y)))
"1234")
(# #)(3 #)
; Evaluation aborted on #<TYPE-ERROR expected-type: CHARACTER datum: 3>.
你可以想想手动展开会是什么样子:
(+ (digit-char-p (+ (digit-char-p (+ (digit-char-p #)
(digit-char-p #)))
(digit-char-p #)))
(digit-char-p #))
在那些中间调用中,您调用 digit-char-p 的对象是数字,而不是字符。
你的最终解决方案很好:
(defun sum-digits-in-a-string (str)
(reduce #'+ (map 'list #'digit-char-p str)))
但这有一个不幸的问题,即它创建了一个全新的序列来包含数字,然后对它们进行归约。另一个常见的反模式是 (apply '+ (map …))。你的要好一些,因为它使用了 reduce,但 reduce 实际上采用了一个关键参数来消除对 map 的需求。你可以使用 key 参数来 reduce 来指定应该如何从序列元素中提取值,你可以使用一个初始值(如果你的序列是空的,或者只有一个元素,这很重要):
CL-USER> (reduce '+ "1234" :key 'digit-char-p :initial-value 0)
;=> 10
(reduce f "125")
执行的计算是
(f (f # #) #)
在你的例子中,这意味着它将计算
(+ (digit-char-p (+ (digit-char-p #) (digit-char-p #)))
(digit-char-p #))
所以它要评估
(+ (digit-char-p 3) (digit-char-p #))
因类型错误而中断。
最简单的解决方案是编写一个适用于整数的 digit-char-p
变体:
(defun digit-to-int (x)
(etypecase x
(integer x)
(character (digit-char-p x))))
(reduce #'(lambda (x y) (+ (digit-to-int x) (digit-to-int y))) *ANSWER-AS-string*)
正如 Joshua Taylor 所指出的,更简洁的解决方案是将 :key
参数用于 reduce
,这大致等同于使用 map
,但无需生成中间序列:
(reduce #'+ *ANSWER-AS-string* :key #'digit-char-p)
可以避免将数字打印为字符串并直接生成十进制数字列表。
爆炸和内爆
通常这个操作叫做explode。通常 explode 操作可以处理符号、整数等。它创建一个组件列表。逆运算叫做implode.
这对于正整数会爆炸:
(defun explode-integer (integer)
"Explode a positve integer."
(labels ((aux-explode-integer (integer)
(nreverse
(loop with i = integer and r = 0
while (> i 0)
do (multiple-value-setq (i r) (floor i 10))
collect r))))
(cond ((plusp integer)
(aux-explode-integer integer))
((zerop integer) (list 0)))))
示例:
CL-USER 31 > (explode-integer 572304975029345020734)
(5 7 2 3 0 4 9 7 5 0 2 9 3 4 5 0 2 0 7 3 4)