4
\$\begingroup\$

An elevator starts on the ground floor 0 and moves up or down by one floor at a time as described by an input string travel. Here's an example travel string udddudduuuuddu where u is a move up 1 floor and d is a move down 1 floor.

count-basement-trips takes a travel string and counts how many times the elevator returns back to the ground floor from the dark and mysterious underground levels.

(defun count-basement-trips (travel)
  (flet ((finished-basement-trip-p (old-floor new-floor)
           (and (= -1 old-floor)
                (=  0 new-floor)))
         (change (c)
           (if (char= c #\u) 1 -1)))
    (let ((floor 0)
          (count 0))
      (loop for c across travel
            do (let ((new-floor (+ floor (change c))))
                 (if (finished-basement-trip-p floor new-floor)
                     (incf count))
                 (setf floor new-floor)))
      count)))

(count-basement-trips "udddudduuuuddu")

The last expression evaluates to 2.

What changes would make this more idiomatic Common Lisp? My goal is to speak more like a native. Thanks!

\$\endgroup\$
3
\$\begingroup\$

A few points.

  1. if with only the “then” case is usually written with when, which is in general more handy since it allows several forms inside its body. So you could write (when (finished-basement-trip-p floor new-floor) (incf count)).

  2. In a loop you can define variables that change at each iteration with the clause for variable = expression, and this in many cases avoid the use of nested let clauses, that make more difficult to read the program. Another useful iteration clause is for variable = expression1 then expression2, which assigns to variabile the value of expression1 during the first iteration, and in all the others the value of expression2.

  3. In a loop, if you need to count something and then return its value, you can use the “numeric accumulation” clause count, analogous to the sum clause, in which you count how many times a boolean espression is true.

Here is a proposed simplification. Note that in my version I do not use local functions since I think the program is easy enough to read without them. This is just a personal opinion.

(defun count-basement-trips (travel)
  (let ((floor 0))
    (loop for c across travel
          for up = (char= c #\u)
          do (incf floor (if up 1 -1))
          count (and (zerop floor) up))))

Here's the buggy version suggested by @user235906 in a comment that got deleted. @Renzo points out it gives an incorrect answer for the test string "ud":

(defun count-basement-trips (travel)
  (loop for c across travel
        for up? = (char= c #\u)
        for floor = 0 then (incf floor (if up? 1 -1))
        for completed-trip? = (and up? (zerop floor))
        count completed-trip?))

And here's the suggested fix:

(defun count-basement-trips (travel)
  (loop for c across travel
        for up? = (char= c #\u)
        for change = (if up? 1 -1)
        for floor = change then (incf floor change)
        for completed-trip? = (and up? (zerop floor))
        count completed-trip?))
\$\endgroup\$
3
  • 1
    \$\begingroup\$ @user235906, you are welcome! Note that the solution that you proposed in the comment has been the first that I thought of. But it has a subtle error! Try it with the travel "ud", then try the same travel with my solution, and then you will find the problem. Then think about it to understand the reason of the error! \$\endgroup\$ – Renzo Jan 31 at 10:41
  • \$\begingroup\$ For a good tutorial on loop, see the chapter “LOOP for Black Belts” of the book “Practical Common Lisp”. \$\endgroup\$ – Renzo Jan 31 at 10:49
  • \$\begingroup\$ (note: Renzo is referring to a comment I deleted after failing to edit it, while the UI hadn't yet updated to show me his response... My buggy solution he's referring to is to use the then construct to use 0 as the initial value of floor instead of binding floor in a surrounding let) \$\endgroup\$ – user235906 Jan 31 at 19:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.