Emacs menyelaraskan matriks

8

Karena saya mendapati diri saya menulis banyak matriks dan tabel, saya mencari cara untuk menyelaraskan angka dengan baik di Emacs (mirip dengan paket penyelarasan dalam vim). Saya menemukan bahwa ada align-regexp, tetapi saya tidak bisa menjalankannya seperti yang saya inginkan. Apakah ada cara untuk menyelaraskan angka di desimal mereka --- dan jika tidak ada desimal sejajar di depan desimal lainnya. Akan lebih baik jika bisa menyelaraskan pada 'pemisah ribuan' dan meluruskan bilangan kompleks. Lebih disukai dengan dua spasi putih di antara angka untuk dibaca. Berikut ini sebuah contoh:

Memasukkan:

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

Output yang diinginkan:

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

Atau, untuk membuatnya sedikit lebih mudah (tanpa 'pemisah ribuan' dan angka kompleks):

Memasukkan:

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

Output yang diinginkan:

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

Terima kasih banyak.

Siang dan malam
sumber

Jawaban:

5

Ini memakan waktu lebih lama dari perkiraan saya, dan kodenya agak terlalu panjang untuk memposting semuanya di sini, jadi saya mempostingnya ke Patebin: http://pastebin.com/Cw82x11i

Meskipun tidak sepenuhnya lengkap dan dapat menggunakan beberapa pekerjaan lagi, jadi jika ada yang akan memiliki saran atau kontribusi, saya dapat mengatur ulang ini sebagai repositori Git di suatu tempat / memposting ulang ini ke wiki Emacs.

Beberapa poin penting:

  1. Tidak ada upaya yang dilakukan untuk memenuhi matriks dengan pembatas selain spasi.
  2. Saya juga tidak mencoba menguraikan bilangan kompleks.
  3. Perlakuan entri non-numerik berbeda dari yang ada di contoh Anda (sejujurnya, saya tidak akan benar-benar tahu bagaimana menguraikannya persis seperti yang Anda inginkan. Dugaan saya adalah bahwa titik koma adalah pembatas baris Matlab / Octave baris , tetapi jika saya mencoba membuatnya lebih umum, sangat sulit untuk membungkus kepala saya di sekitar itu. Juga, tebakan saya adalah bahwa elipsis adalah cara Matlab / Oktaf untuk memberi tahu penerjemah bahwa pernyataan tersebut berlanjut pada baris berikutnya, tetapi, lagi, mencoba membuat ini lebih umum akan sangat sulit, sebagai gantinya, saya hanya memperlakukan nilai non-numerik apa pun yang saya temui seolah-olah itu adalah angka bulat.
  4. Akhirnya, saya harus menyerah align-regexpkarena terlalu rumit untuk mencoba menyelaraskannya dengan tepat menggunakan aturan yang tampaknya ada dalam pikiran Anda.

Begini tampilannya:

;; 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. Anda dapat menyesuaikan ruang di antara kolom dengan mengubah nilai spacervariabel.

OK, saya juga membuat sedikit penyempurnaan ke kode di mana sekarang bisa meminta string untuk mengisi di antara kolom.

(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
sumber
Kerja luar biasa. Saya pikir Anda masih harus membagikan kode di sini. Jawaban Anda tidak akan berguna jika tautan pastebin mati. Saya telah melihat potongan kode yang jauh lebih lama dari 122 baris di SE :)
Kaushal Modi
Wooow, terima kasih banyak. Saya minta maaf membuat Anda banyak bekerja, saya berharap beberapa regex atau plugin mewah bisa melakukan pekerjaan. Itu persis apa yang saya cari. Namun saya tidak bisa membuatnya bekerja. Bagaimana cara menggunakannya (maaf saya tidak punya banyak pengalaman di lisp)? Saya mencoba untuk menandai wilayah dan memanggil / menyelaraskan-matriks saya, tetapi itu memberi saya kesalahan berikut: "Attemnt untuk menetapkan simbol konstan: t"
DayAndNight
@DayAndNight ini benar-benar aneh. Saya tidak dapat menemukan tempat di mana kesalahan ini dapat terjadi. Tetapi jika Anda bisa memberi saya contoh data, maka peluang saya akan lebih baik. Anda mungkin tidak perlu menandai wilayah sebelum menelepon my/align-matrix. Jika angka-angka itu ada di dalam sesuatu yang diperlakukan Emacs sebagai semacam tanda kurung (biasanya siapa pun dari [], (), {}), maka kode akan berupaya untuk menemukan wilayah itu sendiri.
wvxvw