1
votes

I'm running a for loop in racket, for each object in my list, I want to execute two things: if the item satisfies the condition, (1) append it to my new list and (2) then print the list. But I'm not sure how to do this in Racket.

This is my divisor function: in the if statement, I check if the number in the range can divide N. If so, I append the item into my new list L. After all the loops are done, I print L. But for some unknown reason, the function returns L still as an empty list, so I would like to see what the for loop does in each loop. But obviously racket doesn't seem to take two actions in one "for loop". So How should I do this?

    (define (divisor N)
       (define L '())
      (for ([i (in-range 1 N)])
         (if (equal? (modulo N i) 0)
             (append L (list i))
             L)
           )
       write L)

Thanks a lot in advance!

5
I think you got 4 good answers here, is there anything that remains unclear? If so, please tell us what's missing. Otherwise, please mark the answer you prefer as "accepted". - uselpa

5 Answers

2
votes

Note: This answer builds on the answer from @uselpa, which I upvoted.

The for forms have an optional #:when clause. Using for/fold:

#lang racket

(define (divisors N)
  (reverse (for/fold ([xs '()])
                     ([n (in-range 1 N)]
                      #:when (zero? (modulo N n)))
             (displayln n)
             (cons n xs))))

(require rackunit)
(check-equal? (divisors 100)
              '(1 2 4 5 10 20 25 50))

I realize your core question was about how to display each intermediate list. However, if you didn't need to do that, it would be even simpler to use for/list:

 (define (divisors N)
  (for/list ([n (in-range 1 N)]
             #:when (zero? (modulo N n)))
    n))

In other words a traditional Scheme (filter __ (map __)) or filter-map can also be expressed in Racket as for/list using a #:when clause.


There are many ways to express this. I think what all our answers have in common is that you probably want to avoid using for and set! to build the result list. Doing so isn't idiomatic Scheme or Racket.

1
votes

@sepp2k has answered your question why your result is always null.

In Racket, a more idiomatic way would be to use for/fold:

(define (divisor N)
  (for/fold ((res null)) ((i (in-range 1 N)))
    (if (zero? (modulo N i))
        (let ((newres (append res (list i))))
          (displayln newres)
          newres)
        res)))

Testing:

> (divisor 100)
(1)
(1 2)
(1 2 4)
(1 2 4 5)
(1 2 4 5 10)
(1 2 4 5 10 20)
(1 2 4 5 10 20 25)
(1 2 4 5 10 20 25 50)
'(1 2 4 5 10 20 25 50)

Since append is not really performant, you usually use cons instead, and end up with a list you need to reverse:

(define (divisor N)
  (reverse
   (for/fold ((res null)) ((i (in-range 1 N)))
     (if (zero? (modulo N i))
         (let ((newres (cons i res)))
           (displayln newres)
           newres)
         res))))

> (divisor 100)
(1)
(2 1)
(4 2 1)
(5 4 2 1)
(10 5 4 2 1)
(20 10 5 4 2 1)
(25 20 10 5 4 2 1)
(50 25 20 10 5 4 2 1)
'(1 2 4 5 10 20 25 50)
1
votes

It's possible to create the list as you intend, as long as you set! the value returned by append (remember: append does not modify the list in-place, it creates a new list that must be stored somewhere) and actually call write at the end:

(define (divisor N)
  (define L '())
  (for ([i (in-range 1 N)])        ; generate a stream in the range 1..N
    (when (zero? (modulo N i))     ; use `when` if there's no `else` part, and `zero?`
                                   ; instead of `(equal? x 0)`
      (set! L (append L (list i))) ; `set!` for updating the result
      (write L)                    ; call `write` like this
      (newline))))                 ; insert new line

… But that's not the idiomatic way to do things in Scheme in general and Racket in particular:

  • We avoid mutation operations like set! as much as possible
  • It's a bad idea to write inside a loop, you'll get a lot of text printed in the console
  • It's not recommended to append elements at the end of a list, that'll take quadratic time. We prefer to use cons to add new elements at the head of a list, and if necessary reverse the list at the end

With all of the above considerations in place, this is how we'd implement a more idiomatic solution in Racket - using stream-filter, without printing and without using for loops:

(define (divisor N)
  (stream->list                       ; convert stream into a list
   (stream-filter                     ; filter values
    (lambda (i) (zero? (modulo N i))) ; that meet a given condition
    (in-range 1 N))))                 ; generate a stream in the range 1..N

Yet another option (similar to @uselpa's answer), this time using for/fold for iteration and for accumulating the value - again, without printing:

(define (divisor N)
  (reverse                    ; reverse the result at the end
   (for/fold ([acc '()])      ; `for/fold` to traverse input and build output in `acc`
     ([i (in-range 1 N)])     ; a stream in the range 1..N
     (if (zero? (modulo N i)) ; if the condition holds
         (cons i acc)         ; add element at the head of accumulator
         acc))))              ; otherwise leave accumulator alone

Anyway, if printing all the steps in-between is necessary, this is one way to do it - but less efficient than the previous versions:

(define (divisor N)
  (reverse
   (for/fold ([acc '()])
     ([i (in-range 1 N)])
     (if (zero? (modulo N i))
         (let ([ans (cons i acc)])
           ; inefficient, but prints results in ascending order
           ; also, `(displayln x)` is shorter than `(write x) (newline)`
           (displayln (reverse ans))
           ans)
         acc))))
0
votes

To do multiple things in a for-loop in Racket, you just write them after each other. So to display L after each iteration, you'd do this:

(define (divisor N)
  (define L '())
  (for ([i (in-range 1 N)])
    (if (equal? (modulo N i) 0)
        (append L (list i))
        L)
    (write L))
  L)

Note that you need parentheses to call a function, so it's (write L) - not write L. I also replaced your write L outside of the for-loop with just L because you (presumably) want to return L from the function at the end - not print it (and since you didn't have parentheses around it, that's what it was doing anyway).

What this will show you is that the value of L is () all the time. The reason for that is that you never change L. What append does is to return a new list - it does not affect the value of any its arguments. So using it without using its return value does not do anything useful.

If you wanted to make your for-loop work, you'd need to use set! to actually change the value of L. However it would be much more idiomatic to avoid mutation and instead solve this using filter or recursion.

0
votes

'Named let', a general method, can be used here:

(define (divisors N)
  (let loop ((n 1)                ; start with 1
             (ol '()))            ; initial outlist is empty;
    (if (< n N)
        (if(= 0 (modulo N n))
           (loop (add1 n) (cons n ol))  ; add n to list
           (loop (add1 n) ol)           ; next loop without adding n
           )
        (reverse ol))))           

Reverse before output since items have been added to the head of list (with cons).