Skip to content

Instantly share code, notes, and snippets.

@purcell
Last active September 17, 2024 13:38
Show Gist options
  • Save purcell/34824f1b676e6188540cdf71c7cc9fc4 to your computer and use it in GitHub Desktop.
Save purcell/34824f1b676e6188540cdf71c7cc9fc4 to your computer and use it in GitHub Desktop.
Various methods of randomly shuffling a list or vector of elements in emacs lisp, with benchmarks
(defun key-quiz--shuffle-list (list)
"Shuffles LIST randomly, modying it in-place."
(dolist (i (reverse (number-sequence 1 (1- (length list)))))
(let ((j (random (1+ i)))
(tmp (elt list i)))
(setf (elt list i) (elt list j))
(setf (elt list j) tmp)))
list)
(defun key-quiz--shuffle-list-nreverse (list)
"Shuffles LIST randomly, modying it in-place."
(dolist (i (nreverse (number-sequence 1 (1- (length list)))))
(let ((j (random (1+ i)))
(tmp (elt list i)))
(setf (elt list i) (elt list j))
(setf (elt list j) tmp)))
list)
(defun elfeed--shuffle (seq)
"Destructively shuffle SEQ."
(let ((n (length seq)))
(prog1 seq ; don't use dotimes result (bug#16206)
(dotimes (i n)
(cl-rotatef (elt seq i) (elt seq (+ i (random (- n i)))))))))
(defun faster-seq-sort-by (function pred sequence)
"Sort SEQUENCE using PRED as a comparison function.
Elements of SEQUENCE are transformed by FUNCTION before being
sorted. FUNCTION must be a function of one argument."
;; This version is modified to avoid calling "random" twice every time the predicate is called.
(seq-map 'cdr
(sort (seq-map (lambda (x) (cons (funcall function x) x)) sequence)
(lambda (a b)
(funcall pred (car a) (car b))))))
(defun seq-sort-by--shuffle (seq)
(seq-sort-by (lambda (_) (random)) #'<= seq))
(defun faster-seq-sort-by--shuffle (seq)
(faster-seq-sort-by (lambda (_) (random)) #'<= seq))
(defun faster-seq-sort-by--shuffle (seq)
(faster-seq-sort-by (lambda (_) (random)) #'<= seq))
(defun my-seq-shuffle (sequence)
"Unrolled version of seq-sort-by"
(seq-map 'cdr
(sort (seq-map (lambda (x) (cons (random) x)) sequence)
(lambda (a b)
(<= (car a) (car b))))))
(defun hugot-shuffle (list)
"Method from hugot, tweaked to avoid converting vecs back into lists when not needed"
(let* ((vec (seq-into list 'vector))
(length (length vec))
(n 0))
(while (< n length)
(let ((i (+ n (random (- length n))))
(tmp (aref vec n)))
(setf (aref vec n) (aref vec i)
(aref vec i) tmp))
(cl-incf n))
(if (vectorp list)
vec
(seq-into vec 'list))))
(defun elfeed-on-vec-shuffle (list)
"Elfeed's method, but converting to vec"
(let* ((seq (seq-into list 'vector)))
(let ((n (length seq)))
(prog1 (if (vectorp list) seq (seq-into seq 'list))
(dotimes (i n)
(cl-rotatef (elt seq i) (elt seq (+ i (random (- n i))))))))))
(dolist (sorter '(key-quiz--shuffle-list
key-quiz--shuffle-list-nreverse
elfeed--shuffle
seq-sort-by--shuffle
faster-seq-sort-by--shuffle
my-seq-shuffle
hugot-shuffle
elfeed-on-vec-shuffle
))
(message "\n*** %s ***" sorter)
(dolist (list-type '(list vector))
(let ((big-list (seq-into (number-sequence 1 5000) list-type))
(reps 100)
;;(gc-cons-threshold (* 128 1024 1024))
)
(message "-- %s:" list-type)
(garbage-collect)
(benchmark reps `(funcall ',sorter big-list))
)))
*** key-quiz--shuffle-list ***
-- list:
Elapsed time: 6.650204s
-- vector:
Elapsed time: 0.240027s
*** key-quiz--shuffle-list-nreverse ***
-- list:
Elapsed time: 6.742569s
-- vector:
Elapsed time: 0.244173s
*** elfeed--shuffle ***
-- list:
Elapsed time: 11.157770s
-- vector:
Elapsed time: 0.236123s
*** seq-sort-by--shuffle ***
-- list:
Elapsed time: 0.644674s
-- vector:
Elapsed time: 0.685449s
*** faster-seq-sort-by--shuffle ***
-- list:
Elapsed time: 0.573194s
-- vector:
Elapsed time: 0.572976s
*** my-seq-shuffle ***
-- list:
Elapsed time: 0.470165s
-- vector:
Elapsed time: 0.465306s
*** hugot-shuffle ***
-- list:
Elapsed time: 0.209031s
-- vector:
Elapsed time: 0.197855s
*** elfeed-on-vec-shuffle ***
-- list:
Elapsed time: 0.252475s
-- vector:
Elapsed time: 0.239680s
@hugot
Copy link

hugot commented Sep 16, 2024

@purcell great, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment