Подтвердить что ты не робот

Аналоговый диапазон Python() в Common Lisp

Как создать список последовательных чисел в Common Lisp?

Другими словами, что эквивалентно функции Python range в Common Lisp?

В Python range(2, 10, 2) возвращается [2, 4, 6, 8], причем первый и последний аргументы являются необязательными. Я не мог найти идиоматический способ создания последовательности чисел, хотя Emacs Lisp имеет number-sequence.

Диапазон может быть эмулирован с использованием макроса цикла, но я хочу знать принятый способ генерации последовательности чисел с начальными и конечными точками и шагом.

Связанный: Аналог диапазона Python в Схеме

4b9b3361

Ответ 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)