(ループ …) 内でバッククォート/コンマ イディオムを使用するのは正しいですか?
質問
次のようなループからポイント(変換された整数)を収集するコードがあります。
(loop
for x from 1 to 100
for y from 100 downto 1
collect `(,x . ,y))
私の質問は、使用するのが正しいかということです `(,x . ,y)
この状況で?
編集:このサンプルは、100x100 項目のテーブルを生成するものではありません。ここでのコードは、2 つのループ変数の使用とその値の変換を示しているだけです。これを明確にするためにループを編集しました。私が使用する実際のループは他のいくつかの関数に依存している (そして関数自体の一部でもある) ため、呼び出しをリテラル整数に置き換えて関数からループを取り出す方が合理的です。
解決
(cons x y) を実行する方がはるかに「良い」でしょう。
しかし、 質問に答えて, 、そうすることに何も問題はありません:) (少し遅くなる点を除いて)。
他のヒント
ここでの答えはリソースの使用率だと思います(以下のとおり) この郵便受け)
たとえば、clisp では次のようになります。
[1]> (time
(progn
(loop
for x from 1 to 100000
for y from 1 to 100000 do
collect (cons x y))
()))
WARNING: LOOP: missing forms after DO: permitted by CLtL2, forbidden by ANSI
CL.
Real time: 0.469 sec.
Run time: 0.468 sec.
Space: 1609084 Bytes
GC: 1, GC time: 0.015 sec.
NIL
[2]> (time
(progn
(loop
for x from 1 to 100000
for y from 1 to 100000 do
collect `(,x . ,y)) ;`
()))
WARNING: LOOP: missing forms after DO: permitted by CLtL2, forbidden by ANSI
CL.
Real time: 0.969 sec.
Run time: 0.969 sec.
Space: 10409084 Bytes
GC: 15, GC time: 0.172 sec.
NIL
[3]>
DSM:あなたのコードには奇妙な点がいくつかあります ここ. 。ご了承ください
(loop for x from 1 to 100000
for y from 1 to 100000 do
collect `(,x . ,y))
は以下と同等です:
(loop for x from 1 to 100
collecting (cons x x))
おそらくそれはあなたが意図したものではありません。次の 3 つの点に注意してください。まず、あなたの書き方では、x と y は同じ役割を持っています。おそらくループをネストするつもりだったでしょう。第二に、y の後の do は、その後に Lisp 形式がないため、間違っています。第三に、ここでバッククォートのアプローチを使用できることは正しいですが、コードが読みにくくなり、慣用的で利益も得られないため、避けるのが最善です。
実際に何を意図していたかを推測すると、次のようなことを行うことができます (ループを使用)。
(loop for x from 1 to 100 appending
(loop for y from 1 to 100 collecting (cons x y)))
ループ マクロ (Kyle など) が気に入らない場合は、次のような別の反復構造を使用できます。
(let ((list nil))
(dotimes (n 100) ;; 0 based count, you will have to add 1 to get 1 .. 100
(dotimes (m 100)
(push (cons n m) list)))
(nreverse list))
このようなことを頻繁に行う場合は、リストを交差させるためのより一般的な関数を作成し、それにこれらの整数のリストを渡す必要があります。
ループだけでなく反復に本当に問題がある場合は、この種の処理を再帰的に行うことができます (ただし、これはスキームではないため、実装では TCO が保証されない可能性があることに注意してください)。カイルが示した関数「genint」 ここ は、一般的な (ただし標準ではない) 関数 iota のバリアントです。ただし、リストに追加するのは悪い考えです。同等の実装は次のようになります。
(defun iota (n &optional (start 0))
(let ((end (+ n start)))
(labels ((next (n)
(when (< n end)
(cons n (next (1+ n))))))
(next start))))
はるかに効率的になるはずですが、それでも末尾呼び出しではありません。これは、より一般的な 0 ベースに設定しましたが、1 またはその他の整数で開始するオプションのパラメータを指定していることに注意してください。もちろん、上記は次のように書くこともできます。
(defun iota (n &optional (start 0))
(loop repeat n
for i from start collecting i))
これには、大きな引数によってスタックがパンクしないという利点があります。実装が末尾呼び出しの削除をサポートしている場合は、次のようなことを行うことで、場違いな再帰の実行を回避することもできます。
(defun iota (n &optional (start 0))
(labels ((next (i list)
(if (>= i (+ n start))
nil
(next (1+ i) (cons i list)))))
(next start nil)))
お役に立てば幸いです!
なぜそうではないのか
(cons x y)
ちなみに、CLISP でコードを実行しようとしましたが、期待どおりに動作しませんでした。私はループ マクロの大ファンではないので、同じことを再帰的に実行する方法を次に示します。
(defun genint (stop)
(if (= stop 1) '(1)
(append (genint (- stop 1)) (list stop))))
(defun genpairs (x y)
(let ((row (mapcar #'(lambda (y)
(cons x y))
(genint y))))
(if (= x 0) row
(append (genpairs (- x 1) y)
row))))
(genpairs 100 100)