Emacs allinea le matrici


8

Dato che mi ritrovo a scrivere molte matrici e tabelle, sto cercando un modo per allineare i numeri in Emacs (simile al pacchetto align in vim). Ho scoperto che esiste align-regexp, ma non sono riuscito a farlo funzionare come volevo. C'è un modo per allineare i numeri ai loro decimali --- e se non ci sono decimali allinearli di fronte agli altri decimali. Sarebbe anche bello essere in grado di allinearsi a separatori 'migliaia' e allineare numeri complessi. Preferibilmente con due spazi bianchi tra i numeri per la leggibilità. Ecco un esempio:

Ingresso:

A = [-15 9 33.34;...
1.0 0.99 1+3i;...
13,000 2 11 ];

Uscita desiderata:

A = [   -15     9     33.34 ;...
          1.0  -0.99   1+3i ;...
     13,000     2     11    ];

In alternativa, per renderlo un po 'più semplice (senza separatore "migliaia" e numeri complessi):

Ingresso:

A = [-15 9 33.34;...
1.0 0.99 1;...
13000 2 11 ];

Uscita desiderata:

A = [  -15     9      33.34 ; ...
         1.0   0.99    1    ; ...
     13000     2      11    ];

Molte grazie.

Risposte:


5

Questo mi ha richiesto un po 'più di tempo di quanto inizialmente previsto, e il codice è un po' troppo lungo per pubblicarlo qui, quindi l'ho pubblicato su Patebin: http://pastebin.com/Cw82x11i

Non è del tutto completo e può usare un po 'più di lavoro, quindi se qualcuno avrà suggerimenti o contributi, potrei riorganizzarlo come repository Git da qualche parte / ripubblicarlo sul wiki di Emacs.

