4

私はこのコーディングチャレンジのClojure実装を作成しており、Fasta形式のシーケンスレコードの平均の長さを見つけようとしています。

>1
GATCGA
GTC
>2
GCA
>3
AAAAA

詳細な背景については、Erlangソリューションに関するこの関連するStackOverflowの投稿を参照してください。

私の初心者のClojureの試みでは、lazy-seqを使用して、一度に1レコードずつファイルを読み込もうとしているため、大きなファイルにスケーリングされます。ただし、かなりメモリを消費し、低速であるため、最適に実装されていないのではないかと思います。これは、 BioJavaライブラリを使用してレコードの解析を抽象化するソリューションです。

(import '(org.biojava.bio.seq.io SeqIOTools))
(use '[clojure.contrib.duck-streams :only (reader)])

(defn seq-lengths [seq-iter]
  "Produce a lazy collection of sequence lengths given a BioJava StreamReader"
  (lazy-seq
    (if (.hasNext seq-iter)
      (cons (.length (.nextSequence seq-iter)) (seq-lengths seq-iter)))))

(defn fasta-to-lengths [in-file seq-type]
  "Use BioJava to read a Fasta input file as a StreamReader of sequences"
  (seq-lengths (SeqIOTools/fileToBiojava "fasta" seq-type (reader in-file))))

(defn average [coll]
  (/ (reduce + coll) (count coll)))

(when *command-line-args*
  (println
    (average (apply fasta-to-lengths *command-line-args*))))

および外部ライブラリを使用しない同等のアプローチ:

(use '[clojure.contrib.duck-streams :only (read-lines)])

(defn seq-lengths [lines cur-length]
  "Retrieve lengths of sequences in the file using line lengths"
  (lazy-seq
    (let [cur-line (first lines)
          remain-lines (rest lines)]
      (if (= nil cur-line) [cur-length]
        (if (= \> (first cur-line))
          (cons cur-length (seq-lengths remain-lines 0))
          (seq-lengths remain-lines (+ cur-length (.length cur-line))))))))

(defn fasta-to-lengths-bland [in-file seq-type]
  ; pop off first item since it will be everything up to the first >
  (rest (seq-lengths (read-lines in-file) 0)))

(defn average [coll]
  (/ (reduce + coll) (count coll)))

(when *command-line-args*
  (println
    (average (apply fasta-to-lengths-bland *command-line-args*))))

現在の実装は、Python実装の7秒と比較して、大きなファイルでは44秒かかります。コードを高速化し、より直感的にするための提案はありますか?lazy-seqの使用法は、意図したとおりにファイルレコードをレコードごとに正しく解析していますか?

4

2 に答える 2

3

それはおそらく問題ではありませんが、average長さのシーケンスの頭を保持しています。
以下は完全にテストされていませんが、私があなたが望むと思うことをするためのより怠惰な方法です。

(use 'clojure.java.io) ;' since 1.2

(defn lazy-avg [coll]
  (let [f (fn [[v c] val] [(+ v val) (inc c)])
        [sum cnt] (reduce f [0 0] coll)]
    (if (zero? cnt) 0 (/ sum cnt)))

(defn fasta-avg [f]
  (->> (reader f) 
    line-seq
    (filter #(not (.startsWith % ">")))
    (map #(.length %))
    lazy-avg))
于 2010-07-21T21:54:52.177 に答える
1

あなたのaverage関数は怠惰ではありません-それはcollその頭を保持しながら議論全体を実現する必要があります。更新:私の元の答えには、上記の問題を解決する方法についての無意味な提案が含まれていることに気づきました...ああ。幸いなことに、ataggartはそれ以来正しい解決策を投稿しています。

それ以外は、コードの使用read-linesは現在推奨されていませんが、一見怠惰に見えます(line-seq代わりに使用してください)。

ファイルが非常に大きく、関数が何度も呼び出される場合は、Clojure 1.1を使用している場合の代わりに-seq-iterの引数ベクトルでタイプヒントを使用すると、大きな違いが生じる可能性があります。実際、次にコードをコンパイルし、タイプヒントを追加して、すべてのリフレクション警告を削除します。seq-length^NameOfBiojavaSeqIterClass seq-iter#^^(set! *warn-on-reflection* true)

于 2010-07-21T21:23:18.223 に答える