我有一个按其键排序的地图,其中包含如下数据:
(def h {50 Text1
70 Text2
372 Text1
391 Text2
759 Text1
778 Text2
})
地图按键排序。键(数字)可以解释为在大块文本中找到相应值的位置。在上面的例子中,在文本的第50位找到“Text1”。
现在,我想查找在彼此k个位置内找到的所有文本。我定义了一个像这样的函数:
(defn nearest [m k]
(for [m1 (keys m) m2 (keys m)
:when (and (> m2 m1) (not= (m m1) (m m2)) (< (- m2 m1) k))]
[m1 (get m m1) m2 (get m m2)]))
(nearest h 50)
; [[50 "Text1" 70 "Text2"] [372 "Text1" 391 "Text2"] [759 "Text1" 778 "Text2"]]
这很有效,但是当地图m有数千个元素时,它太慢了。因为for循环实际上是查看地图中的所有元素对。由于对地图进行了排序,因此对于地图中的每个元素,一旦下一个元素已经超过k个字符,就不必检查更多元素。我能够使用循环和重复编写一个版本。但它有点难以理解。是否有一种更自然的方式来执行此操作?我假设(:while)应该做的伎俩,但无法找到方法。
(defn nearest-quick [m k]
(let [m1 (keys m) m2 (keys m)]
(loop [inp m res [] i (first m1) m1 (rest m1) j (first m2) m2 (rest m2)]
(cond
(nil? i) res
(nil? j)(recur inp res (first m1) (rest m1) j m2)
(= i j) (recur inp res i m1 (first m2) (rest m2))
(< j i) (recur inp res i m1 (first m2) (rest m2))
(= (inp i) (inp j)) (recur inp res i m1 (first m2) (rest m2))
(< (- j i) k) (recur inp (conj res [i (inp i) j (inp j)]) i m1 (first m2) (rest m2))
(>= (- j i) k) (recur inp res (first m1) (rest m1) (first (rest m1)) (rest (rest m1)))))))
注意:对于带有42K元素的地图,第一个版本需要90分钟,第二个版本需要3分钟。
答案 0 :(得分:6)
当地图是排序地图时,人们可能会利用subseq
。
(defn nearest
[m n]
(for [[k v] m
[nk nv] (subseq m < k < (+ k n))
:when (not= v nv)]
[k v nk nv]))
代码没有基准。
答案 1 :(得分:2)
Clojure的for
也有:while
修饰符,因此您可以使用条件停止迭代。
答案 2 :(得分:0)
从我对你理解的任何例子:
(def h (sorted-map 50 "Text1"
70 "Text2"
372 "Text1"
391 "Text2"
759 "Text1"
778 "Text2"))
(->> (map #(-> [%1 %2]) h (rest h))
(filter (fn [[[a b] [x y]]] (< (- x a) 50)))
(map flatten))