Here's how I would systematically make a function to find the length of a list.
The final result will be
;; length : [Listof Element] -> Number
;; Produces the number of elements in the list
(define (length lst)
(cond
[(empty? lst) 0]
[(cons? lst) (+ 1 (length (rest lst)))]))
But the more important thing is the systematic process, that you can use to write many more programs. The process I use here is the Design Recipe explained in the book How to Design Programs.
1: What is a list?
A list is either empty, or it's the combination of the first element with the rest of the elements. This "rest" is another list.
In Racket, "empty" is written as '(), and "combine" for lists is written as cons.
A [Listof Element] is one of:
- '()
- (cons Element [Listof Element])
Some examples of lists:
'() ; empty
(cons "I'm alone" '()) ; one element "I'm alone"
(cons "Hello" (cons "there" '())) ; two elements "Hello" and "there"
(cons 1 (cons 3 (cons 5 (cons 7 (cons 9 '()))))) ; five elements, odd numbers
2: What does length do?
The length function takes a list and produces a number representing the number of elements in the list. The empty list as length 0, and the examples above should have lengths 0, 1, 2, and 5.
;; length : [Listof Element] -> Number
;; Produces the number of elements in the list
;; (length '()) = 0
;; (length (cons "I'm alone" '())) = 1
;; (length (cons "Hello" (cons "there" '()))) = 2
;; (cons 1 (cons 3 (cons 5 (cons 7 (cons 9 '()))))) = 5
(define (length lst)
???)
3: What are the cases in the definition?
A list is either empty or a cons. To test whether its empty, we can use a cond with the question (empty? lst) for the empty case, and the question (cons? lst) for the cons case.
(define (length lst)
(cond
[(empty? lst) ???]
[(cons? lst) ???]))
4: What "sub-pieces" of the data are available in each case?
An empty list has no sub-pieces.
However, a (cons Element [Listof Element]) has two sub-pieces:
- the first thing,
Element,
- and the rest of the list,
[Listof Element].
To get these in racket, you can use first and rest respectively.
(define (length lst)
(cond
[(empty? lst) ???]
[(cons? lst) ... (first lst) ... (rest lst) ...]))
5: Are any of those sub-pieces complex data?
The (first lst) is just an Element. For the purposes of length, it's not complex, and we don't have to process it further.
The (rest lst) is another [Listof Element] though, and that is complex. How do we deal with that? By using a helper function.
In this case, we want a helper function that's length-related, and takes a [Listof Element] as an argument. In this case, that helper function happens to be length, the function we're currently defining! We can use it recursively on (rest lst) because it's a smaller sub-piece.
(define (length lst)
(cond
[(empty? lst) ???]
[(cons? lst) ... (first lst) ... (length (rest lst)) ...]))
6: Fill in the holes, using both your intuition of how length should work, and the examples we wrote before
The first example (length '()) = 0 tells us that the first ??? hole should be filled with 0.
(define (length lst)
(cond
[(empty? lst) 0]
[(cons? lst) ... (first lst) ... (length (rest lst)) ...]))
The second hole, the ...s around the first and the length of the rest, is harder. But your intuition about length should tell you that the length of a "combined" list of first and rest should be one plus the length of the rest. Turing that into code:
(define (length lst)
(cond
[(empty? lst) 0]
[(cons? lst) (+ 1 (length (rest lst)))]))
The systematic steps I used are explained in the book How to Design Programs.