球拍累加器列表功能

时间:2015-04-03 03:52:30

标签: scheme racket

我正在制作您可能已经玩过的2048游戏中的特定步骤。它出现在一堆网站上。

基本上所有这个功能都是: 1)所有空格都移到后面 2)如果前两个数字相等,那么它会加倍并检查每两个数字

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

  

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

     

一次性完成:首先,您可能会认为使用累加器作为解决方案是个好主意。但是订单有问题。您可以使用类似(附加解决方案(列表编号))之类的内容将新元素添加到当前解决方案的末尾,但该追加操作是递归操作,并且需要时间与解决方案列表的长度成比例。如果可以的话,我们肯定希望在APS递归期间避免非平凡的操作。相反,您可以决定在当前解决方案的前面添加新数字(使用利弊),目的是在最后反转解决方案。这肯定比追加方法更好。缺点是它需要对数据进行第二次传递才能进行反转。我们希望一次通过,我们可以。因此,最简单快速的做法是在退出递归时以正确的顺序构建解决方案。

我在这里添加了一堆检查预期,这样你就可以看到它正在做什么:

(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 +

1 个答案:

答案 0 :(得分: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)递归调用。这会从列表中删除前两个元素。一旦你得到了这个结果(结果将是'(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 '- '-)

如果第一个数字是空白,则无法对其执行任何操作。您可能会认为在这种情况下案例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)))。空白可以再次,不能丢弃。您需要将它放在列表的末尾。列表的结尾在哪里?的确,累加器。因此,您{(1}}将解决方案背面所需的元素(空白)cons添加到acc(cons (second ls) acc)。再次,你已经把空白放在最后,你进行递归调用。没有任何元素必须放在递归调用的解决方案之前,以便调用为您提供总答案。

案例5:输入为空

输入:'() 结果:acc

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

案例6:输入长度为1

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

如果输入只是一个元素,则不能对其应用任何总和或其他内容。你只有一个元素。因此,结果是cons前面的元素acc d。

<强> 来源

#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))]))