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.

lunes, 19 de mayo de 2014

María Jesús y su acordeón (o cómo aprovechar mejor la interfaz de usuario)


Habréis observado que muchas aplicaciones y páginas web, en particular en su versión para teléfonos móviles, organizan la información de una forma muy útil, que consiste en agrupar secciones de información bajo un título, de manera que cada uno de esos bloques se puede expandir o contraer. Eso permite aprovechar mejor la interfaz de usuario y agiliza la organización de la información en pantalla. Este tipo de interfaz se denomina "acordeón", por el hecho de que cada sección se puede expandir y contraer como el cuerpo de dicho instrumento.

Fig. 1. Las secciones del acordeón contienen info "en su interior"

Al desplegar la sección de Historia

Fig. 2.Al desplegar una sección, el icono cambia

En la documentación de jQuery sobre los "acordeones" [http://jqueryui.com/accordion/]se describe en detalle cómo utilizar este tipo de interfaz, pero aquí os dejo una brevísima introducción para que en un minuto podáis tener algo así funcionando con tres sencillos pasos:

1) Como siempre, crear una página e incluir en ella la referencia a jQuery

 1 <head>
 2 ...
 3     <link rel="stylesheet" href="//code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css">
 4     <script src="//code.jquery.com/jquery-1.10.2.js"></script>
 5     <script src="//code.jquery.com/ui/1.10.4/jquery-ui.js"></script>
 6 ...
 7 </head>

2) Crear una división (DIV) que contendrá todas las secciones del acordeón. Cada sección ha de ir precedida por una etiqueta de cabecera (H1, H2... H6). Si hacéis pruebas, veréis que da igual usar H1 que H2 que H3... e incluso se pueden mezclar indistintamente, que jQuery las usará como títulos de las secciones.

 1 <body>
 2 ...
 3 <div id="bloque_acordeon">
 4   <h1>Sección 1. Mis personas preferidas</h1>
 5     <div>
 6     <p>
 7     Esto está dentro del bloque1
 8         <ul>
 9             <li>Claudia Chífer</li>
10             <li>Mariano Gandhi</li>
11             <li>Jason Voorhees</li>
12         </ul>
13     </div>
14 
15   <h1>Sección 2. Mis animales preferidos</h1>
16     <div>
17     <p>
18     Esto está dentro del bloque2
19         <ul>
20             <li>Lassie</li>
21             <li>El pollo a la brasa</li>
22             <li>Claudia Chífer</li>
23         </ul>
24     </div>
25     
26   <h1>Sección 3. Mis plantas preferidas</h1>
27     <div>
28     Esto está dentro del bloque3
29         <ul>
30             <li>El algarrobo mediterráneo</li>
31             <li>María</li>
32             <li>Claudia Chífer</li>
33         </ul>   
34     </div>
35     
36 ...
37 </body>

y 3) Incluir el código jQuery que hace que esta sección funcione como un acordeón. Una simple llamada a una función

 1   <script>
 2   $(function() {
 3     $("#bloque_acordeon" ).accordion();  
 4   });
 5   </script>


Hay un detalle que a mí me gusta, y es el hecho de que la primera sección se pueda cerrar sin tener que abrir ninguna otra. Para esto, basta con añadir dentro de la función "accordion" la propiedad "collapsible: true", quedando así el paso 3:

 1   <script>
 2   $(function() {
 3     $("#bloque_acordeon" ).accordion(
 4         { collapsible: true }
 5     );
 6   });
 7   </script>

Pues aquí está todo junto en esta plantilla que podéis adaptar a vuestras necesidades:

 1 <html>
 2 <head>
 3     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
 4     <title>Ejemplo de un acordeón con jQuery</title>
 5     <link rel="stylesheet" href="//code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css">
 6     <script src="//code.jquery.com/jquery-1.10.2.js"></script>
 7     <script src="//code.jquery.com/ui/1.10.4/jquery-ui.js"></script>
 8   <script>
 9   $(function() {
10     $("#bloque_acordeon" ).accordion(
11         { collapsible: true }
12     );
13   });
14   </script>
15   
16 </head>
17 
18 <body>
19 Organizar la interfaz de usuario con un "acordeón"
20 <div id="bloque_acordeon">
21     <h1>Mis personas favoritas</h1>
22     <div>
23         Esto está dentro del bloque1
24         <ul>
25             <li>Claudia Chífer</li>
26             <li>Mariano Gandhi</li>
27             <li>Jason Voorhees</li>
28         </ul>
29     </div>
30     
31     <h1>Mis animales favoritos</h1>
32     <div>
33         Esto está dentro del bloque2
34         <ul>
35             <li>Lassie</li>
36             <li>El pollo a la brasa</li>
37             <li>Claudia Chífer</li>
38         </ul>
39     </div>
40     
41         <h1>Mis plantas favoritas</h1>
42     </span>
43     <div id="contenido_bloque3" style='display: none'>
44         Esto está dentro del bloque3
45         <ul>
46             <li>El algarrobo mediterráneo</li>
47             <li>María</li>
48             <li>Claudia Chífer</li>
49         </ul>       
50     </div>
51 </div>
52  
53 </body>
54 </html>

Related Posts Plugin for WordPress, Blogger...