I originally wrote this as an answer to a question on Stack Overflow, but it turned out so nicely that I decided to post it here to see if I can make it even better.
(defn char-seq
"Returns a lazy sequence of characters from rdr. rdr must implement
java.io.Reader."
[rdr]
(let [c (.read rdr)]
(if-not (neg? c)
(cons (char c) (lazy-seq (char-seq rdr))))))
(defn line-offsets
"Returns a lazy sequence of offsets of all lines in s."
[s]
(if (seq s)
(->> (partition-all 3 1 s)
(map-indexed
(fn [i [a b c]]
(cond
(= b \newline) (if c (+ 2 i))
(= a \return) (if b (inc i)))))
(filter identity)
(cons 0))))
(defn ordered-line-seq
"Returns the lines of text from raf at each offset in offsets as a lazy
sequence of strings. raf must implement java.io.RandomAccessFile."
[raf offsets]
(map (fn [i]
(.seek raf i)
(.readLine raf))
offsets))
Example usage:
(let [filename "data.txt"
offsets (with-open [rdr (clojure.java.io/reader filename)]
(shuffle (line-offsets (char-seq rdr))))]
(with-open [raf (java.io.RandomAccessFile. filename "r")]
(run! println (ordered-line-seq raf offsets))))
I'm interested in any way to improve this code, but here are some specific things I'm looking for:
- Since
RandomAccessFile
obviously uses byte offsets, andline-offsets
returns character offsets, this code won't work for Unicode files. Is there a way to fix that problem without adding a lot of complexity? - Are
a
,b
, andc
good names for the parameters of the anonymous function inline-offsets
? - What exactly are the guidelines for parameter ordering in named functions like
ordered-line-seq
?