【问题标题】:Converting from C to Racket Scheme从 C 转换为 Racket 方案
【发布时间】:2021-03-07 08:13:09
【问题描述】:

我编写了以下 C 代码来递归解决给定问题,即接收一个非空整数列表和一个目标值,并返回最接近的正值而不超过目标。例如(3 4) 与目标 2 应该返回 1。唯一的组合是 -3 + 4 = 1。:

int closest(int arr[], int target, int i, int n)
{
    
    if (i == n)
        return 0; 

    int sum1 = arr[i] + closest(arr, target - arr[i], i + 1, n); 
                                                                
    int sum2 = -1 * arr[i] + closest(arr, target + arr[i], i + 1, n); 

    if (abs(sum1 - target) < abs(sum2 - target))
    {
        return sum1; 
    }
    else
    {
        return sum2;
    }
}

不幸的是,这个问题是用 Racket 编写的,限制使用 car + cdr 而不是使用 let 为 sum1/sum2 创建变量,我遇到了很大的困难。

到目前为止:

(define closest (lambda (l target i n)
  (cond
    ((= i n)0) 
   )
  )
  )

我的问题是:如何将 arr[i] 和指针转换为 car/cdr 逻辑?我隐约明白它控制两个值,但迭代似乎把我的大脑一分为二。

【问题讨论】:

  • 大概 C 函数最初是用 i 作为 0 调用的......每次再次调用它时它都会增加一个,直到它等于 n......这可以直接转换为链接列表和汽车/cdr。
  • 从这个骨架开始:(define (closest lst target) (if (null? lst) 0 (the rest of the code here))).
  • 请注意,您可以将(let ((x v)) e) 翻译成((lambda (x) e) v),它以“明显”的方式推广到多个定义。如果您首先重写您的 C 函数以使用指针算术(删除 i)并想象有一种方法可以从指针确定您已到达数组的末尾,则迭代更容易掌握。
  • "没有越过目标"你的 C 代码这样做。你用abs来比较。
  • (define closest (lambda (arr targ) (if (null? lst) 0 ((lambda (sum1 sum2) .... ) (+ (closest (cdr arr) (- targ (car arr))) (car arr)) (- .... ) ))))。这反映了 C 代码,因此,找到低于或高于目标的最接近的值。

标签: c recursion scheme racket code-translation


【解决方案1】:

您确实应该学习如何“在 Scheme 中思考”,但“在 Scheme 中思考,在 C 中”也很有价值。

首先,使用指针算法而不是数组索引。
如果你足够用力地眯着眼睛,你会发现*p 有点像(car p) - “第一个元素” - 而p + 1 有点像(cdr p) - “其余元素”。
然后你倒数到零而不是倒数到 n(也就是说,你问“还有多少元素要做?”而不是“我们做了多少元素?”)。

int closest(int *arr, int target, int n)
{
    if (n == 0)
        return 0; 
    /* I rearranged these two slightly in order to emphasize the symmetry. */
    int sum1 = closest(arr + 1, target - *arr, n-1) + *arr; 
    int sum2 = closest(arr + 1, target + *arr, n-1) - *arr; 
    if (abs(sum1 - target) < abs(sum2 - target))
    {
        return sum1; 
    }
    else
    {
        return sum2;
    }
}

现在我们需要摆脱“let-bindings”、sum1sum2
我们可以通过引入一个函数来做到这一点:

int pick(int target, int sum1, int sum2)
{
    return abs(sum1 - target) < abs(sum2 - target) ? sum1 : sum2;
}

int closest(int *arr, int target, int n)
{
    if (n == 0)
        return 0; 
    return pick(target,
                closest(arr + 1, target - *arr, n-1) + *arr,
                closest(arr + 1, target + *arr, n-1) - *arr);
}

(正如the Fundamental Theorem 所说,任何问题都可以通过添加间接级别来解决。)

这可以在 Scheme 中以一种非常简单的方式表述——主要是改变标点符号。
请注意,我们不再需要计数器,因为我们可以从列表本身判断我们已经到了末尾:

(define (pick target sum1 sum2)
    (if (< (abs (- sum1 target)) (abs (- sum2 target)))
        sum1
        sum2))

(define (closest arr target)
    (if (null? arr)
        0
        (pick target
              (+ (closest (cdr arr) (- target (car arr))) (car arr))
              (- (closest (cdr arr) (+ target (car arr))) (car arr)))))

现在我们可以“内联”pick,删除 target 参数,因为它在周围的上下文中可用:

(define (closest arr target)
    (if (null? arr)
        0
        ((lambda (sum1 sum2)
             (if (< (abs (- sum1 target)) (abs (- sum2 target))) sum1 sum2))
         (+ (closest (cdr arr) (- target (car arr))) (car arr))
         (- (closest (cdr arr) (+ target (car arr))) (car arr)))))

【讨论】:

  • 从足够远的地方看,一切都是标点符号的变化。 :)
【解决方案2】:

这是您正在做的事情的翻译。我认为让你绊倒的主要原因是conscar 的工作原理。球拍中的“向量”(或列表)实际上是这样的:

(cons 1 (cons 2 (cons 3 empty)))

在 C 中翻译成这个

[1, 2, 3]

那么我们如何从中获取ith 元素呢?!好吧,如果您查看您的递归,我们实际上并不需要。实际上,我们总是在我们刚刚处理的元素之后从列表中获取下一个元素。我们一次一个地遍历列表,并不关心我们已经计算过的内容。

实际上,翻译成 C 语言,我们在球拍中所做的是进行指针运算以获取下一个元素并将该指针作为我们的新 arr 传递回函数。列表的其余部分仅在我们无法访问的意义上被“遗忘”。到目前为止我们所做的计算仍然被记住。

我相信您正在使用 in 来跟踪我们在数组中的位置,以便我们可以删除它们,因为我们不需要实际跟踪我们所在的元素:

#lang racket
(require test-engine/racket-tests)

(define (closest arr target)
  (define sum1 (if (or (null? arr)) 0 (+ (car arr) (closest (cdr arr) (- target (car arr))))))
  (define sum2 (if (or (null? arr)) 0 (+ (* -1 (car arr)) (closest (cdr arr) (+ target (car arr))))))
  (if (< (abs (- sum1 target)) (abs (- sum2 target)))
      sum1
      sum2))

(check-expect (closest '(3 4) 2) 1)
(test)

【讨论】:

  • "'Arrays"'in Racket 实际上看起来像这样......" -- 小心......这种说法可能会引起更多的混乱而不是清晰。一些 Lisps 有实际的数组类型,例如Common Lisp,Racket 也有math/array(cons 1 (cons 2 (cons nil))) 是一个链表,而C 中的[1, 2, 3] 是一个实际的数组,即元素在内存中保证是连续的;并不完全相同。
  • 方案调用数组向量。
猜你喜欢
  • 2012-06-18
  • 2016-07-21
  • 2011-09-12
  • 2019-01-29
  • 2015-06-08
  • 2022-07-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多