2010-01-03 12 views
6

Desde que me convertí a la Iglesia de Emacs, he estado tratando de hacer todo desde adentro, y me preguntaba cómo hacer algo de procesamiento de texto de manera rápida y eficiente con él.Procesando texto con elisp

Como ejemplo, tomemos esta lista que estaba editando hace unos minutos en org-mode.

 
** Diego: b QI 
** bruno-gil: b QI 
** Koma: jo 
** um: rsrs pr0n 
** FelipeAugusto: esp 
** GustavoPupo: pinto tr etc 
** GP: lit gtk 
** Alan: jo mil pc 
** Jost: b hq jo 1997 
** Herbert: b rsrs pr0n 
** Andre: maia mil pseudo 
** Rodrigo: c 
** caue: b rsrs 7arte pseudo 
** kenny: cri gif 
** daniel: gtk mu pr0n rsrs b 
** tony: an 1997 esp 
** Vitor: b jo mimimi 
** raphael: b rpg 7arte 
** Luca: b lit gnu pc prog mmu 7arte 1997 
** LZZ: an qt 
** William: b an jo pc 1997 
** Epic: gtk 
** Aldo: b pseudo pol mil fur 
** GustavoKyon: an gtk 
** CarlosIsaksen : an hq jo 7arte gtk 1997 
** Peter: pseudo pol mil est 1997 gtk lit lang 
** leandro: b jo cb 
** frederico: 7arte lit gtk 
** rol: b an pseudo mimimi 7arte 
** mathias: jo lit 
** henrique: 1997 h gtk qt 
** eumané: an qt 
** walrus: cri de 
** FilipePinheiro: lit pseudo 
** Igor: pseudo b 
** Erick: b jo rpg q 1997 gtk 
** Gabriel: pr0n rsrs qt 
** george: clo mimimi 
** anão: hq jo 1997 rsrs clô b 
** jeff: 7arte gtk 
** davidatenas: an 7arte 1997 esp qt 
** HHahaah: b 
** Eduardo: b 

Es una lista de nombres asociados con etiquetas, y quiero obtener una lista de etiquetas asociadas con nombres.

En bash, primero echo con comillas simples todo pegado y luego lo pipeteo a awk, haciendo un bucle sobre cada línea y agregando cada una de sus partes a la variable temporal correcta y luego metiéndola hasta que sea como yo quiero .

 
echo '** Diego: b QI 
** bruno-gil: b QI 
** Koma: jo 
** um: rsrs pr0n 
** FelipeAugusto: esp 
** GustavoPupo: pinto, tr etc 
** GP: lit gtk 
** Alan: jo mil pc 
** Jost: b hq jo 1997 
** Herbert: b rsrs pr0n 
** Andre: maia mil pseudo 
** Rodrigo: c 
** caue: b rsrs 7arte pseudo 
** kenny: cri gif 
** daniel: gtk mu pr0n rsrs b 
** tony: an 1997 esp 
** Vitor: b jo mimimi 
** raphael: b rpg 7arte 
** Luca: b lit gnu pc prog mmu 7arte 1997 
** LZZ: an qt 
** William: b an jo pc 1997 
** Epic: gtk 
** Aldo: b pseudo pol mil fur 
** GustavoKyon: an gtk 
** CarlosIsaksen : an hq jo 7arte gtk 1997 
** Peter: pseudo pol mil est 1997 gtk lit lang 
** leandro: b jo cb 
** frederico: 7arte lit gtk 
** rol: b an pseudo mimimi 7arte 
** mathias: jo lit 
** henrique: 1997 h gtk qt 
** eumané: an qt 
** walrus: cri de 
** FilipePinheiro: lit pseudo 
** Igor: pseudo b 
** Erick: b jo rpg q 1997 gtk 
** Gabriel: pr0n rsrs qt 
** george: clo mimimi 
** anão: hq jo 1997 rsrs clô b 
** jeff: 7arte gtk 
** davidatenas: an 7arte 1997 esp qt 
** HHahaah: b 
** Eduardo: b 
' | awk '{sub(":","");for (i=3;i<=NF;i++) members[$i] = members[$i] " " $2}; END{for (j in members) print j ": " members[j]}' | sort 

