3

行の先頭のハッシュ(#)文字をカウントするコードを作成しました。これは、の見出し行によく似ています。Markdown

###1行目->3を返す
########2行目->6を返します(最初の6文字のみに注意してください。

バージョン1

(defn
  count-leading-hash
  [line]
  (let [cnt (count (take-while #(= % \#) line))]
    (if (> cnt 6) 6 cnt)))

バージョン2

(defn
  count-leading-hash
  [line]
  (loop [cnt 0]
    (if (and (= (.charAt line cnt) \#) (< cnt 6))
      (recur (inc cnt))
      cnt)))

私はtime両方の2つの実装を測定していましたが、に基づく最初のバージョンtake-whileはバージョン2より2倍高速であることがわかりました。"###### Line one"入力として、バージョン1は0.09ミリ秒、バージョン2は約0.19ミリ秒かかりました。

質問recur1.2番目の実装を遅くするのはそれですか?

質問2.バージョン1は関数型プログラミングパラダイムに近いですよね?

質問3.どちらが好きですか?なんで?(独自の実装を作成することを歓迎します。)

- アップデート -

cloujureのドキュメントを読んだ後、この関数の新しいバージョンを思いついたのですが、それは非常に明確だと思います。

(defn
  count-leading-hash
  [line]
  (->> line (take 6) (take-while #(= \# %)) count))
4

4 に答える 4

6
  1. IMO小さなコードの時間を測定するのは役に立ちません
  2. はい、バージョン 1 の方が機能的です
  3. エラーを見つけやすいので、バージョン 1 を好みます
  4. バージョン 1 の方がコードが少なく、維持コストが低いため、私はバージョン 1 を好みます。

私は次のように関数を書きます:

(defn count-leading-hash [line]
  (count (take-while #{\#} (take 6 line))))
于 2012-07-16T10:34:18.470 に答える
3
  1. いいえ、それは呼び出しに使用されるリフレクション.charAtです。関数を作成する前に呼び出す(set! *warn-on-reflection* true)と、警告が表示されます。
  2. HOFを使用している限り、確かに。
  3. ただし、最初(if (> cnt 6) 6 cnt)は のように記述した方が適切です(min 6 cnt)
于 2012-07-16T11:14:04.170 に答える
2

1:いいえrecurかなり速いです。呼び出す関数ごとに、VM からのオーバーヘッドと「ノイズ」が少しあります。たとえば、REPL は呼び出しを解析して評価する必要があります。そうしないと、ガベージ コレクションが発生する可能性があります。そのため、このような小さなコードのベンチマークは何の意味もありません。

と比べて:

(defn
  count-leading-hash
  [line]
  (let [cnt (count (take-while #(= % \#) line))]
    (if (> cnt 6) 6 cnt)))

(defn
  count-leading-hash2
  [line]
  (loop [cnt 0]
    (if (and (= (.charAt line cnt) \#) (< cnt 6))
      (recur (inc cnt))
      cnt)))

(def lines ["### Line one" "######## Line two"])

(time (dorun (repeatedly 10000 #(dorun (map count-leading-hash lines)))))
;; "Elapsed time: 620.628 msecs"
;; => nil
(time (dorun (repeatedly 10000 #(dorun (map count-leading-hash2 lines)))))
;; "Elapsed time: 592.721 msecs"
;; => nil

大きな違いはありません。

2:この場合、 loop/recurの使用は慣用的ではありません。本当に必要な場合にのみ使用し、可能な場合は他の利用可能な機能を使用することをお勧めします。コレクション/シーケンスを操作する便利な関数が多数あります。参照と例については、ClojureDocsを確認してください。私の経験では、関数型プログラミングに不慣れな命令型プログラミングのスキルを持つ人は、Clojure の経験が豊富な人よりもはるかに多く使用しloopます。/コードのにおいがすることがあります。recurlooprecur

3: 最初のバージョンの方が好きです。さまざまなアプローチがあります。

;; more expensive, because it iterates n times, where n is the number of #'s
(defn count-leading-hash [line]
  (min 6 (count (take-while #(= \# %) line))))

;; takes only at most 6 characters from line, so less expensive
(defn count-leading-hash [line]
  (count (take-while #(= \# %) (take 6 line))))

;; instead of an anonymous function, you can use `partial`
(defn count-leading-hash [line]
  (count (take-while (partial = \#) (take 6 line))))

編集:partial匿名関数と比較して、 いつ使用するかを決定する方法は?

(partial = \#)は と評価されるため、パフォーマンスに関しては問題ありません(fn [& args] (apply = \# args))#(= \# %)に変換され(fn [arg] (= \# arg))ます。どちらも非常に似ていpartialますが、任意の数の引数を受け入れる関数を提供するため、それが必要な状況では、それが適しています。ラムダ計算partialの λ (ラムダ)です。読みやすいものを使用するか、任意の数の引数を持つ関数が必要な場合に使用します。partial

于 2012-07-16T11:45:21.167 に答える
2

JVM のマイクロ ベンチマークは、自分が何をしているのかを本当に理解していない限り、ほとんどの場合誤解を招くものです。したがって、私はあなたの 2 つのソリューションの相対的なパフォーマンスにあまり重きを置きません。

最初の解決策はより慣用的です。Clojureコードで明示的なループ/再帰が実際に見られるのは、それが唯一の合理的な代替手段である場合だけです。この場合、明らかに、合理的な代替手段があります。

正規表現に慣れている場合の別のオプション:

(defn count-leading-hash [line]
     (count (or (re-find #"^#{1,6}" line) "")))
于 2012-07-16T10:35:04.833 に答える