2010-07-21 4 views
4

Я пишу реализацию Clojure из this coding challenge, пытаясь найти среднюю длину записи последовательности в формате FASTA:Улучшения Clojure отложенным сло использование для итерационного разбора текста

>1 
GATCGA 
GTC 
>2 
GCA 
>3 
AAAAA 

Для получения дополнительной информации см это related StackOverflow post о решение Эрланга.

Моя начинающая попытка 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 корректным синтаксическим анализом записи файла по записи?

ответ

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)) 
+1

На самом деле я думаю, что это вполне вероятно, имеет значение с большими наборами данных ... Я даже отправил ответ, указывая на это ранее, но затем предложил смешной способ решения проблемы в мгновенном промежуток разума - +1 для правильного решения этой проблемы. –

+1

Я думаю, что группы линий между> s должны рассматриваться как отдельные записи; что-то вроде '(partition-by # (. startsWith^String%"> "))' может помочь. Общая идея остается прежней. –

+0

ataggart и Michal, большое спасибо за эти указатели. С ними, вот более чистая версия, которая заканчивается в 1/4 раза: http://gist.github.com/485853. Это примерно в 2 раза медленнее, чем моя версия Python, включая время запуска JVM из командной строки. Я многому учусь из этого упражнения; если есть другие видимые области для улучшения, пожалуйста, дайте мне знать, и я могу перебирать другую версию. –

1

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

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

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