Pochi punti importanti:

  1. Non è stato fatto alcun tentativo di soddisfare matrici con delimitatori diversi dagli spazi.
  2. Non ho nemmeno provato ad analizzare numeri complessi.
  3. Il trattamento delle voci non numeriche è diverso da quello del tuo esempio (a dire il vero, non saprei davvero come analizzarlo esattamente come vuoi. La mia ipotesi è che il punto e virgola sia il delimitatore di riga Matlab / Octave , ma se provo a renderlo più generico, è davvero difficile avvolgerci la testa. Inoltre, la mia ipotesi è che l'ellissi sia il modo Matlab / Octave di dire all'interprete che l'affermazione continua nella riga successiva, ma, ancora una volta, provare a renderlo più generico sarebbe davvero difficile, invece, sto solo trattando qualsiasi valore non numerico che incontro come se fosse un numero intero.
  4. Alla fine, ho dovuto rinunciare al align-regexpperché era troppo complicato per cercare di allinearlo esattamente usando la regola che sembra avere in mente.

Ecco come sarebbe:

;; before
A = [-15 9 33.34;...
1.0 0.99 1;...
13000 2 11 ];

;; after
A = [  -15   9    33.34 ;... 
         1.0 0.99  1    ;... 
     13000   2    11         ];

PS. È possibile regolare lo spazio tra le colonne modificando il valore della spacervariabile.

OK, ho anche apportato un piccolo perfezionamento al codice in cui ora può richiedere la compilazione della stringa tra le colonne.

(defun my/string-to-number (line re)
  (let ((matched (string-match re line)))
    (if matched
        (list (match-string 0 line)
              (substring line (length (match-string 0 line))))
      (list nil line))))

(defun my/string-to-double (line)
  (my/string-to-number
   line
   "\\s-*[+-]?[0-9]+\\(?:\\.[0-9]+\\(?:[eE][+-]?[0-9]+\\)?\\)?"))

(defun my/string-to-int (line)
  (my/string-to-number line "\\s-*[+-]?[0-9]+"))

(defun my/vector-transpose (vec)
  (cl-coerce
   (cl-loop for i below (length (aref vec 0))
            collect (cl-coerce 
                     (cl-loop for j below (length vec)
                              collect (aref (aref vec j) i))
                     'vector))
   'vector))

(defun my/align-metric (col num-parser)
  (cl-loop with max-left = 0
           with max-right = 0
           with decimal = 0
           for cell across col
           for nump = (car (funcall num-parser cell))
           for has-decimals = (cl-position ?\. cell) do
           (if nump
               (if has-decimals
                   (progn
                     (setf decimal 1)
                     (when (> has-decimals max-left)
                       (setf max-left has-decimals))
                     (when (> (1- (- (length cell) has-decimals))
                              max-right)
                       (setf max-right (1- (- (length cell) has-decimals)))))
                 (when (> (length cell) max-left)
                   (setf max-left (length cell))))
             (when (> (length cell) max-left)
               (setf max-left (length cell))))
           finally (cl-return (list max-left decimal max-right))))

(defun my/print-matrix (rows metrics num-parser prefix spacer)
  (cl-loop with first-line = t
           for i upfrom 0
           for row across rows do
           (unless first-line (insert prefix))
           (setf first-line nil)
           (cl-loop with first-row = t
                    for cell across row
                    for metric in metrics
                    for has-decimals =
                    (and (cl-position ?\. cell)
                         (car (funcall num-parser cell)))
                    do
                    (unless first-row (insert spacer))
                    (setf first-row nil)
                    (cl-destructuring-bind (left decimal right) metric
                      (if has-decimals
                          (cl-destructuring-bind (whole fraction)
                              (split-string cell "\\.")
                            (insert (make-string (- left (length whole)) ?\ )
                                    whole
                                    "."
                                    fraction
                                    (make-string (- right (length fraction)) ?\ )))
                        (insert (make-string (- left (length cell)) ?\ )
                                cell
                                (make-string (1+ right) ?\ )))))
           (unless (= i (1- (length rows)))
             (insert "\n"))))

(defun my/read-rows (beg end)
  (cl-coerce
   (cl-loop for line in (split-string
                         (buffer-substring-no-properties beg end) "\n")
            collect
            (cl-coerce
             (nreverse
              (cl-loop with result = nil
                       with remaining = line do
                       (cl-destructuring-bind (num remainder)
                           (funcall num-parser remaining)
                         (if num
                             (progn
                               (push (org-trim num) result)
                               (setf remaining remainder))
                           (push (org-trim remaining) result)
                           (cl-return result)))))
             'vector))
   'vector))

(defvar my/parsers '((:double . my/string-to-double)
                     (:int . my/string-to-int)))

(defun my/align-matrix (parser &optional spacer)
  (interactive
   (let ((sym (intern
               (completing-read
                "Parse numbers using: "
                (mapcar 'car my/parsers)
                nil nil nil t ":double")))
         (spacer (if current-prefix-arg
                     (read-string "Interleave with: ")
                   " ")))
     (list sym spacer)))
  (unless spacer (setf spacer " "))
  (let ((num-parser
         (or (cdr (assoc parser my/parsers))
             (and (functionp parser) parser)
             'my/string-to-double))
        beg end)
    (if (region-active-p)
        (setf beg (region-beginning)
              end (region-end))
      (setf end (1- (search-forward-regexp "\\s)" nil t))
            beg (1+ (progn (backward-sexp) (point)))))
    (goto-char beg)
    (let* ((prefix (make-string (current-column) ?\ ))
           (rows (my/read-rows beg end))
           (cols (my/vector-transpose rows))
           (metrics
            (cl-loop for col across cols
                     collect (my/align-metric col num-parser))))
      (delete-region beg end)
      (my/print-matrix rows metrics num-parser prefix spacer))))

Lavoro fantastico. Penso che dovresti comunque condividere il codice qui. La tua risposta sarà inutile se il link pastebin dovesse estinguersi. Ho visto frammenti di codice molto più lunghi di 122 righe su SE :)
Kaushal Modi

Wow, grazie mille. Mi dispiace di averti causato così tanto lavoro, speravo che qualche regex o plug-in di fantasia potessero fare il lavoro. È esattamente quello che stavo cercando però. Tuttavia non riesco a farlo funzionare. Come lo uso (scusate se non ho molta esperienza in lisp)? Ho provato a contrassegnare la regione e chiamare my / align-matrix, ma mi dà il seguente errore: "Tentativo di impostare un simbolo costante: t"
DayAndNight

@DayAndNight è davvero strano. Non riesco a trovare un posto in cui questo errore può verificarsi. Ma se puoi darmi un esempio di dati, allora le mie possibilità saranno migliori. Potrebbe non essere necessario contrassegnare la regione prima di chiamare my/align-matrix. Se i numeri si trovano all'interno di qualcosa che Emacs considera come una specie di parentesi (in genere chiunque tra [], (), {}), il codice farà uno sforzo per trovare quella regione da sola.
wvxvw,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.