Clojure CSV 匹配文件

Clojure CSV Matching Files

我有 2 个 csv 文件,文件 F1 中有大约 22K 条记录,文件 F2 中有 50K 条记录,都包含公司名称和地址信息。我需要对姓名、地址和 phone 进行模糊匹配。 F1 中的每条记录都需要与 F2 中的每条记录进行模糊匹配。我制作了第三个文件 R3,它是一个 csv,包含模糊匹配规则,从 F1 的哪一列到 F2 的哪一列,具有模糊容差级别。我正在尝试用 for 循环来做到这一点 -

(for [j f1-rows
      h f2-rows
      r r3-rows
      :while (match-row j h r)]
  (merge j h))

(defn match-row [j h rules]
  (every?
   identity
   (map (fn [rule]
          (<= (fuzzy/jaccard
           ((keyword (first rule)) j)
           ((keyword (second rule)) h))
          ((nth rule 2))))
    rules)))

f1-rows 和 f2-rows 是地图的集合。规则是包含来自 f1、f2 和容差级别的列名称的序列集合。代码是 运行 并且按预期运行。但我的问题是,执行大约需要 2 个小时。我阅读了转换器如何通过消除中间块来帮助提高性能,但我无法想象我将如何在我的案例中应用它。关于如何制作这个 better/faster 有什么想法吗?

22k x 50k 超过 10 亿种组合。乘以 3 个模糊规则,您将进行 30 亿次计算。其中大部分都被浪费了。

加快速度的唯一方法是对所有组合进行一些预排序或其他预修剪。例如,只有邮政编码相同时才进行模糊计算。如果您浪费时间尝试匹配来自 N.Y 的人。和佛罗里达州,您将放弃 99% 或更多的工作

:while 对比 :when

您在这种情况下对 :while 的使用似乎与您陈述的问题不符。当 match-row 为真时,您的 for 表达式将继续运行,并在第一个错误结果处完全停止。 :when 将遍历所有组合,并仅包括 match-row 在生成的惰性序列中为真的组合。差异解释here.

例如:

(for [i (range 10)
      j (range 10)
      :while (= i j)]
  [i j]) ;=> ([0 0])

(for [i (range 10)
      j (range 10)
      :when (= i j)]
  [i j]) ;=> ([0 0] [1 1] [2 2] [3 3] [4 4] [5 5] [6 6] [7 7] [8 8] [9 9])

您的代码将 运行 保持 2 小时,这真的很奇怪,因为这意味着在这两个小时内,对 (match-row j h r) 的每次调用都返回 true,只有最后一个返回 false。我会再次检查结果,看看它是否真的有意义。

需要多长时间?

让我们先做一些纸巾背面的数学运算。如果您想将 22k 条记录中的每条记录与 55k 条记录中的每条记录进行比较,您将进行 22k * 55k 比较,这是没有办法的。

22k * 55k = 1,210,000,000

这是一个很大的数字!

比较的费用是多少?

在维基百科上浏览半分钟后,jaccard 是关于集合的。 以下将对成本进行粗略估计,尽管它可能非常低端。

(time (clojure.set/difference (set "foo") (set "bar")))

这在我的计算机上大约需要十分之一毫秒。

(/ (* 22e3 55e3) ;; Number of comparisons.
   10 ; milliseconds
   1000 ;seconds
   60 ;minutes
   60) ;hours
;=> 33.611111111111114

那是 33 个半小时。这是对个人成本的低端估计,还没有计算你想要比较姓名、地址和 phone 的事实(?)。因此,如果每次比较都在第一行失败,则需要 33 小时,如果它们都到达最后一行,则需要 99 小时。

在进行任何微优化之前,您需要研究算法,找到一些不需要进行超过十亿次比较的聪明方法。如果您需要这方面的帮助,您至少需要提供一些示例数据。

吹毛求疵

match-row 中 anon fn 的缩进令人困惑。我会使用一个自动缩进的编辑器,并坚持 99% 的时间,因为 lisp 程序员通过缩进读取嵌套,比如 python。 editors/autoindenters之间略有不同,但都符合嵌套。

(defn match-row [j h rules]
  (every?
   identity
   (map (fn [rule]
          (<= (fuzzy/jaccard
               ((keyword (first rule)) j)
               ((keyword (second rule)) h))
              ((nth rule 2))))
        rules)))

此外,match-row 需要在使用前定义(它可能在您的实际代码中,在编译时看到)。