我正在尝试读取 CSV 文件并将其解析为向量图。因此,映射的键是 CSV 中的列名称,映射的值是包含 CSV 中的值列的向量。
clojure.data.csv
来读取文件,即使 CSV 文件(在here找到)只有 32 MB,我的代码运行速度确实很慢。
(require '[clojure.data.csv :as csv]
'[clojure.java.io :as io])
(defn csv->df [file-path]
(with-open [reader (io/reader file-path)]
(let [in-file (csv/read-csv reader)
names (first in-file)
data (rest in-file)]
(zipmap (map keyword names) (apply mapv vector data)))))
(csv->df "data/flights.csv")
我怀疑我正在做一些与惰性序列相关的愚蠢的事情,因为作为一个 Clojure 新手,我仍然在掌握它们,但我无法确定问题的根本原因。
是否可以重组此功能,使其运行速度不至于缓慢?
你并没有从懒惰中受益,因为转置矩阵(这就是
apply map vector
正在做的事情)不能是懒惰的。但你并没有做任何特别错误的事情。在我的机器上进行的测试中,9 秒是在此文件上调用 read-csv
并迭代结果所需的时间。你的函数大约需要两倍的时间。因此,您的处理速度并不快,但如果您以某种方式设法在零时间内完成所有后处理,那么您所希望的最好结果是整个过程加速 50%。
我认为如果您使用 clojure.data.csv,这些成本是不可避免的。将内容打包到整洁的 Clojure 数据结构中非常方便,但它并不是免费的。我尝试使用 FastCSV 进行比较:读取并丢弃整个文件的速度大约快 20 倍,并且从文件生成向量=向量的速度也同样快。转置仍然很慢,但整个过程只需 5 秒,而不是您的函数的 17 秒。这是我写的;不灵活且笨重,但它产生相同的结果并且足够简单:
(defmacro row-fn [[arg] & body]
(let [x (gensym 'arg)]
`(reify java.util.function.Consumer
(accept [this ~x]
(let [~(with-meta arg {:tag `CsvRow}) ~x]
~@body)))))
(defn fastcsv->df [file-path]
(let [headers (promise)
csv (-> (CsvReader/builder)
(.build (io/reader file-path))
(.spliterator))
rows (atom (transient []))]
(.tryAdvance csv (row-fn [row]
(deliver headers (map keyword (.getFields row)))))
(.forEachRemaining csv (row-fn [row]
(swap! rows conj! (.getFields row))))
(zipmap @headers (apply map vector (persistent! @rows)))))