Emacs alinea matrices

8

Como me encuentro escribiendo muchas matrices y tablas, estoy buscando una forma de alinear bien los números en Emacs (similar al paquete de alineación en vim). Descubrí que hay align-regexp, pero no pude hacer que funcionara como quería. ¿Hay alguna manera de alinear los números en sus decimales? Y si no hay decimales, se alinean delante de los otros decimales También sería bueno poder alinearse en separadores 'miles' y alinear números complejos. Preferiblemente con dos espacios en blanco entre números para facilitar la lectura. Aquí hay un ejemplo:

Entrada:

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

Salida deseada:

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

Alternativamente, para hacerlo un poco más fácil (sin separador 'miles' y números complejos):

Entrada:

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

Salida deseada:

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

Muchas gracias.

Día y noche
fuente

Respuestas:

5

Esto me llevó bastante más tiempo del que originalmente estimé, y el código es demasiado largo para publicarlo aquí, así que lo publiqué en Patebin: http://pastebin.com/Cw82x11i

Sin embargo, no está completamente completo y puede usar algo más de trabajo, por lo que si alguien tiene sugerencias o contribuciones, podría reorganizar esto como un repositorio de Git en algún lugar / volver a publicar esto en la wiki de Emacs.

Pocos puntos importantes:

  1. No se ha intentado atender matrices con delimitadores que no sean espacios.
  2. Tampoco intenté analizar números complejos.
  3. El tratamiento de las entradas no numéricas es diferente al de su ejemplo (para ser sincero, realmente no sabría cómo analizarlo exactamente de la manera que desea. Mi conjetura es que el punto y coma es el delimitador de fila Matlab / Octave , pero si trato de hacerlo más genérico, es realmente difícil entenderlo. Además, supongo que la elipsis es la forma de Matlab / Octave de decirle al intérprete que la declaración continúa en la siguiente línea, pero, nuevamente, tratar de hacer esto más genérico sería realmente difícil. En cambio, solo estoy tratando cualquier valor no numérico que encuentro como si fuera un número entero.
  4. Finalmente, tuve que renunciar align-regexpporque era demasiado complicado intentar alinearlo exactamente usando la regla que parece tener en mente.

Así es como se vería:

;; 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         ];

PD. Puede ajustar el espacio entre las columnas cambiando el valor de la spacervariable.

OK, también he hecho un pequeño refinamiento al código donde ahora puede pedir que la cadena se complete entre las columnas.

(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))))
wvxvw
fuente
Impresionante trabajo. Creo que aún debes compartir el código aquí. Su respuesta será inútil si el enlace pastebin se cortara. He visto fragmentos de código mucho más largos que 122 líneas en SE :)
Kaushal Modi
Wooow, muchas gracias. Lamento haberte causado tanto trabajo, esperaba que algunas expresiones regulares o complementos pudieran hacer el trabajo. Sin embargo, es exactamente lo que estaba buscando. Sin embargo, no puedo hacer que funcione. ¿Cómo lo uso (lo siento, no tengo mucha experiencia en lisp)? Traté de marcar la región y llamar a mi / align-matrix, pero me da el siguiente error: "Elemento para establecer un símbolo constante: t"
DayAndNight
@DayAndNight esto es realmente extraño. No puedo encontrar un lugar donde pueda ocurrir este error. Pero si me puede dar un ejemplo de datos, entonces mis posibilidades serán mejores. Es posible que no necesite marcar la región antes de llamar my/align-matrix. Si los números están dentro de algo que Emacs trata como una especie de paréntesis (generalmente cualquiera de [], (), {}), entonces el código hará un esfuerzo por encontrar esa región por sí mismo.
wvxvw