... y TA-DA! El resultado esperado en menos de 2 minutos, hecho de una manera intuitiva e incremental. ¿Puedes mostrarme cómo hacer algo como esto en elisp, preferiblemente en un buffer de emacs, con elegancia y simplicidad?

Gracias!

Respuesta

0

Este es mi segundo intento. Escribí un pequeño macro y algunas funciones para tratar con esos datos.

 
(defun better-numberp (s) 
    (string-match "^ *[0-9.,]* *$" s)) 

(defmacro awk-like (&rest args) 
    (let ((arg (car (last args))) 
     (calls (mapcar #'(lambda (l) 
          (cond 
          ((numberp (first l)) (cons `(lambda (f) (equal %r ,(first l))) (rest l))) 
          ((stringp (first l)) (cons `(lambda (f) (string-match ,(first l) %)) (rest l))) 
          (t l))) 
         (butlast args)))) 
    `(mapcar #'(lambda (%%) 
       (let ((%r 0)) 
        (mapcar 
        #'(lambda (l) 
         (setq %r (1+ %r)) 
         (let ((% l)) 
          (dolist (tipo ',calls) 
          (progn 
           (setq % (cond 
             ((funcall (first tipo) %) (eval (cadr tipo))) (t %))) 
           (set (intern (format "%%%d" %r)) %))) %)) %%))) 
      (mapcar #'(lambda (y) (split-string y " " t)) 
        (split-string ,arg "\n" t))))) 

(defun hash-to-list (hashtable) 
    "Return a list that represent the hashtable." 
    (let (mylist) 
    (maphash (lambda (kk vv) (setq mylist (cons (list kk vv) mylist))) hashtable) 
    mylist 
    ) 
) 

(defun append-hash (key value hashtable) 
    (let ((current (gethash key hashtable))) 
    (puthash key 
      (cond 
       ((null current) (list value)) 
       ((listp current) (cons value current)) 
       (t current)) 
      hashtable))) 

 
(let ((foohash (make-hash-table :test 'equal))) 
    (awk-like 
    (2 (replace-regexp-in-string ":" "" %)) 
    ((lambda (f) (> %r 2)) (append-hash % %2 foohash)) 
    "** Diego: b QI 
** bruno-gil: b QI 
** Koma: jo 
** um: rsrs pr0n 
** FelipeAugusto: esp 
** GustavoPupo: pinto tr etc 
** GP: lit gtk 
** Alan: jo mil pc 
** Jost: b hq jo 1997 
** Herbert: b rsrs pr0n 
** Andre: maia mil pseudo 
** Rodrigo: c 
** caue: b rsrs 7arte pseudo 
** kenny: cri gif 
** daniel: gtk mu pr0n rsrs b 
** tony: an 1997 esp 
** Vitor: b jo mimimi 
** raphael: b rpg 7arte 
** Luca: b lit gnu pc prog mmu 7arte 1997 
** LZZ: an qt 
** William: b an jo pc 1997 
** Epic: gtk 
** Aldo: b pseudo pol mil fur 
** GustavoKyon: an gtk 
** CarlosIsaksen: an hq jo 7arte gtk 1997 
** Peter: pseudo pol mil est 1997 gtk lit lang 
** leandro: b jo cb 
** frederico: 7arte lit gtk 
** rol: b an pseudo mimimi 7arte 
** mathias: jo lit 
** henrique: 1997 h gtk qt 
** eumané: an qt 
** walrus: cri de 
** FilipePinheiro: lit pseudo 
** Igor: pseudo b 
** Erick: b jo rpg q 1997 gtk 
** Gabriel: pr0n rsrs qt 
** george: clo mimimi 
** anão: hq jo 1997 rsrs clô b 
** jeff: 7arte gtk 
** davidatenas: an 7arte 1997 esp qt 
** HHahaah: b 
** Eduardo: b 
") 
    (hash-to-list foohash)) 
+0

Esto puede ser solo un detalle, pero me parece que ayuda al aprender un nuevo idioma aprender el tipo de sangría de código que es idiomática para ese idioma - el tipo de sangría y paréntesis esperado con el lenguaje me ayuda a mantenerme en el flujo de ese lenguaje Y también, como alguien que usa lisp bastante, verlo parantizado de esa manera es discordante, como ver código en mayúsculas en un idioma que no sea BASIC o FORTRAN. –

+0

¿Te refieres a la macro o la función hash-to-list? Si es el macro, ¿podría mostrarme cómo sangrarlo correctamente? La función simplemente fue copiada de la página de Xah Lee – konr

5

Hay una función shell-command-on-region que hace más de lo que dice. Puede resaltar una región, hacer M- |, escribir el nombre de un comando de shell y los datos se canalizan a ese comando. Dale un argumento y la región se reemplaza con el resultado del comando.

Para un ejemplo trivial, resalte una región, escriba 'C-u 0 M- | wc '(control-u, cero, meta-pipe y luego' wc ') y la región se reemplazará por el número de caracteres, palabras y líneas de esa región.

Otra cosa que puede hacer es descubrir cómo manipular una línea, convertirla en una macro y luego ejecutar la macro repetidamente. Por ejemplo, 'C-x (C-s foo C-g barra C-x)' buscará la palabra "foo", luego tecleará la palabra "bar", cambiándola a "foobar". A continuación, puede hacer 'C-u C-x e' una vez que ejecutará continuamente la macro hasta que no encuentre más apariciones de "foo".

+1

Además, Emacsen moderno tiene enlaces convenientes para macros de teclado. está obligado a 'start-macro-of-insert-counter', está vinculado a 'kmacro-end-or-call-macro '- esto ahorra tipeo. Ignorando la funcionalidad del contador (como siempre, "C-h k RET" para la documentación completa), esto le permite presionar " C-s foo C-bar ..." - el primer finaliza la definición de macro, el segundo lo ejecuta. – ariels

3

autorización, aquí es mi primer intento en elisp:

  1. empiezo un tampón con modos elisp y paredit en, comillas dobles abiertas y pegar el texto
  2. ato a un símbolo utilizando let
 
(let ((foobar "** Diego: b QI 
** bruno-gil: b QI 
** Koma: jo 
** um: rsrs pr0n 
** FelipeAugusto: esp 
** GustavoPupo: pinto, tr etc 
** GP: lit gtk 
** Alan: jo mil pc 
** Jost: b hq jo 1997 
** Herbert: b rsrs pr0n 
** Andre: maia mil pseudo 
** Rodrigo: c 
** caue: b rsrs 7arte pseudo 
** kenny: cri gif 
** daniel: gtk mu pr0n rsrs b 
** tony: an 1997 esp 
** Vitor: b jo mimimi 
** raphael: b rpg 7arte 
** Luca: b lit gnu pc prog mmu 7arte 1997 
** LZZ: an qt 
** William: b an jo pc 1997 
** Epic: gtk 
** Aldo: b pseudo pol mil fur 
** GustavoKyon: an gtk 
** CarlosIsaksen : an hq jo 7arte gtk 1997 
** Peter: pseudo pol mil est 1997 gtk lit lang 
** leandro: b jo cb 
** frederico: 7arte lit gtk 
** rol: b an pseudo mimimi 7arte 
** mathias: jo lit 
** henrique: 1997 h gtk qt 
** eumané: an qt 
** walrus: cri de 
** FilipePinheiro: lit pseudo 
** Igor: pseudo b 
** Erick: b jo rpg q 1997 gtk 
** Gabriel: pr0n rsrs qt 
** george: clo mimimi 
** anão: hq jo 1997 rsrs clô b 
** jeff: 7arte gtk 
** davidatenas: an 7arte 1997 esp qt 
** HHahaah: b 
** Eduardo: b 
")) 
    foobar) 

Ahora cambio foobar a algo elegante.

  1. Primero se quita los símbolos con una expresión regular y dividir el texto en cadenas utilizando (split-string)
  2. Entonces hago un mapcar para convertir cada línea en una lista de palabras
 
(mapcar #'(lambda (y) (split-string y " " t)) (split-string (replace-regexp-in-string "[:\*]" "" foobar) "\n" t)) 
  1. luego de crear un mapa hash y enlazarlo a temphash ((temphash (make-hash-table :test 'equal)))
  2. y entonces bucle en las listas anidadas para agregar los elementos a la tabla hash.Creo que no tengo que hacer la programación no funcional con mapcar, pero nadie está mirando;)
 
(mapcar #'(lambda (l) 
       (mapcar #'(lambda (m) (puthash m (format "%s %s" (car l) (let ((tempel (gethash m temphash))) 
                  (if tempel tempel ""))) temphash)) (rest l))) 
      (mapcar #'(lambda (y) (split-string y " " t)) (split-string (replace-regexp-in-string "[:\*]" "" foobar) "\n" t))) 
  1. Por último, extraer los elementos de la tabla de hash en otro conjunto de listas anidadas con una práctica función robado a la página web de Xah Lee,
  2. Y finalmente bastante imprimirlo en otro tampón con Mx pp-eval-last-sexp

es un poco alucinante, especialmente el doble carro, pero funciona. Aquí está el "código" completo:

 
;; Stolen from Xah Lee's page 


(defun hash-to-list (hashtable) 
    "Return a list that represent the hashtable." 
    (let (mylist) 
    (maphash (lambda (kk vv) (setq mylist (cons (list kk vv) mylist))) hashtable) 
    mylist 
) 
) 

;; Code 

(let ((foobar "** Diego: b QI 
** bruno-gil: b QI 
** Koma: jo 
** um: rsrs pr0n 
** FelipeAugusto: esp 
** GustavoPupo: pinto, tr etc 
** GP: lit gtk 
** Alan: jo mil pc 
** Jost: b hq jo 1997 
** Herbert: b rsrs pr0n 
** Andre: maia mil pseudo 
** Rodrigo: c 
** caue: b rsrs 7arte pseudo 
** kenny: cri gif 
** daniel: gtk mu pr0n rsrs b 
** tony: an 1997 esp 
** Vitor: b jo mimimi 
** raphael: b rpg 7arte 
** Luca: b lit gnu pc prog mmu 7arte 1997 
** LZZ: an qt 
** William: b an jo pc 1997 
** Epic: gtk 
** Aldo: b pseudo pol mil fur 
** GustavoKyon: an gtk 
** CarlosIsaksen : an hq jo 7arte gtk 1997 
** Peter: pseudo pol mil est 1997 gtk lit lang 
** leandro: b jo cb 
** frederico: 7arte lit gtk 
** rol: b an pseudo mimimi 7arte 
** mathias: jo lit 
** henrique: 1997 h gtk qt 
** eumané: an qt 
** walrus: cri de 
** FilipePinheiro: lit pseudo 
** Igor: pseudo b 
** Erick: b jo rpg q 1997 gtk 
** Gabriel: pr0n rsrs qt 
** george: clo mimimi 
** anão: hq jo 1997 rsrs clô b 
** jeff: 7arte gtk 
** davidatenas: an 7arte 1997 esp qt 
** HHahaah: b 
** Eduardo: b 
") 
     (temphash (make-hash-table :test 'equal))) 
    (mapcar #'(lambda (l) 
       (mapcar #'(lambda (m) (puthash m (format "%s %s" (car l) (let ((tempel (gethash m temphash))) 
                  (if tempel tempel ""))) temphash)) (rest l))) 
      (mapcar #'(lambda (y) (split-string y " " t)) (split-string (replace-regexp-in-string "[:\*]" "" foobar) "\n" t))) 
    (hash-to-list temphash)) 

Y aquí está la salida:

 
(("clô" "anão ") 
("clo" "george ") 
("q" "Erick ") 
("de" "walrus ") 
("h" "henrique ") 
("cb" "leandro ") 
("lang" "Peter ") 
("est" "Peter ") 
("fur" "Aldo ") 
("pol" "Peter Aldo ") 
("qt" "davidatenas Gabriel eumané henrique LZZ ") 
("mmu" "Luca ") 
("prog" "Luca ") 
("gnu" "Luca ") 
("rpg" "Erick raphael ") 
("mimimi" "george rol Vitor ") 
("an" "davidatenas eumané rol CarlosIsaksen GustavoKyon William LZZ tony ") 
("mu" "daniel ") 
("gif" "kenny ") 
("cri" "walrus kenny ") 
("7arte" "davidatenas jeff rol frederico CarlosIsaksen Luca raphael caue ") 
("c" "Rodrigo ") 
("pseudo" "Igor FilipePinheiro rol Peter Aldo caue Andre ") 
("maia" "Andre ") 
("1997" "davidatenas anão Erick henrique Peter CarlosIsaksen William Luca tony Jost ") 
("hq" "anão CarlosIsaksen Jost ") 
("pc" "William Luca Alan ") 
("mil" "Peter Aldo Andre Alan ") 
("gtk" "jeff Erick henrique frederico Peter CarlosIsaksen GustavoKyon Epic daniel GP ") 
("lit" "FilipePinheiro mathias frederico Peter Luca GP ") 
("etc" "GustavoPupo ") 
("tr" "GustavoPupo ") 
("pinto," "GustavoPupo ") 
("esp" "davidatenas tony FelipeAugusto ") 
("pr0n" "Gabriel daniel Herbert um ") 
("rsrs" "anão Gabriel daniel caue Herbert um ") 
("jo" "anão Erick mathias leandro CarlosIsaksen William Vitor Jost Alan Koma ") 
("QI" "bruno-gil Diego ") 
("b" "Eduardo HHahaah anão Erick Igor rol leandro Aldo William Luca raphael Vitor daniel caue Herbert Jost bruno-gil Diego ")) 
7

La primera cosa que me gustaría hacer es aprovechar el soporte de etiquetas org-mode 's. En lugar de

** Diego: b QI 

Usted tendría

** Diego       :b:QI: 

Qué org-mode reconoce como etiquetas "b" y "QI".

para transformar su formato actual al formato estándar org-mode, puede utilizar lo siguiente (suponiendo que el buffer con su fuente se llama "asdf")

(with-current-buffer "asdf" 
    (beginning-of-buffer) 
    (replace-string " " ":") 
    (beginning-of-buffer) 
    (replace-string "**:" "** ") 
    (beginning-of-buffer) 
    (replace-string "::" " :") 
    (beginning-of-buffer) 
    (replace-string "\n" ":\n") 
    (org-set-tags-command t t)) 

No es bonito o eficiente, pero se pone el trabajo hecho

Después de eso, se puede utilizar el siguiente para producir un tampón que tiene el formato que quería de la secuencia de comandos de la shell:

(let ((results (get-buffer-create "results")) 
     tags) 
    (with-current-buffer "asdf" 
    (beginning-of-buffer) 
    (while (org-on-heading-p) 
     (mapc '(lambda (item) (when item (add-to-list 'tags item))) (org-get-local-tags)) 
     (outline-next-visible-heading 1))) 
    (setq tags (sort tags 'string<)) 
    (with-current-buffer results 
    (erase-buffer) 
    (mapc '(lambda (item) 
      (insert (format "%s: %s\n" 
          item 
          (with-current-buffer "asdf" 
           (org-map-entries '(substring-no-properties (org-get-heading t)) item))))) 
      tags) 
    (beginning-of-buffer) 
    (replace-regexp "[()]" ""))) 

Esto pone los resultados en un buffer llamado "resultados", creándolo si ya no existe . Básicamente, está recopilando todas las etiquetas en el búfer "asdf", ordenándolas, luego recorriendo cada etiqueta y buscando cada título con que se etiqueta en "asdf" y se inserta en "resultados".

Con un poco de limpieza, esto podría convertirse en una función; básicamente solo reemplazando "asdf" y "resultados" con argumentos. Si necesita eso, puedo hacer eso.

1

Las alternativas anteriores son interesantes, pero no creo capturar el aspecto de "cómo voy a hacer esto en Emacs como una conversión reciente" de la pregunta. Sospecho que alguien que está aprendiendo Emacs con la vista puesta en uso de Emacs Lisp a hacer todo el trabajo podría comenzar con algo como:

(defun create-tags-to-name (buffer-name) 
    "Create a buffer filled with lines containg `** TAG: 
LIST-OF-NAMES' by transposing lines in the region matching the 
format `** NAME: LIST-OF-TAGS' where the list items are white 
space separated." 
    (interactive) 
    (let ((buf (get-buffer-create buffer-name)) 
    (tag-to-name-list (list)) 
    name tags element) 
    ;; Clear the destination buffer 
    (with-current-buffer buf 
     (erase-buffer)) 
    ;; Build the list of tag to name associations. 
    (while (re-search-forward "^** \\([-a-zA-Z0-9 ]+\\):\\(.+\\)$" (point-max) t) 
     (setq name (buffer-substring (match-beginning 1) (match-end 1)) 
     tags (split-string (buffer-substring (match-beginning 2) (match-end 2)))) 
     ;; For each tag add the name to the tag's name list 
     (while tags 
    (let ((tag (car tags))) 
     (setq element (assoc tag tag-to-name-list) 
     tags (cdr tags)) 
     (if element 
      (setcdr element (append (list name) (cdr element))) 
     (setq tag-to-name-list (append (list (cons tag (list name))) tag-to-name-list)))))) 
    ;; Dump the associations to the target buffer 
    (with-current-buffer buf 
     (while tag-to-name-list 
    (setq element (car tag-to-name-list) 
      tag-to-name-list (cdr tag-to-name-list)) 
    (insert (concat "** " (car element) ":")) 
    (let ((tag-list (cdr element))) 
     (while tag-list 
     (insert " " (car tag-list)) 
     (setq tag-list (cdr tag-list)))) 
    (insert "\n"))))) 
2

Si conoces *nix pipes, que está familiarizado con functional programming, porque los programas de trata de programación funcionales como la transformación sucesiva de datos mediante la aplicación de funciones. ¿Recuerda la composición de la función de matemáticas escolares? Básicamente, g ∘ f significa que se aplica antes f y luego aplicar inmediatamente g: (g ∘ f) (x) = g (f (x)). Un programa funcional es una composición de función gigante. Y un pipe is just a function composition, solo con una dirección opuesta: (g ∘ f) (x) en matemáticas es lo mismo que x | f | g en línea de comandos.

Hay una biblioteca de terceros dash.el que proporciona multitud de funciones para transformaciones de lista y árbol y también funciones y macros que facilitan el enfoque funcional. Uno de ellos es una rosca macro ->>, que imita la línea de comandos de tuberías:

(->> '(1 2 3) (-map '1+) (-reduce '+)) ; returns 9 
;; equivalent to (-reduce '+ (-map '1+ '(1 2 3))) 

Así que si queremos manipular los datos de texto por las operaciones de aplicación de serie, nuestra función puede tener este aspecto:

(defun key-value-swap (s) 
    (->> s 
     nil ; Split into lines 
     nil ; Remove stars from each line 
     nil ; Split each line 
     nil ; Add 1st element as a value to each element starting from 
      ; 2nd as keys 
     nil ; Return a hash-table 
     )) 

El función que hace exactamente lo que desea que el siguiente aspecto:

(defun key-value-swap (s) 
    (let ((h (make-hash-table :test 'equal))) 
    (->> s 
     s-lines ; split into lines 
     (--map (s-split "\\(\\s-\\|:\\)" ; split each line 
         (s-chop-prefix "** " it) ; throw away stars 
         t)) 
     (--map (-each (cdr it) ; for every field in the line, except 1st 
        (lambda (k) ; append 1st line to value under key 
        (puthash k (cons (car it) (gethash k h)) h))))) 
    h)) ; return hash-table 

(puthash k (cons (car it) (gethash k h)) h) parece críptica, pero esto simplemente significa que en cada ke y en hash-table hay una lista a la que anexa cada vez que encuentra un nuevo valor. Entonces, si bajo b hay (Diego) y encontramos que bruno-gil también debe estar por debajo de b, el valor por debajo de b se convierte en (bruno-gil Diego).