Mostrando entradas con la etiqueta contar. Mostrar todas las entradas
Mostrando entradas con la etiqueta contar. Mostrar todas las entradas

martes, 3 de noviembre de 2015

Solución quick-and-dirty para contar funciones

Pregunta 1: ¿Cuántas funciones escribiste para tu último proyecto en PHP? No cuentes las de librerías de terceros

grep -r "function " --include="*.php"  .|grep -v "./libs" |wc -l
1291




Pregunta 2: ¿Puedes sacar un listado de ellas, ordenado por los nombres de los ficheros en los que se encuentran?

grep -r "function " --include="*.php"  .|grep -v "./libs" |sort >funciones.txt





 Las dos líneas de código se explican casi por sí solas, en todo caso puede requerir una brevísima consulta a la ayuda de grep (grep --help), así que no me molestaré en explicar lo que ya está escrito allí. Una vez entendidas las opciones, se entiende muy fácilmente.


VAMOS A PONERLE PEGAS

1. En la primera solución se realizan dos llamadas al comando grep, cuando se podría hacer en una. Además, el primer grep obtiene líneas que luego desechará el segundo grep ==> Ineficiente.

2. Además de lo que se pretende, también muestra aquellas líneas que tengan la palabra 'function' seguida de un espacio, aunque estas estén entre comentarios ==> Aunque en mi caso es poco probable que se dé esa situación, sin embargo es algo potencialmente inexacto (si hay algún comentario en inglés conteniendo esa palabra)

Resumiendo: Ineficiente, potencialmente inexacto...

Y seguramente tiene muchas más pegas.



Y SIN EMBARGO... TE QUIERO

Pero tiene una ventaja brutal: es definitivamente rápido de hacer. Muy rápido. Y rápido, también, de ejecutar (en nuestro caso, dado el lote de ficheros con los que tengo que trabajar). Así que, ¿por qué hacerlo más complicado cuando una solución quick-and-dirty puede valer?

Y es que, a veces, lo perfecto es enemigo de lo bueno. O como dijo R. Gabriel, lo peor es lo mejor. Pero de esto ya hablaremos en otra ocasión.

jueves, 29 de enero de 2015

Contar frecuencia de palabras en un texto (versión 2) sin distinguir mayúsculas de minúsculas

Como complemento a la publicación de hace unos días, en la que contaba cómo contar cuántas veces aparece cada palabra en un fichero de texto, aquí podemos ver otra versión en la que se utilizan las "clases" (categorías de caracteres) definidas en el comando "tr", cuyos nombres son bastante intuitivos.  

Fig. 1. A ver quién tiene redaños a contar las palabras en este texto,
un evangelio alemán del s. XV

No es una mejora espectacular respecto a la opción de especificar los caracteres por rangos o individualmente (aquí sí es significativa la mejora), pero al menos así uno está seguro de que no se deja fuera un signo de puntuación si establece la categoría :punct:, por poner un ejemplo.

El código:

cat palabras.txt | tr [:punct:][:blank:][:digit:] "\n" | tr [:upper:] [:lower:] | sort | uniq -c | sort -nr > resul.txt

Una mejora añadida a esta versión es que no distingue entre mayúsculas y minúsculas, lo que he conseguido con la segunda invocación al comando "tr" que, como se puede ver, transforma todas las mayúsculas ([:upper:]) en minúsculas ([:lower:]).

Las categorías admisibles en el comando "tr" son (al menos en la versión de tr que tengo en mi máquina Windows actual):

 Fig. 2. Clases (categorías) de caracteres en el comando tr


Aquí está el enlace a la versión anterior, donde está explicado un poco más detallado el código
http://cosicasdeinformatica.blogspot.com.es/2015/01/contar-frecuencia-de-palabras-en-un.html

lunes, 5 de enero de 2015

Contar frecuencia de palabras en un texto con los filtros (comandos de consola) de Linux


Esta es una de esas necesidades recurrentes que aparecen cada poco tiempo. Y al final, siempre termino buscándolo en Internet, así que me he decidido a ponerlo aquí simplemente para tenerlo a mano.


El caso es que tengo un documento del que me gustaría sacar la estadística de palabras más utilizadas. O ya puestos, no limitarme a las más utilizadas, sino contar el número de veces que aparece cada palabra, para todas ellas.

