views:

171

answers:

1

Has anyone implemented a natural order sort in Emacs Lisp? I know it's not hard to write, but it's easier to borrow someone else's work.

(Yeah, I can't believe I just searched for an Emacs function and couldn't find it.)

+2  A: 

This code provides a 'dictionary-lessp which can be used in sorting algorithms. Seems to work in my testing so far:

(defun dictionary-lessp (str1 str2)
  "return t if STR1 is < STR2 when doing a dictionary compare
(splitting the string at numbers and doing numeric compare with them)"
  (let ((str1-components (dict-split str1))
        (str2-components (dict-split str2)))
    (dict-lessp str1-components str2-components)))

(defun dict-lessp (slist1 slist2)
  "compare the two lists of strings & numbers"
  (cond ((null slist1)
         (not (null slist2)))
        ((null slist2)
         nil)
        ((and (numberp (car slist1))
              (stringp (car slist2)))
         t)
        ((and (numberp (car slist2))
              (stringp (car slist1)))
         nil)
        ((and (numberp (car slist1))
              (numberp (car slist2)))
         (or (< (car slist1) (car slist2))
             (and (= (car slist1) (car slist2))
                  (dict-lessp (cdr slist1) (cdr slist2)))))
        (t
         (or (string-lessp (car slist1) (car slist2))
             (and (string-equal (car slist1) (car slist2))
                  (dict-lessp (cdr slist1) (cdr slist2)))))))

(defun dict-split (str)
  "split a string into a list of number and non-number components"
  (save-match-data 
    (let ((res nil))
      (while (and str (not (string-equal "" str)))
        (let ((p (string-match "[0-9]*\\.?[0-9]+" str)))
          (cond ((null p)
                 (setq res (cons str res))
                 (setq str nil))
                ((= p 0)
                 (setq res (cons (string-to-number (match-string 0 str)) res))
                 (setq str (substring str (match-end 0))))
                (t
                 (setq res (cons (substring str 0 (match-beginning 0)) res))
                 (setq str (substring str (match-beginning 0)))))))
      (reverse res))))

This is my testing:

(and (dictionary-lessp "a" "b")
     (null (dictionary-lessp "b" "a"))
     (null (dictionary-lessp "a" "a"))
     (dictionary-lessp "1" "2")
     (null (dictionary-lessp "2" "1"))
     (null (dictionary-lessp "1" "1"))
     (dictionary-lessp "1" "a")
     (null (dictionary-lessp "a" "1"))
     (dictionary-lessp "" "a")
     (null (dictionary-lessp "a" ""))

     (dictionary-lessp "ab12" "ab34")
     (dictionary-lessp "ab12" "ab123")
     (dictionary-lessp "ab12" "ab12d")
     (dictionary-lessp "ab132" "ab132z")


     (dictionary-lessp "132zzzzz" "ab132z")
     (null (dictionary-lessp "1.32" "1ab")))

Example usage is:

(sort '("b" "a" "1" "f19" "f" "f2" "f1can") 'dictionary-lessp)

yields

("1" "a" "b" "f" "f1can" "f2" "f19")
Trey Jackson
This is awesome, thanks! P.S., I think you forgot a ;; before "p != 0".
Ken
I don't understand the comment, which check are you talking about?
Trey Jackson
The text "p != 0" looks like a comment (and makes no sense in a COND there), but there's no comment marker before it.
Ken
Ahhh, blind me. Thanks
Trey Jackson