(defun merge-tables-by-first-column (&rest tables) "Merge any number of tables by treating their first column as a key; sort the result" (interactive) (let (is-all-numbers less-than-function equal-function conversion-function format-specifier rests-of-tables rest-of-rests-of-tables rest-of-table widths-of-tables current-key result-table result-line i) ;; Find out, if all keys in all tables are numbers or if there are strings among them (setq is-all-numbers (catch 'not-a-number (dolist (table tables) (dolist (line table) (unless (numberp (car line)) (throw 'not-a-number 'nil)))) 't)) ;; prepare functions to treat table contents in a unified way (setq format-specifier (if is-all-numbers "%g" "%s")) (setq conversion-function (if is-all-numbers (lambda (x) x) (lambda (x) (if (numberp x) (number-to-string x) x)) )) (setq less-than-function (lambda (x y) (if is-all-numbers (< x y) (string< (funcall conversion-function x) (funcall conversion-function y))))) (setq equal-function (lambda (x y) (if is-all-numbers (= x y) (string= (funcall conversion-function x) (funcall conversion-function y))))) ;; sort tables (setq tables (mapcar (lambda (table) (sort table (lambda (x y) (funcall less-than-function (car x) (car y))))) tables)) ;; compute and remember table widths (setq widths-of-tables (mapcar (lambda (x) (length (car x))) tables)) (setq rests-of-tables (copy-list tables)) ;; loop as long as the rest of table still contains lines (while (progn ;; find lowest key among all tables, which is the key for the next line of the result (setq current-key nil) (dolist (rest-of-table rests-of-tables) (when (and rest-of-table (or (null current-key) (funcall less-than-function (caar rest-of-table) current-key))) (setq current-key (caar rest-of-table)))) current-key) (progn (setq result-line (list current-key)) ;; go through all tables and collect one line for the result table ... (setq i 0) ; table-count ;; cannot use dolist like above, because we need to modify the cons-cells (setq rest-of-rests-of-tables rests-of-tables) (while (progn (setq rest-of-table (car rest-of-rests-of-tables)) (setq i (1+ i)) ;; if table contains current key (if (and rest-of-table (funcall equal-function current-key (caar rest-of-table))) ;; then copy rest of line (progn (nconc result-line (cdar rest-of-table)) ;; and shorten rest (setcar rest-of-rests-of-tables (cdar rest-of-rests-of-tables)) ;; and check, if current-key appears again (when (and (caadr rest-of-table) (funcall equal-function current-key (caadr rest-of-table)) ) (error (concat "Key '" format-specifier "'appears twice within input table %i") (funcall conversion-function current-key) i) ) ) ;; otherwise fill with nil and do not shorten rest of table (progn (nconc result-line (make-list (1- (elt widths-of-tables (1- i))) "")) ) ) (setq rest-of-rests-of-tables (cdr rest-of-rests-of-tables)) ;; condition for while-loop rest-of-rests-of-tables ) ) (setq result-table (cons result-line result-table)) ; store away line ) ) (nreverse result-table) ) )