问题
I'm working on a specific step in creating the 2048 game which you may have played. It's on a bunch of websites online.
Basically all this function does is: 1) All blank spaces move to the back and 2) if the first two numbers are equal then it doubles and checks every two numbers
These are the instructions for the step I'm stuck on:
Design a slide-left function so that it runs a single pass over the given row using an APS (accumulator passing style) helper. You will need to maintain two accumulators. The first accumulator remembers the last unmerged number. Initially, this value is false, meaning that we have not yet seen a number. The second accumulator is where we pile up the blanks as we come across them (a blank is '-) The easiest thing to do is to use a list for this purpose and, thus, its initial value is empty.
Do it in a single pass: You might think, at first, that it would be a good idea to use an accumulator for the solution. But then there is a problem with order. You could add new elements to the end of the current solution using something like (append solution (list number)), but that append operation is recursive and takes time proportional to the length of the solution list. We definitely want to avoid non-trivial operations during an APS recursion if we can. You could, instead, decide to add new numbers to the front of the current solution (using cons), with the intention of reversing the solution at the end. This is certainly better than the append approach. The drawback is that it requires a second pass over the data to do the reversal. We want to do this in one pass, and we can. So, the easiest and fastest thing to do is to just construct the solution, in the right order, as you back out of the recursion.
I added a bunch of check-expects here so you can see what it's doing:
(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 '- '-))
Alright, so here is what I have:
(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))]))
The first accumulator slide-row-left/help creates a list of the numbers that are not going to be merging. It checks that the first number and second are not equal and adds them to the list. If they are equal (which means they merge to double the original amount) then it just recurs. The second accumulator blank-help pushes all of the blank spaces '- to the end of the list, so all the numbers move left.
The problem is that I don't know how to make the pieces merge using these, especially in a single pass.
I'm about to head off for the night so hopefully you guys respond by tomorrow. Any help would be so great. Also this is for ISL+
回答1:
I think I found your problem. You were not quite there yet frankly. The assignment clearly states that you have to build up the result while coming back from your recursion. This is because you don't want to reverse at the end of your program, or do an append while doing tail recursion.
Below are a few examples that explain each approach:
;; 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
So for different inputs to the function slide-row-left/help:
Case 1: First two numbers are equal
Input: '(2 2 4 -)
Result: '(4 4 '- '-)
What you want to do here is sum both first elements (4). Then you want to calculate the rest of the list. The rest of the list should now also contain an extra blank, because summing two elements makes the list one element shorter. So you pass a new blank to the acc value in your recursive call. So what you want to do here first is calculate the rest of the list. Hence, call recursively with (drop ls 2) and (cons '- acc). This drops the first 2 elements form the list. Once you get that result back (result will be '(4 '- '-), you just cons your sum in front of it. This results in a total result of '(4 4 '- '-).
Case 2: First two numbers differ
Input: '(4 2 2 2)
Result: '(4 4 2 '-)
If the first two numbers differ we can not do anything. We take the first number off of the list (4), which leaves you with '(2 2 2). You can not drop the first two because the second and third element might be able to be summed. You remember the first element and call the function recursively to calculate the rest of the result. The function returns and gives you '(4 2 '-). Now all you need to do is cons the first element back in front of it, yielding '(4 4 2 '-).
Case 3: First element is a blank
Input: '(- 2 2 4)
Result: '(4 4 '- '-)
If the first number is a blank you can not do anything with it. You might think that in this case case 2 applies but it doesn't. A blank should be put in the back of your solution. But how can you do that? Simple, you put the blank in your accumulator. As you will soon see, the accumulator is basically the end of your list. So, take the blank away from the list, which leaves you with '(2 2 4). Put '- in front of the accumulator, which makes the accumulator equal to '('- <rest of acc>) and do your recursive call. So you call it with the list '(2 2 4) and the accumulator '('- <rest of acc>). Your recursive call will yield '(4 4 '- '-). Ergo, you don't have to cons anything in front of it anymore, it is just your result.
Case 4: Second element is a blank
Input: '(2 - 2 4)
Result: '(4 4 '- '-)
If the second number is a blank, the situation is a bit more tricky. You will have to cut out the blank from the list. So you would want (2 2 4). To do this you can do (cons (first ls) (rest (rest ls))). The blank can again, not be discarded. You will need it to put it at the end of the list. Where is the end of the list? Indeed, the accumulator. So you cons the element you want at the back of the solution (the blank) to the acc as such: (cons (second ls) acc). Again, you have put the blank at the end and you make your recursive call. There is no element that has to be put in front of of the solution of the recursive call so that call gives you the total answer.
Case 5: Input is empty
Input: '()
Result: acc
If your input is empty you need to return the acc value. Since your input is empty, you need to return the end of the list, which we know is the acc value.
Case 6: Input is length 1
Input: '(4)
Result: (cons 4 acc)
If the input is only one element you can not apply any sum or something to it. You only have one element. So, the result is that element, consd in front of the acc.
Source
#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 '- '-))
Edit
The drop function is used to remove the n first elements of a list. It can be implemented as shown below. However, you can just as well use (rest (rest ls)) instead. I used it for brevity.
drop
(define (drop ls n)
(cond [(empty? ls)
ls]
[(>= 0 n)
ls]
[else
(drop (rest ls) (- n 1))]))
来源:https://stackoverflow.com/questions/29425944/racket-accumulator-list-function