0
\$\begingroup\$

I quickly created this function in Lisp that resembles

numpy.random.randint(low, high=None, size=None, dtype='I')

The goal is simply to have a function that creates a list of random integers between low and high and that the list has a certain shape. I did not add the ability to choose the dtype as well, but this would be a possibility.

My function for doing this is the following:

(defun randint (&key (start 0) end (shape '(1)))
  (if (> (length shape) 3)
    (error "Shape can be maximally 3 long")
  (loop for i from 1 upto (first shape)
        if (not (null (rest shape)))
        collect (loop for j from 1 upto (first (rest shape))
                      if (not (null (rest (rest shape))))
                      collect (loop for k from 1 upto (first (rest (rest shape)))
                                    collect (+ start (random (+ 1 (- end start)))))
                      else
                      collect (+ start (random (+ 1 (- end start)))))
        else
        collect (+ start (random (+ 1 (- end start)))))))

Is there a more concise or more elegant way of writing this in Common Lisp?

\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

The seems to be no reason to use lists as opposed to arrays here. I would use multidimensional arrays instead:

(defun random-array (dimensions limit)
  (let ((ret (make-array dimensions)))
    (dotimes (pos (array-total-size ret) ret)
      (setf (row-major-aref ret pos)
            (random limit)))))
(random-array '(2 3 4) 100)
==> #3A(((26 28 3 72) (49 92 34 71) (3 96 76 38))
        ((95 97 60 84) (62 26 36 95) (83 99 12 81)))

Please see

PS. Note that your function would fail in (- end start) if you do not pass end.

\$\endgroup\$
1
\$\begingroup\$

Actually, I find the structure of the program quite complicated and difficult to read and understand. I think that the reason for this is that the program structure is patterned on the shape parameter, whose limitation to three elements is, in my opinion, rather arbitrary.

Why do not eliminate such limitation, and make the function more similar to the original numpy one, in which shape can be any list, even an empty list?

The elimination of this restriction could seem to complicate the problem, but instead it simplifies the function if we use a recursive approach on the structure of shape:

  1. if shape is empty, then generate a single random number
  2. if shape is a list, get the first element, which is a number n, apply n times the function to the rest of the shape and collect the result in a list.

In this way the function is greatly simplified:

(defun randint (&key (start 0) (end (1+ start)) (shape '()))
  (if (null shape)
      (+ start (random end))
      (loop as i below (car shape) collect (randint :start start :end end :shape (cdr shape)))))

Finally, I don't like the fact that a function has a key parameter which, if omitted, causes an error (end). So I would prefer to give it an optional value (for instance start + 1). Finally, to complete the similarity with the original function, I think we can default the shape parameter to the empty list, so that the call (randint :start n :end m) produce a single integer in the range specified.

\$\endgroup\$
1
  • \$\begingroup\$ I think this is very elegant! \$\endgroup\$
    – JNevens
    Commented Dec 19, 2017 at 14:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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