                   matching-listings))))))
 
 
-
-
-(defn test-random [products listings]
-  (let [listing (rand-nth listings)]
-    (or (match-title products listing)
-        [nil listing])))
-
-
-
 (defn -main
   [& args]
   ; We need to read all products into memory at once, but we

  [path]
  (with-open [rdr (io/reader path)]
    (->> (line-seq rdr)
         ; We need to return a vector (mapv), because otherwise with-open will
         ; close the stream before the lazy sequence is realized.
         (mapv parse-json))))

(defn product-regex
  "Generates a regex pattern that represents the product."
  [product]
  (let [optional-spaces (fn [s]
                          (-> (or s "")
                              (str/replace #"[\s_-]+" "[\\\\s_-]*")
                              (str/replace #"(?i)(DSC|DSLR|DMC|PEN|SLT|IS)" "(?:$1)?")))]
    (re-pattern
      (str #"(?i)^(?:[\w()]*\s*){0,3}"
           (if-let [fam (:family product)]
             (str "(?:"
                  (:family product)
                  ")?"
                  #"\s*\w*\s*"))
           (optional-spaces (:model product))
           #"[^\d]+"))))

(defn prepare-product [product]
  (-> product
      (#(assoc % :regex (product-regex %)))))

(defn common-prefix-length
  "Returns the length of the longest common prefix between sequences a and b."
  [a b]
  (->> (map = a b)
       (take-while true?)
       count))

(defn starts-with? [^String s candidate]
  (.startsWith s (or candidate "")))

(defn first-word [s]
  (-> (or s "")
      (str/split #"\s+" 2)  ; Split at most once
      first))


(def manufacturer-matchers
  [(fn first-word? [manufacturers candidate]
     (p/p :firstword
          (some #{(first-word candidate)} manufacturers)))
   
   (fn levenshtein? [manufacturers candidate]
     ; This is mainly for things like "OPYMPUS" -> "Olympus"
     (p/p :levenshtein
          (let [min-length 4
                max-lev-dist 2]
            (and ; Very short strings are not likely to be similar
                 (>= (count candidate) min-length)

                 ; Levenshtein is expensive, so require the first letter to match
                 ; before trying to compute it.
                 ((set (map first manufacturers)) (first candidate))

                 (let [[guess lev-dist]
                       (p/p :lev-lev
                            (->> manufacturers
                                 (map (juxt identity (partial levenshtein candidate)))
                                 (apply min-key second)))]
                   (when (<= lev-dist max-lev-dist)
                     guess))))))])

(def match-manufacturer
  (memoize
