Улучшение использования clojure lazy-seq для итеративного анализа текста - PullRequest
4 голосов
/ 22 июля 2010

Я пишу реализацию Clojure этой проблемы кодирования , пытаясь найти среднюю длину записей последовательности в формате Fasta:

>1
GATCGA
GTC
>2
GCA
>3
AAAAA

Для получения дополнительной информации см. связанный пост StackOverflow о решении Erlang.

Моя попытка новичка в Clojure использует lazy-seq, чтобы попытаться прочитать в файле одну запись за раз, чтобы она масштабировалась до больших файлов.Однако он довольно жадный и медленный, поэтому я подозреваю, что он не реализован оптимально.Вот решение, использующее библиотеку 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*))))

текущая реализация занимает 44 секунды для большого файла по сравнению с 7 секундами для реализации Python.Можете ли вы предложить какие-либо предложения по ускорению кода и сделать его более интуитивным?Правильно ли используется lazy-seq при разборе записи файла по записи, как задумано?

Ответы [ 2 ]

3 голосов
/ 22 июля 2010

Это, вероятно, не имеет значения, но 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))
1 голос
/ 22 июля 2010

Ваша average функция не ленива - она ​​должна реализовать весь аргумент coll, удерживая ее за голову. Обновление: Только что понял, что мой первоначальный ответ содержал бессмысленное предложение о том, как решить вышеуказанную проблему ... аааа.К счастью, с тех пор ataggart опубликовал правильное решение.

Кроме того, ваш код на первый взгляд кажется ленивым, хотя использование read-lines в настоящее время не рекомендуется (используйте вместо него line-seq).

Если файл действительно большой и ваши функции будут вызываться большое количество раз, указание типа seq-iter в векторе аргументов seq-length - ^NameOfBiojavaSeqIterClass seq-iter, используйте #^ вместо ^если вы используете Clojure 1.1 - может иметь значение.Фактически, (set! *warn-on-reflection* true), затем компилируют ваш код и добавляют подсказки типов, чтобы удалить все предупреждения об отражениях.

...