Hay muchas soluciones para esto, podéis, por ejemplo, mirar aquí, en StackOverflow [1] varias formas de hacerlo.

La que a mí me ha parecido más sencilla, dentro de lo que yo necesitaba, y con alguna adaptación por mi parte (la opción -r en el sort) es esta:


Figura 1. Las 20 palabras más utilizadas en un texto



ALGUNOS DETALLES


¿Y si quiero saber las menos utilizadas? Simplemente, miremos el final del fichero:


Figura 2. Las palabras menos utilizadas (una vez) están al final


Al mirar el fichero de resultados, me he dado cuenta de que me incluye los dígitos. Puede ser que alguien quiera contar el número de veces que se menciona un año, por poner un ejemplo en el que interese contar los números, pero no es mi caso, así que me gustaría descartar los dígitos.

Por otro lado, me gustaría también, una vez puesto, ignorar los signos de puntuación como el punto, la coma, el punto y coma, paréntesis, comillas dobles, etc... Solucionar esto es fácil.

Habréis visto que el espacio en blanco es el único separador de palabras que estoy utilizando, así que lo único que tengo que hacer es decirle al comando tr (translate, traducir unos caracteres en otros) que no sólo me "traduzca" los espacios en blanco a nueva línea, sino también todos los caracteres que quiero excluir. Algo así:

Figura 3. ¿Y si quitamos los dígitos y signos de puntuación?

Hmmm... vaya. En la primera línea vemos que aparece una "palabra" que es el espacio en blanco. Bueno, no era mi intención contarlo, pero esto tampoco es un problema, se puede ignorar. Por otro lado, vemos que la palabra "de" ahora se cuenta algunas veces más (ha pasado de 1709 a 1712 apariciones). Seguramente antes había algunas apariciones en la que estaba pegada a una coma o algún otro signo de puntuación. Lo mismo ocurre con "la", "que" y otras palabras. Parece que eso pinta bien.

Pero si vemos las últimas líneas, aún siguen apareciendo dígitos. ¿Qué ha pasado? Y, por cierto, el símbolo del % y el € y "las otras" comillas dobles también los quitaré en la próxima versión de mi script.

Lo que ocurre con los dígitos es que le he querido decir que traduzca cualquier cosa que esté en el intervalo 0..9 en un carácter de nueva línea. Y yo, ignorante, he pensado que el intervalo para definir un dígito se podía poner como para otros filtros de unix, como un rango. Pues resulta que no, para el filtro tr podemos usar ciertas categorías de caracteres especiales como :digit: :alpha: y otras (más info tecleando "man tr").

Así que la forma correcta de decírselo sería utilizar esta categoría o simplemente teclear directamente los diez dígitos en la orden:
 
Figura 4.Ahora sí

Como veréis, he introducido algunos caracteres especiales (como tres versiones para las comillas dobles, pues el texto provenía de un documento de Writer de LibreOffice y tenía comillas normales y tipográficas, que a su vez diferencian entre las de apertura y las de cierre), o los tres puntos suspensivos como un único carácter. Como estos caracteres no he sabido cómo ponerlos directamente con el teclado, he usado el copiar / pegar de la consola para conseguir introducirlos en la línea del script.

Por último, os pego aquí la línea final del script en modo texto para que podáis copiarla si os interesa:

cat texto.txt  | tr ' .,;:0123456789()[]"“”%€…' '\n' | sort | uniq -c | sort -nr > resul.txt


Quedan un par de cosas pendientes, como las dichosas tildes, pero eso ya os lo dejo a vosotros, que no es cuestión de darlo todo masticado.

Creo que esto es un buen ejemplo de la potencia de los filtros en el mundo *NIX, y de cómo combinar herramientas pequeñas que hacen pequeñas tareas de forma muy eficiente, para conseguir resultados combinados verdaderamente potentes (¿alguien ha dicho sinergia?).


REFERENCIAS

[1] Contar palabras de diferentes maneras. http://stackoverflow.com/questions/10552803/how-to-create-a-frequency-list-of-every-word-in-a-file

viernes, 30 de mayo de 2014

Leer ficheros, contar el número de líneas y procesarlas

¿Os gusta la lectura? A mí me encanta, uno de mis géneros preferidos es la ciencia ficción. Pero uno no siempre puede leer lo que quiere, y en ocasiones hay que leer cosas que no son tan entretenidas como una buena novela. Por ejemplo, programando, a veces tenemos que leer un fichero de texto línea a línea. (Vale, es un chiste muuuuyyyyy malo, pero al menos es corto ;-)

