Emacs aligne les matrices

8

Comme je me retrouve à écrire beaucoup de matrices et de tableaux, je cherche un moyen d'aligner bien les nombres dans Emacs (similaire au paquet align dans vim). J'ai trouvé qu'il y avait align-regexp, mais je ne pouvais pas le faire fonctionner comme je le voulais. Existe-t-il un moyen d'aligner les nombres à leurs décimales --- et s'il n'y a pas de décimales, alignez-les devant les autres décimales. Il serait également intéressant de pouvoir aligner à des milliers de séparateurs et aligner des nombres complexes. De préférence avec deux espaces blancs entre les numéros pour plus de lisibilité. Voici un exemple:

Contribution:

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

Sortie désirée:

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

Alternativement, pour le rendre un peu plus facile (sans séparateur de «milliers» et nombres complexes):

Contribution:

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

Sortie désirée:

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

Merci beaucoup.

Jour et nuit
la source

Réponses:

5

Cela m'a pris un peu plus de temps que je ne l'avais initialement estimé, et le code est un peu trop long pour tout publier ici, alors je l'ai posté sur Patebin: http://pastebin.com/Cw82x11i

Cependant, il n'est pas entièrement complet et il peut nécessiter plus de travail, donc si quelqu'un a des suggestions ou des contributions, je pourrais réorganiser cela en tant que référentiel Git quelque part / republier cela sur le wiki Emacs.

Quelques points importants:

  1. Aucune tentative n'a été faite pour répondre aux matrices avec des délimiteurs autres que des espaces.
  2. Je n'ai pas non plus essayé d'analyser des nombres complexes.
  3. Le traitement des entrées non numériques est différent de celui de votre exemple (pour être honnête, je ne saurais pas vraiment comment l'analyser exactement comme vous le souhaitez. Je suppose que le point-virgule est le délimiteur de ligne Matlab / Octave , mais si j'essaye de le rendre plus générique, il est vraiment difficile de faire le tour de ma tête. De plus, je suppose que les points de suspension sont la manière Matlab / Octave de dire à l'interprète que la déclaration continue sur la ligne suivante, mais, encore une fois, essayer de rendre cela plus générique serait vraiment difficile. Au lieu de cela, je traite simplement la valeur non numérique que je rencontre comme s'il s'agissait d'un nombre entier.
  4. Enfin, j'ai dû abandonner le align-regexpcar il était trop compliqué d'essayer de l'aligner exactement en utilisant la règle que vous semblez avoir en tête.

Voici à quoi cela ressemblerait:

;; 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. Vous pouvez ajuster l'espace entre les colonnes en modifiant la valeur de la spacervariable.

OK, j'ai également apporté un petit raffinement au code où il peut maintenant demander que la chaîne soit remplie entre les colonnes.

(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
la source
Super travail. Je pense que vous devriez toujours partager le code ici. Votre réponse sera inutile si le lien pastebin venait à disparaître. J'ai vu des extraits de code beaucoup plus longs que 122 lignes sur SE :)
Kaushal Modi
Wooow, merci beaucoup. Je suis désolé de vous causer autant de travail, j'espérais que certains regex ou plugins fantaisistes pourraient faire le travail. C'est exactement ce que je cherchais. Cependant, je ne peux pas le faire fonctionner. Comment puis-je l'utiliser (désolé, je n'ai pas beaucoup d'expérience dans le lisp)? J'ai essayé de marquer la région et d'appeler ma / align-matrix, mais cela me donne l'erreur suivante: "Attemnt pour définir un symbole constant: t"
DayAndNight
@DayAndNight c'est vraiment étrange. Je ne trouve pas d'endroit où cette erreur peut se produire. Mais si vous pouvez me donner un exemple de données, mes chances seront meilleures. Vous n'aurez peut-être pas besoin de marquer la région avant d'appeler my/align-matrix. Si les nombres se trouvent dans quelque chose qu'Emacs traite comme une sorte de parenthèse (généralement n'importe qui de [], (), {}), alors le code fera un effort pour trouver cette région par lui-même.
wvxvw