23

...変更可能なデータを使用する命令型プログラミングは、私の脳に深く入り込みすぎているのかもしれませんが、Clojure でデータのベクトルを構築するためのコードは、冗長で扱いにくく、複雑であることがわかります。もっと良い方法があるはずです!

Ruby では、次のようなコードを記述できます。

results = []
a_collection.each do |x|
  x.nested_collection.each do |y|
    next if some_condition_holds
    results << y
  end
end

Clojure では、おそらく次の (恐ろしい) コードのように、再帰関数を使用するよりも良い方法を知りません。

; NEWBIE ALERT! NEWBIE ALERT!
(loop [results   []
       remaining a_collection]
  (if (empty? remaining)
      results
      (recur
        (loop [results results
               nested  (nested_collection (first remaining))]
           (if (empty? nested)
               results
               (if (some_condition_holds)
                   (recur results (rest nested))
                   (recur (conj results (first nested)) (rest nested))))) 
        (rest remaining))))

変更可能なデータと反復ループがなければ、再帰を使用してコレクションを構築する必要があります。このような再帰関数にはそれぞれ(empty?)ガード句などが必要です。全体が非常に反復的で、叫びたくなるほどで​​す。

単純なケースでmapは十分ですが、複数レベルのネストがあり、各レベルで反復をスキップする必要がある条件がある場合を考えています。

Common Lisp では、loopマクロまたはmapcan. Clojure には のようなものはありませんmapcanか?

4

5 に答える 5

25

オプションの見栄えが良いと思う降順で:

(for [x coll,
      y (nested-collection x)
      :when (not (some-condition-holds y))]
  y)

または、構文を使用する代わりにmapandのような関数から構築したい場合は、次のようにします。mapcatfor

(mapcat (fn [x]
          (remove some-condition-holds
                  (nested-collection x)))
        coll)

本当に熱心であれば、部分関数アプリケーションと構成で構築することもできます。

(mapcat (comp (partial remove some-condition-holds)
              nested-collection)
        coll)

この 3 番目のスタイルは、Clojure ではあまり適切に読み取れませんが、他のいくつかの言語では同等のコードが非常に優れています。たとえば、Haskell では次のようになります。

coll >>= (filter (not . someConditionHolds) . nestedCollection)
于 2012-07-03T00:01:50.600 に答える
7
(mapcat (fn [y] (filter condition y)) x)
于 2012-07-03T00:59:13.853 に答える
7

高階関数を使用するなどのFPの概念を使用して、指定された問題を解決する方法に関する回答をすでに提供している人もいます。既存のコードにつながる思考プロセスを分析し、それを他の人が提供したFPソリューションと比較すると、「処理された結果を格納するための変数を持っている」と考えるときはいつでも、命令型のORにつながることがわかります。ステップバイステップのソリューションであるため、結果の保存は「ベクトル変数」であると考えたため、Clojureコードはほとんどの場合必須です。このような考え方では、「表現の評価」や「構成による問題の解決」に基づくFPの概念を適用することはできません。

于 2012-07-03T05:20:24.197 に答える
4

高階関数を使用すると、シーケンスをより美しくすることができますが、シーケンスの考え方やシーケンスの変換に慣れるまでには時間がかかります。

これを書くには多くの方法があります:

user> (into [] a_colletion)
[0 1 2 3 4 5 6 7 8 9]

user> (vec a_colletion)
[0 1 2 3 4 5 6 7 8 9]

user> (for [x a_colletion :when (even? x)] x) 
(0 2 4 6 8)

より複雑な例は次のようになります。

(flatten (for [x (map extract-from-nested-collection a_collection)
                 :when (test-conditions? x)]
            x))

ネストされたコレクションを作成する

user> (def a_collection (map #(reductions + (range %)) (range 1 5)))
#'user/a_collection 
user> a_collection
((0) (0 1) (0 1 3) (0 1 3 6))

a_collection の各要素からネストされたコレクションを取得し、それらのいくつかをスキップします。

user> (map #(filter pos? %) a_collection)
(() (1) (1 3) (1 3 6))

ネストされたコレクションを一緒に追加します

user> (flatten (map #(filter pos? %) a_collection))
(1 1 3 1 3 6)

フラット化されたコレクションから3より大きいものをいくつかフィルタリングし、それぞれを2乗します

user> (for [x (flatten (map #(filter pos? %) a_collection))
              :when (<= x 3)]
           (* x x))
(1 1 9 1 9)
user> 
于 2012-07-02T22:41:46.047 に答える
2

アマロイの答えは、慣用的な機能スタイルに従い、怠惰なシーケンスを作成したい場合におそらく最適です。

(怠惰なシーケンスではなく)ベクトルを強制的に構築することに実際に興味がある場合は、おそらく次のようにアトムとdoseqを使用して構築します。

(let [v (atom [])]
  (doseq [x (range 5)
          y (range 5)]
    (if (> y x)
      (swap! v conj (str x y))))
  @v)

=> ["01" "02" "03" "04" "12" "13" "14" "23" "24" "34"]

ご覧のとおり、これは構造がRubyコードと非常に似ていることになります。

これはreduceを使用して行うこともできますが、入力シーケンスが1つしかない場合に最適です。例:

(reduce
  (fn [v x] 
    (if (even? x)
      (conj v x)
      v))
  []
  (range 20))

=> [0 2 4 6 8 10 12 14 16 18]
于 2012-07-03T08:01:29.657 に答える