Bueno, ahora en serio, lo de leer el fichero lo podemos hacer como mínimo de dos formas:

1) Si no nos interesa todo el contenido del fichero, sino que únicamente estamos buscando las líneas que cumplan una determinada condición, podemos ir leyendo línea a línea y procesar esas líneas individualmente. Para ello, abrimos el fichero (fopen) y en un bucle vamos leyendo cada línea (fgets). Esto nos da control sobre cada línea individualmente, y no consume demasiada memoria, ya que una vez procesada la línea la podemos descartar (también la podemos almacenar si nos interesa, claro).

2) Si queremos todo el contenido del fichero de golpe, por ejemplo, en un array de líneas, con PHP podemos usar la función "file" que nos devuelve precisamente eso. Lógicamente, esta segunda opción ocupará más memoria, lo cual puede ser importante si el fichero leído tiene un tamaño grande, aunque me imagino que posiblemente sea una forma más óptima en tiempo, leerlo todo de un golpe. Ya sabéis, el eterno dilema de memoria frente al tiempo. Si después queremos analizar esas líneas leídas y almacenadas en el array, tendremos que hacer un bucle para recorrerlas.

Bien, ¿y qué técnica me conviene? Pues como siempre, depende de lo que quieras hacer. En un primer caso, para ilustrar ambas técnicas, mi objetivo va a ser contar el número de líneas del archivo, sin procesar cada línea individualmente. Os dejo aquí dos funciones en PHP (contar1 y contar2) que ilustran ambos métodos, aunque fácilmente se pueden traducir a cualquier otro lenguaje.

Según lo explicado en el caso 1)

 1 //-------------------------------------------------------------
 2 function contar1($filename) {
 3     $file = fopen($filename, "r");
 4     $num_lineas = 0;
 5     while (!feof($file)) {
 6         if ($line = fgets($file)){
 7            $num_lineas++;
 8         }
 9     }
10     fclose($file);
11     return $num_lineas;
12 }

Y para el caso 2), la función "count" nos da el número de elementos de un array, lo que en este caso coincide con el número de líneas del fichero

 1 function contar2($filename) {
 2     $num_lineas = count(file($filename));
 3     return $num_lineas;
 4 }



Rendimiento de cada opción

Para analizar el rendimiento de ambas opciones, he hecho pruebas contando el número de líneas con un grupo de unos 2.000 ficheros aproximadamente que tengo en un directorio, una aplicación web. He limitado la lectura a ficheros de código fuente en Javascript, PHP, CSS y HTML, con una longitud media de 316 líneas por fichero, aunque esta medida tiene una varianza alta, pues el fichero más largo tiene 30.000 líneas (no es un caso frecuente), y el fichero más pequeño está vacío (cero líneas).

Tras ocho o diez ejecuciones, contando las líneas por el método 1) tardo alrededor de 1.50 segundos, mientras que el método 2) está sobre los 1.25 segundos, es decir, un 17% más rápido. No he hecho pruebas de consumo de memoria RAM, pero supongo que cuando se cargó el fichero de 30.000 líneas en memoria (método 2), mi ordenador debió emitir una pequeña queja, aunque creo que nada preocupante para los estándares de memoria que puede tener un ordenador hoy en día. Claro, esto puede ser muy diferente si esta aplicación la va a ejecutar un único usuario o la van a utilizar varios usuarios a la vez sobre el mismo servidor web.

En mi caso, la diferencia entre ambas técnicas no es significativa, y sin embargo el método 1) me da mucha más flexibilidad para un objetivo que tengo en mente, pues me gustaría hacer algo de procesamiento con cada línea conforme las voy leyendo.

Pero esta entrada ya me está quedando demasiado larga, así que ya hablaré de ello en otra ocasión, y también pondré el código utilizado para el test, con las explicaciones pertinentes.

¿Y tú, sueles enfrentarte al problema de procesar ficheros línea a línea habitualmente? ¿Utilizas alguna de las técnicas de las que hablo aquí o tienes otra forma de hacerlo?

Referencias
La función file es quizás la más interesante de las aquí mencionadas.

Related Posts Plugin for WordPress, Blogger...