Repository: chrovis/cljam
Pull request #122 (2017-11-13 05:14:23)

Fix some serialization functions.
#### Summary
Minor fixes for performance improvement and file compatibility.

#### Changes
- Replace (apply str (interpose ...)) with clojure.stirng/join.
- Use CharBuffer to serialize FASTQ.
- Use \tab for delimiting BED fields.
    - Although BED fields [can be whitespace-delimited or tab-delimited](https://genome.ucsc.edu/FAQ/FAQformat.html#format1), some tools don't support whitespace-delimited BED.

#### Tests

- `lein test :all` 🆗


Patch:
diff --git a/src/cljam/io/bed.clj b/src/cljam/io/bed.clj
index 79089fc..6d2222d 100644
--- a/src/cljam/io/bed.clj
+++ b/src/cljam/io/bed.clj
@@ -66,8 +66,8 @@
 (defn- long-list->str
   "Inverse function of str->long-list."
   [xs]
-  (when-not (nil? xs)
-    (apply str (interpose "," xs))))
+  (when (seq xs)
+    (cstr/join "," xs)))
 
 (defn- update-some
   "Same as update if map 'm' contains key 'k'. Otherwise returns the original map 'm'."
@@ -138,8 +138,7 @@
            (update-some :block-starts long-list->str))
        ((apply juxt bed-columns))
        (take-while identity)
-       (interpose " ")
-       (apply str)))
+       (cstr/join \tab)))
 
 (defn- header-or-comment?
   "Checks if given string is neither a header nor a comment line."
@@ -215,8 +214,7 @@
   (let [w ^BufferedWriter (.writer wtr)]
     (->> xs
          (map serialize-bed)
-         (interpose "\n")
-         ^String (apply str)
+         (cstr/join \newline)
          (.write w))))
 
 (defn write-fields
@@ -225,6 +223,5 @@
   (let [w ^BufferedWriter (.writer wtr)]
     (->> xs
          (map (comp serialize-bed denormalize))
-         (interpose "\n")
-         ^String (apply str)
+         (cstr/join \newline)
          (.write w))))
diff --git a/src/cljam/io/fastq.clj b/src/cljam/io/fastq.clj
index b3d5e57..1eab25f 100644
--- a/src/cljam/io/fastq.clj
+++ b/src/cljam/io/fastq.clj
@@ -4,7 +4,8 @@
             [clojure.string :as string]
             [cljam.io.protocols :as protocols]
             [cljam.util :as util])
-  (:import [java.io Closeable]))
+  (:import [java.io Closeable]
+           [java.nio CharBuffer]))
 
 (declare read-sequences write-sequences)
 
@@ -87,7 +88,7 @@
 
 (defn- ^String serialize-fastq
   "Serialize a FASTQRead to FASTQ format string."
-  [^FASTQRead {:keys [name sequence quality]}
+  [^FASTQRead {:keys [^String name ^String sequence quality]}
    {:keys [encode-quality] :or {encode-quality :phred33}}]
   {:pre [(not-empty name)
          (not-empty sequence)
@@ -97,16 +98,24 @@
                     :phred33 (<= 0 % 93)
                     :phred64 (<= 0 % 62)
                     true) quality)]}
-  (-> [(str "@" name)
-       sequence
-       "+"
-       (apply str (map #(case encode-quality
-                          :phred33 (char (+ % 33))
-                          :phred64 (char (+ % 64))
-                          %)
-                       quality))]
-      (interleave (repeat \newline))
-      (as-> x (apply str x))))
+  (let [cb (CharBuffer/allocate (+ 6 (.length name) (.length sequence) (.length sequence)))]
+    (.put cb \@)
+    (.put cb name)
+    (.put cb \newline)
+    (.put cb sequence)
+    (.put cb \newline)
+    (.put cb \+)
+    (.put cb \newline)
+    (if (string? quality)
+      (.put cb ^String quality)
+      (doseq [q quality]
+        (.put cb (char (case encode-quality
+                         :phred33 (+ q 33)
+                         :phred64 (+ q 64)
+                         q)))))
+    (.put cb \newline)
+    (.flip cb)
+    (.toString cb)))
 
 (defn write-sequences
   "Write given sequence of reads to a FASTQ file."
diff --git a/src/cljam/io/vcf/writer.clj b/src/cljam/io/vcf/writer.clj
index 7abbd9b..eac125f 100644
--- a/src/cljam/io/vcf/writer.clj
+++ b/src/cljam/io/vcf/writer.clj
@@ -58,8 +58,7 @@
          (:species m) (conj (str "species=\"" (:species m) "\""))
          (:taxonomy m) (conj (str "taxonomy=" (:taxonomy m)))
          (:idx m) (conj (str "idx=" (:idx m))))
