Аналог Python для range() в Common Lisp
Как создать список последовательных чисел в Common Lisp?
Другими словами, что эквивалентно Python range
функция в Common Lisp?
В питоне range(2, 10, 2)
возвращается [2, 4, 6, 8]
с первым и последним аргументами, являющимися необязательными. Я не мог найти идиоматический способ создать последовательность чисел, хотя Emacs Lisp имеет number-sequence
,
Диапазон можно эмулировать с помощью макроса цикла, но я хочу знать, как можно сгенерировать последовательность чисел с начальной и конечной точками и шагом.
Связанный: Аналог диапазона Питона в Схеме
11 ответов
Не существует встроенного способа генерирования последовательности чисел, канонический способ сделать это состоит в том, чтобы сделать одно из:
- использование
loop
- Напишите полезную функцию, которая использует
loop
Пример реализации будет (это принимает только подсчет "от низкого" до "высокого"):
(defun range (max &key (min 0) (step 1))
(loop for n from min below max by step
collect n))
Это позволяет вам указать (необязательное) минимальное значение и (необязательное) значение шага.
Чтобы сгенерировать нечетные числа: (range 10 :min 1 :step 2)
Александрия реализует йоту схемы:
(ql:quickload :alexandria)
(alexandria:iota 4 :start 2 :step 2)
;; (2 4 6 8)
Вот как я могу подойти к проблеме:
(defun generate (from to &optional (by 1))
#'(lambda (f)
(when (< from to)
(prog1 (or (funcall f from) t)
(incf from by)))))
(defmacro with-generator ((var from to &optional (by 1)) &body body)
(let ((generator (gensym)))
`(loop with ,generator = (generate ,from ,to ,by)
while
(funcall ,generator
#'(lambda (,var) ,@body)))))
(with-generator (i 1 10)
(format t "~&i = ~s" i))
Но это только общая идея, есть много возможностей для улучшения.
Хорошо, так как здесь, кажется, есть обсуждение. Я предположил, что на самом деле нужен аналог Python range
функция генератора. Который, в определенном смысле, генерирует список чисел, но делает это, выдавая число на каждой итерации (чтобы не создавать более одного элемента за раз). Генераторы - это довольно редкое понятие (его реализуют на нескольких языках), поэтому я предположил, что упоминание Python предполагает, что именно эта функция желательна.
Следуя некоторой критике моего примера выше, вот другой пример, который иллюстрирует причину, по которой генератор может использоваться, а не простой цикл.
(defun generate (from to &optional (by 1))
#'(lambda ()
(when (< from to)
(prog1 from
(incf from by)))))
(defmacro with-generator
((var generator &optional (exit-condition t)) &body body)
(let ((g (gensym)))
`(do ((,g ,generator))
(nil)
(let ((,var (funcall ,g)))
(when (or (null ,var) ,exit-condition)
(return ,g))
,@body))))
(let ((gen
(with-generator (i (generate 1 10) (> i 4))
(format t "~&i = ~s" i))))
(format t "~&in the middle")
(with-generator (j gen (> j 7))
(format t "~&j = ~s" j)))
;; i = 1
;; i = 2
;; i = 3
;; i = 4
;; in the middle
;; j = 6
;; j = 7
Это, опять же, только иллюстрация цели этой функции. Вероятно, расточительно использовать его для генерации целых чисел, даже если вам нужно сделать это в два этапа, но лучше всего использовать генераторы с синтаксическими анализаторами, когда вы хотите получить более сложный объект, который построен на основе предыдущего состояния синтаксического анализатора, например, и куча других вещей. Ну, вы можете прочитать аргумент об этом здесь: http://en.wikipedia.org/wiki/Generator_%28computer_programming%29
Использование рекурсии:
(defun range (min max &optional (step 1))
(when (<= min max)
(cons min (range (+ min step) max step))))
В простой форме с указанием start, stop, step:
(defun range (start stop step)
(do (
(i start (+ i step))
(acc '() (push i acc)))
((>= i stop) (nreverse acc))))
Не найдя того, что я хотел, и не желая использовать внешний пакет, я закончил тем, что написал свою собственную версию, которая отличается от версии Python (надеюсь, улучшается) и избегает зацикливания. Если вы думаете, что это действительно неэффективно и может улучшить это, пожалуйста, сделайте.
;; A version of range taking the form (range [[first] last [[step]]]).
;; It takes negative numbers and corrects STEP to the same direction
;; as FIRST to LAST then returns a list starting from FIRST and
;; ending before LAST
(defun range (&rest args)
(case (length args)
( (0) '())
( (1) (range 0 (car args) (if (minusp (car args)) -1 1)))
( (2) (range (car args) (cadr args)
(if (>= (car args) (cadr args)) -1 1)))
( (3) (let* ((start (car args)) (end (cadr args))
(step (abs (caddr args))))
(if (>= end start)
(do ((i start (+ i step))
(acc '() (push i acc)))
((>= i end) (nreverse acc)))
(do ((i start (- i step))
(acc '() (push i acc)))
((<= i end) (nreverse acc))))))
(t (error "ERROR, too many arguments for range"))))
;; (range-inc [[first] last [[step]]] ) includes LAST in the returned range
(defun range-inc (&rest args)
(case (length args)
( (0) '())
( (1) (append (range (car args)) args))
( (2) (append (range (car args) (cadr args)) (cdr args)))
( (3) (append (range (car args) (cadr args) (caddr args))
(list (cadr args))))
(t (error "ERROR, too many arguments for range-inc"))))
Примечание: я также написал версию схемы
Необходимо реализовать (range n)
в крошечном Лиспе, который только чтоdotimes
а также setq
доступный:
(defun range (&rest args)
(let ( (to '()) )
(cond
((= (length args) 1) (dotimes (i (car args))
(push i to)))
((= (length args) 2) (dotimes (i (- (cadr args) (car args)))
(push (+ i (car args)) to))))
(nreverse to)))
Пример:
> (range 10)
(0 1 2 3 4 5 6 7 8 9)
> (range 10 15)
(10 11 12 13 14)
На всякий случай, вот аналог ответа user1969453, который вместо списка возвращает вектор:
(defun seq (from to &optional (step 1))
(do ((acc (make-array 1 :adjustable t :fill-pointer 0))
(i from (+ i step)))
((> i to) acc) (vector-push-extend i acc)))
Или, если вы хотите предварительно выделить вектор и пропустить идиому 'vector-push':
(defun seq2 (from to &optional (step 1))
(let ((size (+ 1 (floor (/ (- to from) step))))) ; size is 1 + floor((to - from) / step))
(do ((acc (make-array size))
(i from (+ i step))
(count 0 (1+ count)))
((> i to) acc) (setf (aref acc count) i))))
Вот функция диапазона для генерации списка чисел. Мы используем do "loop". Если существует такая вещь, как функциональный цикл, то сделать макрос можно. Хотя нет рекурсии, когда вы создаете do, я нахожу мышление очень похожим. Вы рассматриваете каждую переменную в do так же, как вы рассматриваете каждый аргумент в рекурсивном вызове.
Я использую список * вместо минусов. list * точно такой же, как и cons, за исключением того, что вы можете иметь 1, 2 или более аргументов. (список 1 2 3 4 ноль) и (минусы 1 (минусы 2 (минусы 3 (минусы 4 ноль))))).
(defun range (from-n to-n &optional (step 1)) ; step defaults to 1
(do ((n from-n (+ n step)) ; n initializes to from-n, increments by step
(lst nil (list* n lst))) ; n "pushed" or "prepended" to lst
((> n to-n) ; the "recursion" termination condition
(reverse lst)))) ; prepending with list* more efficient than using append
; however, need extra step to reverse lst so that
; numbers are in order
Вот тестовая сессия:
CL-USER 23> (диапазон 0 10)
(0 1 2 3 4 5 6 7 8 9 10)
CL-USER 24> (диапазон 10 0 -1)
NIL
CL-USER 25> (диапазон 10 0 1)
NIL
CL-USER 26> (диапазон 1 21 2)
(1 3 5 7 9 11 13 15 17 19 21)
CL-USER 27> (реверс (диапазон 1 21 2))
(21 19 17 15 13 11 9 7 5 3 1)
CL-USER 28>
Эта версия не работает для уменьшающихся последовательностей. Однако вы видите, что можете использовать реверс, чтобы получить убывающую последовательность.
Рекурсивное решение:
(defun range(min max &optional (step 1))
(if (> min max)
()
(cons min (range (+ min step) max step))))
Пример:
(range 1 10 3)
(1 4 7 10)