2010-12-27 7 views
11

整数インデックス(dotimesなど)でClojure関数を複数回実行するだけでなく、既成のシーケンス/リスト(「for」など)として結果を取得したいことがよくあります。"dotimes"と "for"機能の間には何かがありますか?

私はこのような何かをしたいと思いますすなわち:明らかにそれを行うことが可能であろう

(fortimes [i 10] (* i i)) 

=> (0 1 4 9 16 25 36 49 64 81) 

(for [i (range 10)] (* i i)) 

しかし、私は一時的に作成し、捨てる避けたいのですが可能な限り範囲内のリスト。

Clojureでこれを達成する最も良い方法は何ですか?

+0

この質問の最新の情報は何ですか? clj-iterateが最善の解決策ですか、それとも良い選択肢がありますか? – jcheat

答えて

5

私は非常に効率的にこのと繰り返し、他の種類の操作を行うことができ、反復マクロを書きました。パッケージはclj-iterateと呼ばれ、githubとclojarsの両方にあります。例:

user> (iter {for i from 0 to 10} {collect (* i i)}) 
(0 1 4 9 16 25 36 49 64 81 100) 

これで一時リストは作成されません。

+0

+1すてきな小さなツールです!興味のある問題として、一時的なリストをどのように回避することができますか? – mikera

6

2番目の例に示すように、forループで範囲を生成することは、Clojureでこの問題を解決するための慣用的な解決策です。

Clojureは機能的なパラダイムに基づいているため、デフォルトでClojureでプログラミングすることで、このような一時的なデータ構造が生成されます。しかし、 "range"コマンドと "for"コマンドの両方がレイジーシーケンスで動作するので、このコードを書くことは、テンポラリレンジデータ構造全体を強制的にメモリに強制的に存在させることはありません。したがって、この例で使用されているように、適切に使用されている場合は、遅延セクションのメモリオーバーヘッドが非常に低くなります。また、あなたの例の計算オーバーヘッドは控えめで、範囲の大きさに比例して増加するだけです。これは、典型的なClojureコードの許容オーバーヘッドとみなされます。

このオーバーヘッドを完全に回避する適切な方法は、テンポラリ範囲リストが絶対的にあなたの状況に正当に受け入れられない場合、アトムまたはトランジェントを使用してコードを記述することです。http://clojure.org/transients。しかし、これを行うと、Clojureプログラミングモデルの利点のほんの一部を放棄して、やや優れたパフォーマンスが得られます。

3

range関数で作成された遅延シーケンスを「作成して投げ捨てる」ことになぜ関心があるのか​​わかりません。 dotimesによって行われる限定的な反復は、より効率的である可能性が高く、インライン増分で各ステップと比較しますが、そこに独自のリスト連結を表すために追加コストを支払うことがあります。

典型的なLispの解決策は、あなたが行くにつれて構築するリストに新しい要素を追加し、そのビルドアップリストを破壊的に逆にして戻り値を返すことです。一定時間内にリストに追加することを可能にする他の技法はよく知られているが、それらは常により前であることを示しているとは限らない。アプローチ。 Clojureので

、あなたはconj!機能の破壊的な行動に頼って、そこにトランジェントを使用することができます。

(let [r (transient [])] 
    (dotimes [i 10] 
    (conj! r (* i i))) ;; destructive 
    (persistent! r)) 

動作するようだが、1は「へconj!を使用してはならないことthe documentation on transientsを警告しますbash値の代わりに "—、つまり、戻り値をキャッチする代わりに破壊的な動作をカウントすることです。したがって、そのフォームを書き直す必要があります。

上記のrを上記の新しい値に再バインドするには、atomを使用してさらに1つの間接レベルを導入する必要があります。しかし、その時点では、我々はちょうどdotimesと戦っているので、looprecurを使って独自のフォームを書く方が良いでしょう。

ベクトルを反復バウンドと同じサイズに事前に割り当てることができればいいと思います。私はそうする方法を見ません。

+0

余分なシーケンスを作成して捨てないようにする主な理由は、ゴミを最小限に抑えることです。はい、ガベージコレクションは今日は本当に安価ですが、一部のアプリケーションでは本物の問題である待ち時間/ GC一時停止の問題が発生します – mikera

3
(defmacro fortimes [[i end] & code] 
    `(let [finish# ~end] 
    (loop [~i 0 results# '()] 
     (if (< ~i finish#) 
     (recur (inc ~i) (cons [email protected] results#)) 
     (reverse results#))))) 

例:

(fortimes [x 10] (* x x)) 

ができます:

(0 1 4 9 16 25 36 49 64 81) 
+0

ありがとうJohn :-)!それは非常にエレガントな小さな解決策です。その逆を行う前に、まだ不必要な一時的なリストを作成していますが、そうですか?それを避ける方法はありますか?私は実際に逆の順序でコードを実行するのが理にかなっていると思っていますが、それは副作用と混同する可能性があります。 – mikera

1

私は登録されていないので、あなたのコメントに答えることができないようです。ただし、clj-iterateはPersistentQueueを使用します。PersistentQueueは、ランタイムライブラリの一部ですが、リーダからは公開されません。

これは基本的にあなたが最後に結びつけることができるリストです。

関連する問題