Аналоговый диапазон Python() в Common Lisp
Как создать список последовательных чисел в Common Lisp?
Другими словами, что эквивалентно функции Python range
в Common Lisp?
В Python range(2, 10, 2)
возвращается [2, 4, 6, 8]
, причем первый и последний аргументы являются необязательными. Я не мог найти идиоматический способ создания последовательности чисел, хотя Emacs Lisp имеет number-sequence
.
Диапазон может быть эмулирован с использованием макроса цикла, но я хочу знать принятый способ генерации последовательности чисел с начальными и конечными точками и шагом.
Связанный: Аналог диапазона Python в Схеме
Ответы
Ответ 1
Нет встроенного способа генерации последовательности чисел, канонический способ сделать это - сделать один из:
- Используйте
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)
Ответ 2
alexandria реализует схему iota:
(ql:quickload :alexandria)
(alexandria:iota 4 :start 2 :step 2)
;; (2 4 6 8)
Ответ 3
Вот как я могу подойти к проблеме:
(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
Ответ 4
Использование рекурсии:
(defun range (min max &optional (step 1))
(when (<= min max)
(cons min (range (+ min step) max step))))
Ответ 5
В простой форме с указанием начала, остановки, шага:
(defun range (start stop step)
(do (
(i start (+ i step))
(acc '() (push i acc)))
((>= i stop) (nreverse acc))))
Ответ 6
Не найдя того, что я хотел, и не желая использовать внешний пакет, я закончил тем, что написал свою собственную версию, которая отличается от версии 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"))))
Примечание. Я также написал версию схемы as well
Ответ 7
Вы можете попробовать змей:
"Генераторы стилей Python для Common Lisp. Включает порт itertools".
Он доступен в Quicklisp. Могут быть и другие библиотеки Common Lisp, которые могут помочь.
Ответ 8
Вот функция диапазона для генерации списка чисел.
Мы используем do "loop". Если существует такая вещь, как функциональный цикл, тогда макрос do это. Хотя нет рекурсии, когда вы создаете 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 > (range 0 10)
(0 1 2 3 4 5 6 7 8 9 10)
CL-USER 24 > (range 10 0 -1)
NIL
CL-USER 25 > (range 10 0 1)
NIL
CL-USER 26 > (range 1 21 2)
(1 3 5 7 9 11 13 15 17 19 21)
CL-USER 27 > (reverse (range 1 21 2))
(21 19 17 15 13 11 9 7 5 3 1)
CL-USER 28 >
Эта версия не работает для уменьшающихся последовательностей. Однако вы видите, что можете использовать реверс, чтобы получить убывающую последовательность.
Ответ 9
Нужно реализовать (range n)
в крошечном Lispе, который только что имел в наличии 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)