【问题标题】:Python's range() analog in Common LispCommon Lisp 中 Python 的 range() 类比
【发布时间】:2012-12-05 22:16:00
【问题描述】:

如何在 Common Lisp 中创建一个连续数字的列表?

换句话说,Python 的 range 函数在 Common Lisp 中的等价物是什么?

在 Python 中,range(2, 10, 2) 返回[2, 4, 6, 8],第一个和最后一个参数是可选的。我找不到创建数字序列的惯用方法,尽管 Emacs Lisp 有 number-sequence

范围可以模拟using loop macro,但我想知道生成具有起点和终点以及步骤的数字序列的公认方式。

相关:Analog of Python's range in Scheme

【问题讨论】:

  • 我使用delayforce 实现了类似的东西。这模仿了 Python range
  • @Vatine,是的,当然也取决于 python 版本。
  • 生成具有起点和终点以及步骤的数字序列的公认方法是使用 LOOP 或使用库函数,通常称为 IOTA。

标签: python common-lisp number-sequence


【解决方案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)

【讨论】:

  • 我试过这个函数,如果我使用 (range 10) 它可以工作,但是当我尝试 (range 10 1) 时,我在 SBCL 中得到一个回溯,上面写着 odd number of &KEY arguments [Condition of type SB-INT:SIMPLE-PROGRAM-ERROR] 如果我尝试 (range 10 1 1 )我得到unknown &KEY argument: 1 [Condition of type SB-INT:SIMPLE-PROGRAM-ERROR]我刚刚开始使用CL,有人能指出我在这里做错了什么吗?我正在使用 SBCL
  • @copyninja 您需要将其称为(range 10 :min 1)(range 10 :step 1)
【解决方案2】:

alexandria 实现了 scheme 的 iota:

(ql:quickload :alexandria)
(alexandria:iota 4 :start 2 :step 2)
;; (2 4 6 8)

【讨论】:

  • 谢谢。这才是正确的做法!
  • 指出cl21 库将它与其他一些相关结构(map-iotatake、...)集成在一起。
【解决方案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

【讨论】:

  • 我不清楚你赢得了什么(循环 i 从 1 到 10 乘 1 ...)。
  • 您已经实现了比 OP 要求的要多得多,并且以次优的方式实现。例如:函数“GENERATE”的名称是一个糟糕的选择。 WITH-GENERATOR 扩展为复杂代码,然后通过 LOOP 扩展为更复杂的代码 -> 在调试期间很糟糕。对于每个生成的项目,您还可以将一个闭包传递给另一个闭包。真是浪费。
  • 原来的问题是:'如何在 Common Lisp 中创建一个连续数字的列表?'您回答了一个不同的问题:“如何使用某种‘生成器’打印从 1 到 10 的数字。”
  • 这是值得深思的好东西。在幕后,Python 使用惰性/生成风格实现 range,类似于 Clojure 的 range 函数。如果所需的用法是 1 到 10,则循环很好。如果它是 1 到 100 亿,那么您可能应该使用惰性序列(正如 Python 所做的那样),在 Common Lisp 中当然可以,但不是标准化的。
  • 您可以将series 库用于惰性序列、转换器等。
【解决方案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))))
    

    【讨论】:

    • +1 用于使用 do 而不是循环。一方面,我还没有克服我在阅读 Graham 的 ANSI Common Lisp 时建立的循环思维障碍
    【解决方案6】:

    你不妨试试snakes:

    “用于 Common Lisp 的 Python 样式生成器。包括一个 itertools 端口。”

    Quicklisp 中可用。可能还有其他 Common Lisp 库可以提供帮助。

    【讨论】:

      【解决方案7】:

      没有找到我想要的也不想使用外部包,我最终编写了自己的版本,该版本与 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"))))
      

      注意:我也写了scheme version

      【讨论】:

        【解决方案8】:

        这是一个生成数字列表的范围函数。 我们使用 do “循环”。如果有函数循环这样的东西,那么 do 宏就是它。虽然没有递归,但是当你构造一个do时,我发现思路很相似。您考虑 do 中的每个变量的方式与考虑递归调用中的每个参数的方式相同。

        我使用 list* 而不是 conslist*cons 完全相同,但可以有 1、2 或更多参数。 (list 1 2 3 4 nil)(cons 1 (cons 2 (cons 3 (cons 4 nil))))

        (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)

        CL-USER 25 >(范围 10 0 1)

        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-用户 28 >

        此版本不适用于递减序列。但是,您看到您可以使用 reverse 来获得递减序列。

        【讨论】:

          【解决方案9】:

          需要在 tiny Lisp 中实现 (range n),该 dotimessetq 可用:

          (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)
          

          【讨论】:

            【解决方案10】:

            以防万一,这里有一个类似于 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))))
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2016-07-30
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2019-05-10
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多