I use sort-lines fairly frequently to keep my #includes
, uses
or variable names in order. Occasionally though, I need to sort something slightly more complicated and I end up writing a little perl script. I found myself thinking it must be equally easy in emacs.
For example, I say I have the following data which I want to sort by the second field which is in position 12 and 13.
Edward Wood 9 01/07/2005 Ed Smith 25 06/12/2004 James Brown 18 01/07/2005 Jon James 13 05/15/2007
Now, emacs does have a sort-columns
function which does all sorts of fancy things including checking if we are using Unix and shelling out to sort if possible, but it doesn’t seem to be able to do a numerical comparison. However, in sort.el
it does have a general purpose sorting function called sort-subr
.
(defun sort-subr (reverse nextrecfun endrecfun &optional startkeyfun endkeyfun predicate)
sort-lines
itself is a good start for my custom function as it has all the code for handling regions, reversal and skipping between records (lines) that we need.
(defun sort-lines (reverse beg end) (interactive "P\nr") (save-excursion (save-restriction (narrow-to-region beg end) (goto-char (point-min)) (let ((inhibit-field-text-motion t)) (sort-subr reverse 'forward-line 'end-of-line)))))
Getting the startkeyfun and endkeyfun correct was fairly straight forward but I wasn’t entirely sure what information was passed to the predicate.
(defun sort-on-field (reverse beg end) (interactive "P\nr") (save-excursion (save-restriction (narrow-to-region beg end) (goto-char (point-min)) (let ((inhibit-field-text-motion t)) (sort-subr reverse 'forward-line 'end-of-line (lambda () (forward-char 12)) (lambda () (forward-char 2)) (lambda (a b) (message (format "[%s] [%s]" a b)))))))) [(703 . 705)] [(676 . 678)] [(757 . 759)] [(730 . 732)] [(757 . 759)] [(703 . 705)] [(730 . 732)] [(703 . 705)]
The final working version:
(defun sort-on-field (reverse beg end) (interactive "P\nr") (save-excursion (save-restriction (narrow-to-region beg end) (goto-char (point-min)) (let ((inhibit-field-text-motion t)) (sort-subr reverse 'forward-line 'end-of-line (lambda () (forward-char 12)) (lambda () (forward-char 2)) (lambda (a b) (let ((s1 (buffer-substring (car a) (cdr a))) (s2 (buffer-substring (car b) (cdr b)))) (< (string-to-number s1) (string-to-number s2)))))))))
Providing a nice way of asking for the start and end location of the sorting field is left as an exercise for the reader.
Interesting. See also: sort-numeric-fields .
See also sort-regexp-fields for the ultimate in flexibility.
Why not use ‘sort’ via shell-command-on-region?
@Aaron – I think that sort-numeric-fields counts fields based on whitespace, therefore for fixed width fields with a variable number of words it won’t work (e.g. my name field will sometimes have two surnames). Good tip for other data though.
@Sue – sort-subr is even more flexible isn’t it? Pathological example: what if you wanted to sort on the deepest set of brackets?
XXX ((1)) (2)
@Eisen – portability and flexibility. sort doesn’t exist by default on Windows and can’t handle all my sorting needs in any case.
Hello my name is Pedro, i’am from Portugal. I’am in the firs year of university and i have to do a program in scheme call’d mastermind. do you have some ideas to the program? i need help! if you can’t it’s ok but i don’t have more options. i find this blog and i think maybe he can help but if you din’t it’s ok! my e-mail is there if you can help send me an e-mail…. Thanks by the way… nice post…
[…] Sorting Records With Emacs « A Curious Programmer コメント (RSS) | トラックバック […]