views:

80

answers:

3

I've been trying to learn some programming on my own by working through the textbook How to Design Programs for Scheme. I've gotten through everything until now. Here's the problem:

9.5.5 Develop the function convert. It consumes a list of digits and produces the corresponding number. The first digit is the least significant, and so on.

Following the first few steps, from data analysis to template, I end up with this, the bare bones of a program:

;; A list-of-numbers is either 1. an empty list, empty, or 2. (cons n 
lon) where n is a number and lon is a list of numbers 
;; convert : lon -> number 
;; to consume a lon and produce the corresponding number. The least 
significant digit corresponds to the first number in the list, and so 
on. 
;; (= (convert (cons 1 (cons 9 (cons 10 (cons 99 empty))))) 991091) 
(define (convert lon) 
  (cond 
    [(empty? lon)...] 
    [(cons? lon)...(first lon)...(convert (rest lon))...])) 

How do I get past this stage to, as the book has it, "combining values"? The one way I think could work is if I multiplied the first value by 10 to the power of the value's significance in the total number, e.g.,

(cons 1 (cons 9 empty)) => 1 * 10^(SIGNIFICANCE), where LEAST SIGNIFICANCE would be 0. Using my limited understanding of programming, I figure that requires some counter, where n increases by one every time the function, in a manner of speaking, is called recursively. But that looks to me to be an attempt to run two recursions at the same time. Because expressions are evaluated sequentially (obviously), you can't call the counter function as you call the convert function.

So, can anyone help me solve this problem? I would prefer if you solved this using natural recursion and the CONStructor for lists and not lambda or other ideas the book hasn't addressed yet.

Thanks!

A: 

You don't need to do exponentiation - simple multiplication will do just fine.

(define (convert digits) 
  (cond
    ((empty? digits) 0)
    (else (+ (* 10 (convert (rest digits))) (first digits)))
  )
)

(convert '(1 2 3 4 5 6))

Or, another way of thinking about it:

(define (convert digits)
  (convert-helper digits 1 0)
)

(define (convert-helper digits multiplier sofar)
  (cond
    ((empty? digits) sofar)
    (else 
        (convert-helper 
            (rest digits) 
            (* 10 multiplier) 
            (+ (* multiplier (first digits)) sofar)
        )
    )
  )
)


(convert '(1 2 3 4 5 6))
Jamie Wong
+1 for not using exponentation, -1 for code formatting.
danlei
Wow! Thanks! Going through the stepper I realize what I missed: that (first non-empty-list) would "hold" the value in the expression while calling the function recursively. I just didn't think that was a possibility for some reason.Anyway, thanks a ton!
James Callaway
@danlei - I know that this is not the conventional formatting for lisp-y languages, but I personally find it much, much easier to follow. See which brackets line up is, to me, much more useful than saving a few lines. I'm not a functional programmer, so I'm use to my opening and closing things lining up.
Jamie Wong
Just imagine me formatting C code in Lisp-style. :)
danlei
@danlei Fair enough, but I guess I just don't really understand the merits of traditional lisp formatting. I understand why C code gets formatted the way it does for the sake of readability. What's the benefit of the lisp style?
Jamie Wong
I wouldn't concentrate that much on benefits vs. drawbacks, but adapt the predominant style of the language, or whatever the convention in a given community is. In general, functions in Lisps will be kept relatively short, so that you won't have to visually align huge blocks of code. Besides, when reading Lisp, you don't really care about the parens that much, more or less ignore them, and read the code like you would read Python. After a while, you develop a feeling for how things should look, and if they don't (as in your answer), you feel alienated.
danlei
A: 

Here's a tail recursive version:

(define (convert lon)
  (let rec ((i 0)
            (n 0)
            (lon lon))
    (cond ((empty? lon) n)
          (else (rec (+ i 1)
                     (+ n (* (first lon) (expt 10 i)))
                     (rest lon))))))
danlei
Thanks for you input, too! But I'm afraid I haven't gotten to the part of book that deals with let, so I'm ignorant as to what it does.
James Callaway
You're welcome. As far as let goes: It's used to bind variables. In my answer, I used a named let; a construct, which is quite handy for recursion. Also, consider accepting James' or my answer, whichever helped you most.
danlei
I just realized that Jamie Wong's wouldn't work if the number in the list was greater than 9. So (cons 1 (cons 9 (cons 10 (cons 99 empty)))) => 100091, not 991091. Of course, I think he actually understood what the authors intended, whereas I thought a "digit" (0-9) could be any integer. I'd bet that yours does what I was thinking, but I don't know how to deal with the 'let' construct to find out.
James Callaway
And of course, Jamie Wong's works if you break any integer into digits. What I'm asking is if it's possible to do that without breaking it down.
James Callaway
This one works with digits above 9, but won't work for negative digits. The named let works like this: It's like defining a function rec, which is first called with the arguments 0 0 lon and then calls itself recursively (rec (+ i 1) (+ n ...) (rest lon)).
danlei
A: 

The use of multiplication (or an auxiliary that uses accumulator-design) is all wrong. If you were in my course, you'd get 0/10 points for handing in this solution.

matthias
What (besides not handling negative digits) should be "all wrong" about these solutions, justifying giving 0/10 points?
danlei
While I understand that you don't yet have the reputation required to comment, that doesn't mean you should be posting comments as answer.
Jamie Wong