【问题标题】:Racket accumulator list function球拍累加器列表功能
【发布时间】:2015-06-08 04:12:02
【问题描述】:

我正在制定您可能玩过的 2048 游戏的具体步骤。它在一堆在线网站上。

这个函数所做的基本上是: 1)所有空格都移到后面 2)如果前两个数字相等,则加倍并检查每两个数字

这些是我坚持的步骤的说明:

设计一个向左滑动的函数,以便它使用 APS(累加器传递样式)助手在给定行上运行 single 传递。您将需要维护两个累加器。第一个累加器记住最后一个未合并的数字。最初,这个值是 false,这意味着我们还没有看到一个数字。第二个累加器是我们在遇到它们时堆积空白的地方(空白是'-)最简单的方法是为此目的使用一个列表,因此它的初始值为空。

一次性完成:起初,您可能会认为使用蓄能器来解决问题是个好主意。但是接下来的顺序就有问题了。您可以使用类似(追加解决方案(列表编号))将新元素添加到当前解决方案的末尾,但是追加操作是递归的,并且所花费的时间与解决方案列表的长度成正比。如果可以的话,我们绝对希望在 APS 递归期间避免非平凡的操作。相反,您可以决定在当前解决方案的前面添加新数字(使用 cons),以便在最后反转解决方案。这肯定比追加方法好。缺点是它需要对数据进行第二次传递才能进行反转。我们想一次性做到这一点,我们可以。因此,最简单、最快速的做法就是在退出递归时以正确的顺序构建解决方案。

我在这里添加了一堆检查期望,所以你可以看到它在做什么:

(check-expect (slide-row-left '(2 2 4 -))(list 4 4 '- '-))
(check-expect (slide-row-left '(2 2 - 4))(list 4 4 '- '-))
(check-expect (slide-row-left '(2 - 2 4))(list 4 4 '- '-))
(check-expect (slide-row-left '(- 2 2 4))(list 4 4 '- '-))

(check-expect (slide-row-left '(2 2 2 2))(list 4 4 '- '-))
(check-expect (slide-row-left '(4 2 2 2))(list 4 4 2 '-))
(check-expect (slide-row-left '(2 4 2 2))(list 2 4 4 '-))
(check-expect (slide-row-left '(2 2 4 2))(list 4 4 2 '-))
(check-expect (slide-row-left '(2 2 4 4))(list 4 8 '- '-))

好的,这就是我所拥有的:

(define (blank? item) (equal? item '-))

(define (slide-row-left b)
  (blank-help (slide-row-left/help b false) '()))

(define (slide-row-left/help ls acc)
  (cond [(empty? ls) acc]
        [(not (equal? (first ls) (first (rest ls)))) 
         (slide-row-left/help (rest (rest ls)) 
           (cons (first (rest ls)) (cons (first ls) acc)))]
        [else (slide-row-left/help (rest (rest ls)) acc)]))

(define (blank-help ls acc)
  (cond [(empty? ls) acc]
        [(blank? (first ls)) (blank-help (rest ls) (cons (first ls) acc))]     
        [else (cons (first ls) (blank-help (rest ls) acc))]))

第一个累加器slide-row-left/help 创建一个不会合并的数字列表。它检查第一个数字和第二个数字是否不相等并将它们添加到列表中。如果它们相等(这意味着它们合并为原始数量的两倍),那么它只会重复出现。第二个累加器 blank-help 将所有空格 '- 推到列表的末尾,因此所有数字都向左移动。

问题是我不知道如何使用这些来使片段合并,尤其是在单程中。

我马上就要离开了,所以希望你们明天能做出回应。任何帮助都会很棒。这也适用于 ISL+

【问题讨论】:

    标签: scheme racket


    【解决方案1】:

    我想我发现了你的问题。坦率地说,你还没有完全到那里。该作业明确指出,您必须在递归返回时建立结果。这是因为您不想在程序结束时reverse,或者在进行尾递归时执行append

    以下是解释每种方法的几个示例:

    ;; Using an accumulator
    (define (double ls acc)
      (if (empty? ls)
          acc
          (double (rest ls) (cons (* 2 (first ls)) acc))))
    
    (double '(1 2 3) '())
    ;; = '(6 4 2)
    ;; So you could reverse the result: (reverse '(1 2 3) '()))
    ;; but this requires looping over the list again!
    
    ;; Using append
    (define (double2 ls acc)
      (if (empty? ls)
          acc
          (double2 (rest ls) (append acc (list (* 2 (first ls)))))))
    
    (double2 '(1 2 3) '())
    ;; = '(2 4 6)
    ;; But the append operation is very expensive and you don't want that!
    
    (define (double3 ls)
      (if (empty? ls)
          '()
          (cons (* 2 (first ls)) (double3 (rest ls)))))
    
    (double3 '(1 2 3))
    ;; Cons *after* the recursive call.
    ;; Requires more memory though, becuase you can not do tail call optimisation!
    ;; Info: http://c2.com/cgi/wiki?TailRecursion
    

    所以对于函数slide-row-left/help的不同输入:

    案例 1:前两个数相等

    输入:'(2 2 4 -) 结果:'(4 4 '- '-)

    您在这里要做的是将两个第一个元素 (4) 相加。然后你想计算列表的其余部分。列表的其余部分现在也应该包含一个额外的空白,因为将两个元素相加会使列表中的一个元素更短。因此,您在递归调用中将一个新空白传递给 acc 值。所以你首先要做的是计算列表的其余部分。因此,使用(drop ls 2)(cons '- acc) 递归调用。这会从列表中删除前 2 个元素。一旦你得到那个结果(结果将是'(4 '- '-),你只是在它前面cons你的总和。这导致总结果为'(4 4 '- '-)

    案例 2:前两个数字不同

    输入:'(4 2 2 2) 结果:'(4 4 2 '-)

    如果前两个数字不同,我们将无能为力。我们从列表中删除第一个数字 (4),剩下的是 '(2 2 2)。您不能删除前两个,因为第二个和第三个元素可能可以相加。您记住第一个元素并递归调用该函数以计算其余结果。该函数返回并为您提供'(4 2 '-)。现在你需要做的就是cons它前面的第一个元素,产生'(4 4 2 '-)

    案例 3:第一个元素为空白

    输入:'(- 2 2 4) 结果:'(4 4 '- '-)

    如果第一个数字是空白,您将无法对其进行任何操作。您可能认为在这种情况下 case 2 适用,但事实并非如此。应该在您的解决方案的后面放置一个空白。但是你怎么能这样做呢?很简单,您将空白放入蓄电池中。正如您很快将看到的,累加器基本上是您列表的末尾。因此,从列表中删除空白,留下'(2 2 4)。将'- 放在累加器前面,这使得累加器等于'('- <rest of acc>) 并进行递归调用。所以你用列表'(2 2 4) 和累加器'('- <rest of acc>) 来调用它。您的递归调用将产生'(4 4 '- '-)。因此,您不必再在它前面cons 任何东西,这只是您的结果。

    案例 4:第二个元素为空白

    输入:'(2 - 2 4) 结果:'(4 4 '- '-)

    如果第二个数字是空白,情况就有点棘手了。您将不得不从列表中删除空白。所以你会想要(2 2 4)。为此,您可以使用(cons (first ls) (rest (rest ls)))。空白可以再次,不能被丢弃。您需要将其放在列表的末尾。列表的末尾在哪里?确实,蓄能器。所以你cons你想要在解决方案后面的元素(空白)到acc这样:(cons (second ls) acc)。同样,您将空白放在最后,然后进行递归调用。在递归调用的解决方案之前不需要放置任何元素,以便调用为您提供完整的答案。

    案例 5:输入为空

    输入:'() 结果:acc

    如果您的输入为空,则需要返回 acc 值。由于您的输入为空,因此您需要返回列表的末尾,我们知道它是 acc 值。

    案例6:输入长度为1

    输入:'(4) 结果:(cons 4 acc)

    如果输入只是一个元素,则不能对其应用任何总和或其他东西。你只有一个元素。所以,结果是 consd 在 acc 前面的那个元素。

    来源

    #lang racket
    
    ;; A shorthand for (first (rest ls))
    (define (second ls) (first (rest ls)))
    
    (define (blank? item) (equal? item '-))
    
    (define (slide-row-left b)
      (blank-help (slide-row-left/help b '()) '()))
    
    (define (slide-row-left/help ls acc)
      (cond [(empty? ls) acc]
            [(= (length ls) 1) (cons (first ls) acc)]
            ;; When the first element is blank (e.g., '(- 2 4))
            ;; -> Discard the blank
            ;; -> call the function on the rest of the list.
            [(blank? (first ls))
             (slide-row-left/help (rest ls) (cons (first ls) acc))]
            ;; When the second element is blank (e.g., '(2 - 2 4))
            ;; -> Discard the second element
            ;; -> Run the function again with the entire list (without the blank)
            [(blank? (second ls))
             (slide-row-left/help (cons (first ls) (drop ls 2)) (cons (second ls) acc))]        
            ;; If the first two elements are not equal:
            ;; -> drop 1 element from the list
            ;; -> cons this element to the result of the recursive call
            [(not (equal? (first ls) (second ls)))
             (let  ([fst (first ls)]
                    [snd (second ls)]
                    [rst (rest ls)]) 
               (cons fst (slide-row-left/help rst acc)))]
    
            ;; If the first two elements are the same:
            ;; -> drop 2 elements from the list
            ;; -> Sum them
            ;; -> cons the sum in front of the recursive call
            [else
             (let  ([fst (first ls)]
                    [snd (second ls)]
                    [rst (drop ls 2)]) 
               (cons (* 2 snd) (slide-row-left/help rst (cons '- acc))))]))
    
    
    (define (blank-help ls acc)
      (cond [(empty? ls) acc]
            [(blank? (first ls)) (blank-help (rest ls) (cons (first ls) acc))]     
            [else (cons (first ls) (blank-help (rest ls) acc))]))
    
    (define (check-expect x y)
      (display x)
      (display y)
      (equal? x y))
    
    (check-expect (slide-row-left '(2 2 4 -))(list 4 4 '- '-))
    (check-expect (slide-row-left '(2 2 - 4))(list 4 4 '- '-))
    (check-expect (slide-row-left '(2 - 2 4))(list 4 4 '- '-))
    (check-expect (slide-row-left '(- 2 2 4))(list 4 4 '- '-))
    
    (check-expect (slide-row-left '(2 2 2 2))(list 4 4 '- '-))
    (check-expect (slide-row-left '(4 2 2 2))(list 4 4 2 '-))
    (check-expect (slide-row-left '(2 4 2 2))(list 2 4 4 '-))
    (check-expect (slide-row-left '(2 2 4 2))(list 4 4 2 '-))
    (check-expect (slide-row-left '(2 2 4 4))(list 4 8 '- '-))
    

    编辑

    drop 函数用于删除列表的第一个 n 元素。它可以实现如下所示。但是,您也可以改用(rest (rest ls))。我用它是为了简洁。

    放下

    (define (drop ls n)
      (cond [(empty? ls)
             ls]
            [(>= 0 n)
             ls]
            [else
             (drop (rest ls) (- n 1))]))
    

    【讨论】:

    • 我阅读了您的解释,这真的很有帮助。我还没有完全浏览代码,但它看起来不错。我尝试将其放入球拍中,但未定义“drop”功能。我们还没有学会如何使用 let* 表达式。在 Isl+ 中有替代品吗?再次非常感谢
    • 我已经编辑了源代码,所以它不再使用let*。但是let*let 相同,除了您可以使用先前在let* 中定义的标识符。本质上,你可以在第四种情况下使用fstsndrst,这里是rec
    • 我不得不花时间检查你的代码,它很完美。我真的很感谢你的帮助!谢谢
    猜你喜欢
    • 1970-01-01
    • 2022-10-06
    • 1970-01-01
    • 2012-02-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-20
    相关资源
    最近更新 更多