1
votes

I don't have much experience with Scheme/Racket but I'm trying to make a function that calculates how many positive values in a list. I have this so far:

(define num_pos 0)
(define num_neg 0)
(define (numpos lst)
 (cond
  ((null? lst) 0)
  ((>= (car lst) 0) (+ num_pos 1) (numpos (cdr lst)))
  (else (+ num_neg 1) (numpos (cdr lst)))
  )
)

But when I run it in DrRacket and test it, the lst is always empty, so if I call (numpos '(1 -1 2)) it returns 0 by the null check, but if I delete the null check it crashes on the (car lst) saying that it expects a pair but gets '(). I've spent a bit on this with no luck. Any ideas?

2

2 Answers

3
votes

(+ num_pos 1) doesn't alter the value of num_pos. Try replacing it with (set! num_pos (+ num_pos 1)) and do the same for num_neg.

After calling (numpos '(1 -1 2)), the value of num_pos become 2.

EDIT: I totally agree with Óscar's comment and he has already added a better answer. Here is an alternative example with additional num_neg support:

(define (numpos lst num_pos num_neg)
 (cond
  ((null? lst)
   (list num_pos num_neg))
  ((>= (car lst) 0)
   (numpos (cdr lst) (+ num_pos 1) num_neg))
  (else
   (numpos (cdr lst) num_pos      (+ num_neg 1)))))

If you call (numpos '(1 -1 2) 0 0) then it returns a list containing the numbers of positive values and negative values like (2 1).

Note that the new version requires additional 2 arguments, each of which needs to be 0. Those parameters work like num_pos and num_neg in the original code. And this is tail recursive.

With tail recursion, you can easily remove side effects (global variable access).

If you don't like those additional arguments, you can rewrite with internal define like below:

(define (numpos lst)
  (define (iter lst num_pos num_neg)
    (cond
     ((null? lst)
      (list num_pos num_neg))
     ((>= (car lst) 0)
      (iter (cdr lst) (+ num_pos 1) num_neg))
     (else
      (iter (cdr lst) num_pos      (+ num_neg 1)))))
  (iter lst 0 0))
2
votes

In Scheme, we try to write procedures using the functional programming paradigm. In your example, defining a counter outside the procedure is not a good idea, to change its value you'd need to mutate it inside using the set! instruction, and we should avoid doing precisely that.

The usual solution (if we're going to solve this "by hand") would be to recursively traverse the list and increment the value at each recursive call, notice that we don't even need to increment variables inside the procedure, like this:

(define (numpos lst)
  (cond
    ((null? lst) 0)
    ((>= (car lst) 0) (+ 1 (numpos (cdr lst))))
    (else (numpos (cdr lst)))))

The key to understand how this works is in here:

(+ 1 (numpos (cdr lst)))

We add one to the result of the recursion, and we keep doing this for every positive number until we reach the end of the list, and add a zero at the end.

In your code, you had written this: (+ num_pos 1), but that expression is not changing the value of num_pos, it's simply adding one to zero, but never storing the result of the addition!

Now we can define the variables with the proper values, we only need to calculate num_pos with the procedure, the value of num_neg can be easily inferred:

(define num_pos (numpos lst))
(define num_neg (- (length lst) num_pos))

There are many ways to solve this problem. After you're familiar with recursive procedures, you'll discover that there are tons of built-in procedures that allow you to quickly find a solution to common problems. In fact, the idiomatic way to answer your question would be to use count:

(define (numpos lst)
  (count (lambda (n) (>= n 0))
         lst))