Natural Sort Order for Emacs Lisp

Has anyone implemented natural-order sorting in Emacs Lisp? I know that it’s not difficult to write, but it’s easier to borrow someone else.

(Yes, I can't believe that I was just looking for the Emacs function and couldn't find it.)

+3
source share
1 answer

This code provides 'dictionary-lesspthat can be used in sorting algorithms. It seems like I'm working 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")))

Usage example:

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

gives

("1" "a" "b" "f" "f1can" "f2" "f19")
+4
source

Source: https://habr.com/ru/post/1726274/


All Articles