-       (interpose ",")
-       (apply str)))
+       (cstr/join \,)))
 
 (defn- stringify-meta-info-info
   [m]
@@ -70,16 +69,14 @@
          (:source m) (conj (str "Source=" (:source m)))
          (:version m) (conj (str "Version=" (:version m)))
          (:idx m) (conj (str "idx=" (:idx m))))
-       (interpose ",")
-       (apply str)))
+       (cstr/join \,)))
 
 (defn- stringify-meta-info-filter
   [m]
   (->> (cond-> [(str "ID=" (:id m))
                 (str "Description=\"" (:description m) "\"")]
          (:idx m) (conj (str "idx=" (:idx m))))
-       (interpose ",")
-       (apply str)))
+       (cstr/join \,)))
 
 (defn- stringify-meta-info-format
   [m]
@@ -88,15 +85,13 @@
                 (str "Type=" (nil->dot (:type m)))
                 (str "Description=\"" (:description m) "\"")]
          (:idx m) (conj (str "idx=" (:idx m))))
-       (interpose ",")
-       (apply str)))
+       (cstr/join \,)))
 
 (defn- stringify-meta-info-alt
   [m]
   (->> [(str "ID=" (:id m))
         (str "Description=\"" (:description m) "\"")]
-       (interpose ",")
-       (apply str)))
+       (cstr/join \,)))
 
 (defn- stringify-meta-info-sample
   [m]
@@ -104,16 +99,14 @@
         (str "Genomes=" (:genomes m))
         (str "Mixture=" (:mixture m))
         (str "Description=\"" (:description m) "\"")]
-       (interpose ",")
-       (apply str)))
+       (cstr/join \,)))
 
 (defn- stringify-meta-info-pedigree
   [m]
   (->> (range (count m))
        (map (fn [i]
               (str "Name_" i "=" (get m (keyword (str "name-" i))))))
-       (interpose ",")
-       (apply str)))
+       (cstr/join \,)))
 
 (defn stringify-structured-line
   [k m]
@@ -152,7 +145,7 @@
 
 (defn ^String stringify-header
   [header]
-  (str header-prefix (apply str (interpose "\t" header))))
+  (str header-prefix (cstr/join \tab header)))
 
 (defn write-header
   [^VCFWriter wtr header]
@@ -163,7 +156,8 @@
 
 (defn- stringify-data-line-alt
   [v]
-  (if v (apply str (interpose "," v))))
+  (if v
+    (cstr/join \, v)))
 
 (defn- stringify-data-line-qual
   [x]
@@ -181,8 +175,7 @@
                  (map keyword (drop 8 header)))
          (map #(get m* %))
          (map nil->dot)
-         (interpose "\t")
-         (apply str))))
+         (cstr/join \tab))))
 
 (defn write-variants
   [^VCFWriter wtr variants]


Test patch:
diff --git a/test/cljam/io/bed_test.clj b/test/cljam/io/bed_test.clj
index 143fac5..1801665 100644
--- a/test/cljam/io/bed_test.clj
+++ b/test/cljam/io/bed_test.clj
@@ -197,10 +197,10 @@
         "1 0 10\n1 10 20" ["TAACCCTAAC" "CCTAACCCTA"]))))
 
 (deftest bed-writer
-  (is (= (bed->raw-str (raw-str->bed "1 0 10")) "1 0 10"))
-  (is (= (bed->str (str->bed "1 0 1")) "chr1 0 1"))
-  (is (= (bed->str (str->bed "1 0 10")) "chr1 0 10"))
-  (is (= (bed->str (str->bed "1 0 1\n1 1 2")) "chr1 0 1\nchr1 1 2"))
+  (is (= (bed->raw-str (raw-str->bed "1 0 10")) "1\t0\t10"))
+  (is (= (bed->str (str->bed "1 0 1")) "chr1\t0\t1"))
+  (is (= (bed->str (str->bed "1 0 10")) "chr1\t0\t10"))
+  (is (= (bed->str (str->bed "1 0 1\n1 1 2")) "chr1\t0\t1\nchr1\t1\t2"))
   (is (= (with-open [r (bed/reader test-bed-file1)] (str->bed (bed->str (bed/read-fields r))))
          (with-open [r (bed/reader test-bed-file1)] (doall (bed/read-fields r)))))
   (is (= (with-open [r (bed/reader test-bed-file1-gz)] (str->bed (bed->str (bed/read-fields r))))