Reputation: 1331
I would like to implement a function which is a "mix " of interleave
and interpose
called indent
.
Indeed, when you write
(interleave [1 2 3] ["_" "*"])
It returns
(1 "_" 2 "*")
But I would like to have
(1 "_" 2 "*" 3)
So I wrote a function which achieves this goal :
(defn indent
"Mix of clojure.core interleave and interpose functions.
Indents the second collection in the first one."
[coll1 coll2]
(let [n1 (count coll1)
n2 (count coll2)
vcoll1 (vec coll1)
vcoll2 (vec coll2)
stop-idx (min (- n1 2) (- n2 1))]
(-> (loop [init '()
i 0]
(cond (> i stop-idx)
init
:else
(recur (concat init [(vcoll1 i) (vcoll2 i)]) (inc i))))
(concat [(vcoll1 (inc stop-idx))]))))
The thing is the performance is terrible :
(time (dotimes [_ 10000000] (doall f [1 2 3] ["_" "*"])))
For f = interleave : 2 s For f = indent : 7 s
I tried to mimic interleave
impl but at the end I have the same costyly operations (count and vec).
The only idea I can see with fast computation is to write Java code...
Any idea ? Thanks !
EDIT : Faster java way
It's not a Clojure solution but it reduces computation time
package java_utils;
public class Clojure {
public static Object[] indent (Object[] coll1 , Object [] coll2) {
int len1 = coll1.length;
int len2 = coll2.length;
int stop_index = Math.min(len1 - 2, len2 - 1);
Object[] result = new Object[2*(stop_index+1) + 1];
for (int i = 0 ; i <= stop_index ; i++) {
result[2*i] = coll1[i];
result[2*i+1] = coll2[i];
}
result[2*stop_index+2] = coll1[stop_index+1];
return result;
}
}
(defn indent
[coll1 coll2]
(seq (Clojure/indent (to-array coll1) (to-array coll2))))
For 10M iterations, it computes fast at 1,7s.
Upvotes: 1
Views: 85
Reputation: 12883
These sorts of problems are usually best solved in Clojure by creating a seq. Typically seqs require less logic to implement, and they also fit in with the Clojure libraries well.
(defn interdent
([seq1 seq2]
(if (seq seq1)
(lazy-seq
(cons (first seq1) (interdent seq2 (rest seq1)))))))
Also, I'm not so sure about your time code. Not sure what f is in your example. Substituting the function in question for f produces an error. e.g.
user=> (time (dotimes [_ 10000000] (doall interpose [1 2 3] ["_" "*"])))
ArityException Wrong number of args (3) passed to: core/doall clojure.lang.AFn.throwArity (AFn.java:429)
I used the following snippet to time the code.
user=> (time (dotimes [_ 10000000] (doall (interpose [1 2 3] ["_" "*"]))))
e.g.
user=> (interdent [1 2 3] ["_" "*"])
(1 "_" 2 "*" 3)
user=> (interdent [1 2 3] ["_" "*" ":("])
(1 "_" 2 "*" 3 ":(")
user=> (interdent [1 2 3] ["_" "*" ":)" ":("])
(1 "_" 2 "*" 3 ":)")
Upvotes: 0
Reputation: 17859
why don't you just base it on the interleave
? like this:
(defn indent2 [coll1 coll2]
(when (seq coll1)
(cons (first coll1)
(interleave coll2 (rest coll1)))))
it's performance should be almost equal to interleave.
Upvotes: 1