Matrizes de alinhamento do Emacs

8

Como eu me pego escrevendo muitas matrizes e tabelas, estou procurando uma maneira de alinhar números de maneira agradável no Emacs (semelhante ao pacote de alinhamento no vim). Descobri que há align-regexp, mas não consegui fazê-lo funcionar da maneira que queria. Existe uma maneira de alinhar números com suas casas decimais --- e se não houver casas decimais alinhadas à frente das outras casas decimais. Também seria bom poder alinhar em separadores de 'milhares' e alinhar números complexos. De preferência com dois espaços em branco entre os números para facilitar a leitura. Aqui está um exemplo:

Entrada:

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

Saída desejada:

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

Como alternativa, para facilitar um pouco (sem separador de milhares e números complexos):

Entrada:

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

Saída desejada:

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

Muito obrigado.

Dia e noite
fonte

Respostas:

5

Isso me levou um pouco mais de tempo do que o estimado originalmente, e o código é um pouco longo para postar tudo aqui, então eu o publiquei em Patebin: http://pastebin.com/Cw82x11i

No entanto, ele não está totalmente completo e pode usar mais trabalho; portanto, se alguém tiver sugestões ou contribuições, eu posso reorganizá-lo como um repositório Git em algum lugar / repassá-lo para o wiki do Emacs.

Alguns pontos importantes:

  1. Nenhuma tentativa foi feita para atender matrizes com delimitadores que não sejam espaços.
  2. Também não tentei analisar números complexos.
  3. O tratamento de entradas não numéricas é diferente do seu exemplo (para ser honesto, eu realmente não saberia como analisá-lo exatamente da maneira que você deseja. Meu palpite é que o ponto-e-vírgula é o delimitador de linhas Matlab / Octave , mas se eu tentar torná-lo mais genérico, é realmente difícil entender o assunto.Também acho que as reticências são a maneira do Matlab / Octave de dizer ao intérprete que a declaração continua na próxima linha, mas, mais uma vez, tentar tornar isso mais genérico seria muito difícil.Em vez disso, estou apenas tratando qualquer valor não numérico que encontro como se fosse um número inteiro.
  4. Finalmente, tive que desistir align-regexpporque era muito complicado tentar alinhar exatamente usando a regra que você parece ter em mente.

Aqui está o que seria:

;; 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. Você pode ajustar o espaço entre as colunas alterando o valor da spacervariável.

OK, também fiz um pequeno refinamento no código, onde ele agora pode solicitar que a string seja preenchida entre as colunas.

(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
fonte
Otimo trabalho. Eu acho que você ainda deve compartilhar o código aqui. Sua resposta será inútil se o link pastebin ficar inoperante. Eu vi trechos de código muito, muito mais do que 122 linhas em SE :)
Kaushal Modi
Uau, muito obrigado. Sinto muito por lhe causar tanto trabalho, eu esperava que algum regex ou plugin sofisticado pudesse fazer o trabalho. É exatamente o que eu estava procurando. No entanto, não consigo fazê-lo funcionar. Como eu o uso (desculpe, não tenho muita experiência no lisp)? Tentei marcar a região e chamar minha / align-matrix, mas ele me deu o seguinte erro: "Tentativa de definir um símbolo constante: t"
DayAndNight
@DayAndNight isso é realmente estranho. Não consigo encontrar um lugar onde esse erro possa ocorrer. Mas se você puder me dar um exemplo de dados, minhas chances serão melhores. Pode não ser necessário marcar a região antes de ligar my/align-matrix. Se os números estiverem dentro de algo que o Emacs trata como uma espécie de parênteses (geralmente qualquer um de [], (), {})), o código fará um esforço para encontrar essa região por conta própria.
Wdxvw