有没有更好的方法从同一个文件中读取CSV行组?

7eumitmz  于 2023-01-03  发布在  其他
关注(0)|答案(1)|浏览(121)

我有一个函数可以对12行CSV数据执行操作。
这12行是从一个更大的CSV文件中读取的。当它完成时,该过程将在随后的12行批处理中再重复5次。

(defun main ()
  (send-actions 0 12 "client-one")
  (send-actions 12 24 "client-two")
   ....
  (send-actions 60 72 "client-six")
   ...)

(defun send-actions (start end account)
  (check-upload-send (subseq (read-csv #P"~/Desktop/data.csv") start end) account))

(defun check-upload-send (table account)
  (function to check for duplicates....)
  (function to perform action 1....)
  ...)

这真的很有效,大多数时候。
它经常会抛出一个重复的错误,这是因为它将重新读取第25行(这是运行(send-actions 24 36 "client-three")的第一项)。
有没有更好的功能或方法来阅读一组csv行并在其上执行操作?具有移动到下一批12行的能力?
谢啦,谢啦
注意-read-csv来自cl-csv

gijlo24d

gijlo24d1#

让我们使用这个/tmp/test.csv文件:

01,02,03,04,05
za,zb,zc,zd,ze
11,12,13,14,15
aa,ab,ac,ad,ae
21,22,23,24,25
ba,bb,bc,bd,be
31,32,33,34,35
ca,cb,cc,cd,ce
41,42,43,44,45
da,db,dc,dd,de
51,52,53,54,55
ea,eb,ec,ed,ee

(我有6个条目,按2行分组)
当使用带有row-fn参数的cl-csv:read-csv时,行不会被收集,而是给到一个回调函数,我知道当有太多级别的回调时,有时会被称为“回调地狱”,但我倾向于喜欢这种方法,因为它很容易编写,而且,你只需要读取文件一次,并且不需要在内存中存储不确定数量的数据(在这里可以忽略不计)。
例如,我可以编写一个高阶函数make-group-reader来创建一个闭包,它以size大小的组收集行,然后在该组完成时调用另一个回调group-fn。注意,如果最后一个片段不完整,则不会调用组回调(但如果需要,您可以修改一些东西):

(defun make-group-reader (size group-fn &key (sharedp t))
  (check-type size (integer 1))
  (let ((group (make-array size :initial-element nil :fill-pointer 0)))
    (let ((limit (1- size)))
      (lambda (row)
        (when (= limit (vector-push row group))
          (funcall group-fn (if sharedp group (copy-seq group)))
          (setf (fill-pointer group) 0))))))

默认情况下,sharedp参数为T,并直接将内部向量提供给回调函数。如果愿意,可以先用COPY-SEQ复制向量,将其设置为NIL。

> (cl-csv:read-csv #P"/tmp/test.csv" :row-fn (make-group-reader 2 #'print))

#(("01" "02" "03" "04" "05") ("za" "zb" "zc" "zd" "ze")) 
#(("11" "12" "13" "14" "15") ("aa" "ab" "ac" "ad" "ae")) 
#(("21" "22" "23" "24" "25") ("ba" "bb" "bc" "bd" "be")) 
#(("31" "32" "33" "34" "35") ("ca" "cb" "cc" "cd" "ce")) 
#(("41" "42" "43" "44" "45") ("da" "db" "dc" "dd" "de")) 
#(("51" "52" "53" "54" "55") ("ea" "eb" "ec" "ed" "ee"))

然后,我将进行另一个回调,为每个传入组从客户端列表中弹出项目,用于将组与客户端关联起来,并调用另一个函数:

(defun make-clients-callback (callback clients)
  (lambda (group)
    (funcall callback group (pop clients))))

让我们定义一个客户端示例列表:

> (defvar *clients* (loop for i from 1 to 6 collect (format nil "client ~r" i)))
*CLIENTS*

> *clients*
("client one" "client two" "client three" "client four" "client five" "client six")

此外,debug-client-cb用于测试:

> (defun debug-client-cb (group client)
    (print `(:client ,client :group ,group)))
DEBUG-CLIENT-CB

然后,下面的调用按2对行进行分组,并使用关联的客户机为每个组调用调试函数。

> (cl-csv:read-csv
    #P"/tmp/test.csv"
   :row-fn (make-group-reader 2 (make-clients-callback 'debug-client-cb 
                                                       *clients*)))
(:CLIENT "client one" :GROUP #(("01" "02" "03" "04" "05") ("za" "zb" "zc" "zd" "ze"))) 
(:CLIENT "client two" :GROUP #(("11" "12" "13" "14" "15") ("aa" "ab" "ac" "ad" "ae"))) 
(:CLIENT "client three" :GROUP #(("21" "22" "23" "24" "25") ("ba" "bb" "bc" "bd" "be"))) 
(:CLIENT "client four" :GROUP #(("31" "32" "33" "34" "35") ("ca" "cb" "cc" "cd" "ce"))) 
(:CLIENT "client five" :GROUP #(("41" "42" "43" "44" "45") ("da" "db" "dc" "dd" "de"))) 
(:CLIENT "client six" :GROUP #(("51" "52" "53" "54" "55") ("ea" "eb" "ec" "ed" "ee")))

您可以将事情简化一点,如下所示:

(defun make-my-csv-reader (&optional (clients *clients*))
  (make-group-reader 2 (make-clients-callback #'check-upload-send clients)))

并将(make-my-csv-reader)作为:row-fn传递。
我很好奇为什么会使用这种方法而不是subseq方法?主要原因是因为数据只读一次吗?
是的,我尽量避免在处理数据时加载整个文件,将数据视为值流并以增量方式构建结果比需要不确定内存量的步骤更健壮。
在某些情况下,这不是最合理的方法,因为一次读取整个文件并处理它更容易/更快。
还有其他原因吗?
我喜欢把复杂性分解成层次的方式:首先对项目进行分组,然后处理它们,等等,并且可以在流水线中添加任何中间功能以进行调试,等等。
另外,回调函数是闭包,可以用来控制处理结束的时间,我可以提前从闭包返回,而不是阅读每一行并处理它们:

> (block :find-row
    (cl-csv:read-csv #P"/tmp/test.csv" 
                     :row-fn (lambda (row) 
                               (when (find "aa" row :test #'string=)
                                 (return-from :find-row row)))))

("aa" "ab" "ac" "ad" "ae")

相关问题