A fast MAPCONCAT implementation in Common Lisp
Here’s an implementation of Emacs Lisp’s MAPCONCAT function for Common Lisp.
(defun mapconcat (fun list sep)
(when list
(let ((~sep (with-output-to-string (*standard-output*)
(map nil (lambda (ch) (princ (if (char= #~ ch) "~~" ch))) sep))))
(format nil (format nil "~~A~~{~A~~A~~}" ~sep)
(funcall fun (first list))
(mapcar fun (rest list))))))
timed :
* (time (mapconcat 'identity *mylist* "-"))
Evaluation took:
2.805 seconds of real time
2.746358 seconds of total run time (2.736834 user, 0.009524 system)
[ Run times consist of 0.004 seconds GC time, and 2.743 seconds
non-GC time. ]
97.90% CPU
6,324,642,149 processor cycles
17,734,520 bytes consed
"0-1-2-3-4-5-6-7-8-9-10- ... "
And here’s an optimized version.
(setf *mylist*
(let ((l (list 0)))
(dotimes (i 10000 i) (nconc l (list (write-to-string i))))
(cdr l)))
(defun mapconcat(func lst sep)
(let ((vs (make-array 0
:element-type 'character
:fill-pointer 0
:adjustable t)))
(dotimes (i (length lst) i)
(let ((str (funcall func (nth i lst))))
(dotimes (j (length str) j)
(vector-push-extend (char str j) vs))
(dotimes (k (length sep) k)
(vector-push-extend (char sep k) vs))))
vs))
timed :
* (time (mapconcat 'identity *mylist* "-")) Evaluation took: 0.133 seconds of real time 0.098758 seconds of total run time (0.098390 user, 0.000368 system) 74.44% CPU 299,046,898 processor cycles 517,800 bytes consed "0-1-2-3-4-5-6-7-8-9-10- ... "
As someone pointed out the following would be much faster using FORMAT’s powerful directives and turning off *pretty-print* :
(defun mapconcat (function list elem)
(let (*print-pretty*)
(format nil (format nil "~~{~~a~~^~a~~}" elem)
(mapcar function list))))
timed :
* (time (mapconcat 'identity *mylist* "-")) Evaluation took: 0.006 seconds of real time 0.005033 seconds of total run time (0.005001 user, 0.000032 system) 83.33% CPU 11,430,579 processor cycles 539,200 bytes consed "0-1-2-3-4-5-6-7-8-9-10- ... "
However, the FORMAT version does not demonstrate a fast CL implementation.
To get the low-level implementation to match the performance of the FORMAT implementation we simply make a few tweaks.
(We replace DOTIMES/NTH for the outer loop with MAPCAR as NTH is slow).
(defun mapconcat(func lst sep)
(declare (type (cons (simple-array character (*))) lst))
(declare (type (simple-array character (*)) sep))
(let ((vs (make-array 0
:element-type 'character
:fill-pointer 0
:adjustable t))
(lsep (length sep)))
(mapcar #'(lambda (str)
(let ((nstr (funcall func str)))
(declare (type (simple-array character (*)) nstr))
(dotimes (j (length nstr) j)
(declare (type fixnum j))
(vector-push-extend (char nstr j) vs))
(dotimes (k lsep k)
(declare (type fixnum k))
(vector-push-extend (char sep k) vs))))
lst)
vs))
timed :
* (time (mapconcat 'identity *mylist* "-")) Evaluation took: 0.006 seconds of real time 0.005435 seconds of total run time (0.005261 user, 0.000174 system) 83.33% CPU 11,845,515 processor cycles 605,792 bytes consed "0-1-2-3-4-5-6-7-8-9-10- ... "
(all versions timed on SBCL 1.0.29, OS X 10.5.7, 2.26 GHz core 2 duo macbook)