[注意:标题和文本经过大量编辑,以便更清楚地表明我不是特别在字符串之后,而是在一般序列之后,并且对它进行延迟处理]
以字符序列/字符串为例,假设我想转换字符串
“\ t a \ r s \ td \ t \ r \ n f \ r \ n”
到
“a s d f”
更一般地说,我想将序列中的所有连续空格(或任何其他任意项目集)转换为单个项目,并且懒得。
我已经提出了以下 partition-by / mapcat combo ,但想知道是否有更简单或更好的方法(可读性,性能,任何东西)来完成同样的事情。
(defn is-wsp?
[c]
(if (#{\space \tab \newline \return} c) true))
(defn collapse-wsp
[coll]
(mapcat
(fn [[first-elem :as s]]
(if (is-wsp? first-elem) [\space] s))
(partition-by is-wsp? coll)))
行动中:
=> (apply str (collapse-wsp "\t a\r s\td \t \r \n f \r\n"))
" a s d f "
更新: 我使用了字符串/字符序列/ wsp作为示例,但我真正想要的是任何类型的序列上的泛型函数,它会折叠任意数量的连续项,这些项是预定义项集的一部分,由一些预定义项。我特别想知道是否有更好的partition-by / mapcat替代方案,如果可以针对'string'特殊情况进行优化,那就更好了。
更新2 :
这是一个完全懒惰的版本 - 上面的那个并不完全是懒惰的,我担心,除了它正在做多余的is-wsp?检查。我概括了参数名称等,所以它看起来不像是一个你可以通过String.whatever()调用轻松替换的东西 - 它是关于任意序列的。
(defn lazy-collapse
([coll is-collapsable-item? collapsed-item-representation] (lazy-collapse coll is-collapsable-item? collapsed-item-representation false))
([coll is-collapsable-item? collapsed-item-representation in-collapsable-segment?]
(let [step (fn [coll in-collapsable-segment?]
(when-let [item (first coll)]
(if (is-collapsable-item? item)
(if in-collapsable-segment?
(recur (rest coll) true)
(cons collapsed-item-representation (lazy-collapse (rest coll) is-collapsable-item? collapsed-item-representation true)))
(cons item (lazy-collapse (rest coll) is-collapsable-item? collapsed-item-representation false)))))]
(lazy-seq (step coll in-collapsable-segment?)))))
这很快,完全懒惰,但我希望能够更简洁地表达,因为我自己很懒。
到目前为止懒惰合作者的基准: 通过查看代码可以很容易地判断代码是否可读,但为了了解它们在性能方面的比较,以下是我的基准测试。我首先检查函数是否完成它应该做的事情,然后我吐出了多长时间
测试1到3是为了至少测量一下懒惰。 我运行了几次测试,并且执行时间没有显着变化。
user=> (map
(fn [collapse]
(println (class collapse) (str "|" (apply str (collapse test-str is-wsp? \space)) "|"))
(time (dotimes [_ 1000000] (collapse test-str is-wsp? \space)))
(time (dotimes [_ 1000000] (first (collapse test-str is-wsp? \space))))
(time (dotimes [_ 1000000] (second (collapse test-str is-wsp? \space))))
(time (dotimes [_ 1000000] (last (collapse test-str is-wsp? \space)))))
[collapse-overthink collapse-smith collapse-normand lazy-collapse])
user$collapse_overthink | a s d f |
"Elapsed time: 153.490591 msecs"
"Elapsed time: 3064.721629 msecs"
"Elapsed time: 4337.932487 msecs"
"Elapsed time: 24797.222682 msecs"
user$collapse_smith | a s d f |
"Elapsed time: 141.474904 msecs"
"Elapsed time: 812.998848 msecs"
"Elapsed time: 2112.331739 msecs"
"Elapsed time: 10750.224816 msecs"
user$collapse_normand | a s d f |
"Elapsed time: 314.978309 msecs"
"Elapsed time: 1423.779761 msecs"
"Elapsed time: 1669.660257 msecs"
"Elapsed time: 8074.759077 msecs"
user$lazy_collapse | a s d f |
"Elapsed time: 169.906088 msecs"
"Elapsed time: 638.030401 msecs"
"Elapsed time: 1195.445016 msecs"
"Elapsed time: 6050.945856 msecs"
到目前为止的底线:最好的代码是最慢的,最丑的代码是最快的。我很确定它不一定是这样......
答案 0 :(得分:4)
到目前为止,这是我最快的解决方案:(基本上与M Smith相同,但没有解构)
(defn collapse [xs pred rep]
(when-let [x (first xs)]
(lazy-seq
(if (pred x)
(cons rep (collapse (drop-while pred (rest xs)) pred rep))
(cons x (collapse (rest xs) pred rep))))))
这是一个更漂亮的解决方案,但速度要快3倍(!)(实际上与SuperHorst的初始版本相同......)
(defn collapse [col pred rep]
(let [f (fn [[x & more :as xs]] (if (pred x) [rep] xs))]
(mapcat f (partition-by #(if (pred %) true) col))))
迷你基准(full code)输出:
$ clj collapse.clj
SuperHorst: "Elapsed time: 58535.737037 msecs"
Overthink: "Elapsed time: 70154.744605 msecs"
M Smith: "Elapsed time: 89484.984606 msecs"
Eric Normand: "Elapsed time: 83121.309838 msecs"
示例:
(def test-str "\t a\r s\td \t \r \n f \r\n")
(def is-ws? #{\space \tab \newline \return})
user=> (apply str (collapse test-str is-ws? \space))
" a s d f "
用于不同类型的seq:
user=> (collapse (range 1 110) #(= 2 (count (str %))) \X)
(1 2 3 4 5 6 7 8 9 \X 100 101 102 103 104 105 106 107 108 109)
这完全是懒惰的:
user=> (type (collapse test-str is-ws? \space))
clojure.lang.LazySeq
user=> (type (collapse (range 1 110) #(= 2 (count (str %))) \X))
clojure.lang.LazySeq
旧越野车版本:
(defn collapse-bug [col pred rep]
(let [f (fn [x] (if (pred x) rep x))]
(map (comp f first) (partition-by f col))))
该错误是它吃掉了与pred
不匹配的连续项目。
答案 1 :(得分:2)
为什么不简单地使用Java的String
函数replaceAll
?
user=> (.replaceAll "\t a\r s\td \t \r \n f \r\n" "\\s+" " ")
" a s d f "
我认为,它已针对此类操作进行了大量优化......
更新:澄清后更新的答案版本:
(defn is-blank? [^Character c] (not (nil? (#{\space \tab \newline \return} c))))
(defn collapse [coll fun replacement]
(first (reduce (fn [[res replaced?] el]
(if (fun el)
(if replaced?
[res replaced?]
[(conj res replacement) true])
[(conj res el) false]))
[[] false] coll)))
(def aa-str "\t a\r s\td \t \r \n f \r\n")
user> (apply str (collapse aa-str is-blank? \space))
" a s d f "
它也适用于其他seqs:
user> (collapse [1 1 2 3 1 1 1 4 5 6 1 1] #(= % 1) 9)
[9 2 3 9 4 5 6 9]
另一项性能优化可能是使用瞬态而不是标准序列......
答案 2 :(得分:2)
这是一个完全懒惰的实现,有点清洁:
<!-- language: lang-clojure -->
(defn collapse [ss pred replacement]
(lazy-seq
(if-let [s (seq ss)]
(let [[f & rr] s]
(if (pred f)
(cons replacement (collapse (drop-while pred rr) pred replacement))
(cons f (collapse rr pred replacement)))))))
答案 3 :(得分:1)
一种完全懒惰的解决方案,以递归方式编写:
(defn collapse [l p v]
(cond
(nil? (seq l))
nil
(p (first l))
(lazy-seq (cons v (collapse (drop-while p l) p v)))
:otherwise
(lazy-seq (cons (first l) (collapse (rest l) p v)))))
l
是列表,p
是谓词,v
是替换与谓词匹配的子序列的值。
如果你以牺牲可读性为代价追求纯粹的速度,你可以这样做:
(defn collapse-normand2
([l p v]
(lazy-seq (collapse-normand2 (seq l) p v nil)))
([l p v _]
(when l
(lazy-seq
(let [f (first l)
r (next l)]
(if (p f)
(cons v (collapse-normand2 r p v nil nil))
(cons f (collapse-normand2 r p v nil)))))))
([l p v _ _]
(when l
(lazy-seq
(let [f (first l)
r (next l)]
(if (p f)
(collapse-normand2 r p v nil nil)
(collapse-normand2 r p v nil)))))))
可能有一种方法可以让它更具可读性。它在所有4项测试中表现都很好。
答案 4 :(得分:0)
clojure中的字符串是Java字符串,因此您无法懒惰地创建字符串(意味着,您需要使用所有输入来构建字符串)。
你可以懒惰地创建一个字符序列:
USER> (remove #(Character/isWhitespace %) "\t a\r s\td \t \r \n f \r\n")
(\a \s \d \f)
你可以在这里使用字符串或seq作为输入,因为删除了最后一个参数的seq。