【问题标题】:SICP Exercise 2.5 - How to represent negative numbers?SICP 练习 2.5 - 如何表示负数?
【发布时间】:2022-06-28 14:59:06
【问题描述】:

我目前正在阅读 SICP,并在练习 2.5:

练习 2.5。证明如果我们将 a 和 b 对表示为乘积 的整数,我们可以仅使用数字和算术运算来表示非负整数对2a3b。给出过程cons、car、cdr的对应定义。

我找到了a code:

(define (my-cons a b)
  (* (expt 2 a) (expt 3 b)))

(define (my-car x)
  (define (car-iter x count)
    (if (= 0 (remainder x 2))
        (car-iter (/ x 2) (+ 1 count))
        count))
  (car-iter x 0))

(define (my-cdr x)
  (define (cdr-iter x count)
    (if (= 0 (remainder x 3))
        (cdr-iter (/ x 3) (+ 1 count))
        count))
  (cdr-iter x 0))

我的问题是:如果需要将“非负整数”的要求改为“接受负整数和非负整数”怎么办?

例子:

> (define x (my-cons 2 -5))
> (my-car x)
2
> (my-cdr x)
-5

如何修改代码?我想不通。

谢谢。祝你有美好的一天。

【问题讨论】:

  • 这是一道数学题,真的,不是编程题。您显然不能将负数表示为 2^n*3^m,因此您将不得不选择一些替代表示。一个明显的解决方案是包含一个额外的位/布尔值,表示“是负数”。
  • 与此类似的另一种解决方案是包含 5 和 7 的幂,并使用两个数字作为符号,使用两个数字作为量级。

标签: scheme lisp sicp data-representation


【解决方案1】:

正如 amalloy 在评论中所说,这确实是一道数学题。这种编码之所以有效,是因为算术的基本定理,即任何自然数(正整数或零)都具有唯一的素数分解。

因此,您可以使用一个或多个额外的素因子对整数的符号进行编码(实际上,您只需要一个,因为您可以将事物写为 2^a3^b5^s ,其中 s 是[0,3] 对两个元素的符号进行编码)。

另一种方法是简单地使用现有的表示,但将整数映射到自然数。这很好,因为它是一个实际证明,没有比自然数更多的整数。这样的地图可能是:

  • 如果 i >= 0 则 2i。
  • 否则 -2i - 1.

很容易看出这是一个one-to-one 对应关系,而且0 映射到0(这使得0 成为nil 的一个很好的值)。

这是这些地图,写在(对不起)打字球拍,因为我正在努力弄清楚我是否可以使用它。

(define (Z->N (i : Integer)) : Natural
  ;; map an integer to a natural
  (if (>= i 0)
      (* i 2)
      (- (* (- i) 2) 1)))

(define (N->Z (n : Natural)) : Integer
  ;; map the naturals into the integers
  (let-values ([(q r) (quotient/remainder n 2)])
    (if (zero? r)
        q
        (- (- q) 1))))

现在你的实现还有另一个问题:它会很乐意处理不是 2^a3^b 形式的数字,例如任何具有其他素因数的数字。处理这个问题的方法是在提取幂时检查数字是否具有该形式:实际上这意味着检查数字是否为 2^a*3^b*1 形式。

所以下面的代码就是这样做的,以及上面的编码整数。这又是在类型化的 Racket 中(对不起,再次),它还利用了多个值以及可能仅存在于 Racket 中的其他一些东西。

(define (defactor (n : Natural) (p : Natural)) : (Values Natural Natural)
  ;; Given n and a factor p, return m where p does not divide m,
  ;; and j, the number of factors of p removed (so n = m*p^j)
  (let df-loop ([m : Natural n]
                [j : Natural 0])
    (let-values ([(q r) (quotient/remainder m p)])
      (if (zero? r)
          (df-loop q (+ j 1))
          (values m j)))))
  
(define (kar&kdr (k : Natural)) : (Values Integer Integer)
  ;; Given something which should be a kons, return its kar & kdr.
  ;; If it is not a kons signal an error
  (let*-values ([(k2 encoded-kar) (defactor k 2)]
                [(k23 encoded-kdr) (defactor k2 3)])
    (unless (= k23 1)
      (error 'kar&kdr "not a cons"))
    (values (N->Z encoded-kar) (N->Z encoded-kdr))))

(define (kons (the-kar : Integer) (the-kdr : Integer)) : Natural
  (* (expt 2 (Z->N the-kar))
     (expt 3 (Z->N the-kdr))))

(define (kar (the-kons : Natural)) : Integer
  (let-values ([(the-kar the-kdr) (kar&kdr the-kons)])
    the-kar))

(define (kdr (the-kons : Natural)) : Integer
  (let-values ([(the-kar the-kdr) (kar&kdr the-kons)])
    the-kdr))

现在

> (define d (kons 123 -456))
> d
- : Integer [more precisely: Nonnegative-Integer]
51385665200410193914365219310409629004573395973849642473134969706165383608831740620563388986738635202925909198851954060195023302783671526117732269828652603388431987979605951272414330987611274752111186624164906143978901704325355283206259678088536996807776750955110998323447711166379786727609752016045005681785186498933895920793982869940159108073471074955985333560653268614500306816876936016985137986665262182684386364851688838680773491949813254691225004097103180392486216812280763694296818736638062547181764608
> (kar d)
- : Integer
123
> (kdr d)
- : Integer
-456
> (kdr (+ d 1))
kar&kdr: not a cons [,bt for context]

【讨论】:

  • 0 没有素因数分解。
  • 另外,我们只编码整数对,所以这里真的没有cons 的位置,因为我们没有 nums 和 lons 对(nums 列表),只有 nums 和 nums . lons 的自然编码似乎只是使用第 i 个素数的指数来表示 lon(基于 1)中的第 i 个 num。然后一个空列表将由 1 表示。:) 这个 repr 的有趣属性是 O(1) 访问每个索引。但是 cons 将是 O(n)... 除非我们使用另一个 int 来保存“负”索引!
猜你喜欢
  • 1970-01-01
  • 2012-07-05
  • 2011-11-05
  • 2017-02-03
  • 2010-12-26
  • 2012-12-15
  • 2021-05-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多