Common Lispで連続番号のリストを作成する方法は?
言い換えれば、CommonLispのPythonのrange
関数に相当するものは何ですか?
In Python range(2, 10, 2)
は[2, 4, 6, 8]
を返し、最初と最後の引数はオプションです。Emacsは、数字のシーケンスを作成する慣用的な方法を見つけることができませんでした。 LISPにはnumber-sequence
があります。
範囲はエミュレートできます ループマクロを使用 ですが、開始点と終了点、およびステップを含む一連の数値を生成するための受け入れられた方法を知りたいです。
一連の数値を生成する組み込みの方法はありません。そのための標準的な方法は、次のいずれかを実行することです。
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)
alexandriaはスキームのiotaを実装します。
(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))
しかし、これは単なる一般的な考え方であり、改善の余地がたくさんあります。
OK、ここで議論があるようですので。本当に必要なのは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
これもまた、この機能の目的の説明にすぎません。 2つのステップで行う必要がある場合でも、整数の生成に使用するのはおそらく無駄ですが、パーサーの以前の状態に基づいて構築されたより複雑なオブジェクトを生成する場合は、ジェネレーターがパーサーに最適です。たとえば、他にもたくさんあります。さて、あなたはここでそれについての議論を読むことができます: 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))))
欲しいものが見つからなかったり、外部パッケージを使いたくなかったので、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"))))
注:私は スキームバージョン も作成しました
開始、停止、ステップを指定する単純な形式では、次のようになります。
(defun range (start stop step)
(do (
(i start (+ i step))
(acc '() (Push i acc)))
((>= i stop) (nreverse acc))))
dotimes
とsetq
が利用可能になったばかりの 小さなLISP に(range n)
を実装する必要があります:
(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)
これは、数値のリストを生成するための範囲関数です。 do "loop"を使用します。機能ループのようなものがある場合、doマクロがそれです。再帰はありませんが、doを作成すると、考え方は非常に似ていることがわかります。 doの各変数は、再帰呼び出しの各引数と同じように考慮します。
consの代わりにlist *を使用します。 list *は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)
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-ユーザー28>
このバージョンは、シーケンスの減少には機能しません。ただし、reverseを使用して減少するシーケンスを取得できることがわかります。