Archivo

Entradas Etiquetadas ‘array’

Listas enlazadas en Arduino (primera versión)

Miércoles, 30 de Noviembre de 2011 Gaspar Fernández Sin comentarios

Es parte de un proyecto personal que quiero compartir. Se trata de una clase para utilizar listas enlazadas desde Arduino, aunque la memoria RAM de estos bichillos no sea gran cosa (1K para la versión Diecimila), puede dar para mucho, y es que hay ocasiones en las que podemos necesitar esta flexibilidad a la hora de almacenar y acceder a la información (aunque no pueda ser mucha).

La clase ha sido desarrollada utilizando templates, por lo que podréis particularizar esta clase para cualquier tipo de dato que estéis manejando. Declarando la lista de la siguiente forma:

1
LList <tipo> lista;

Antes de dar el código, hagamos algunas operaciones, vamos a ver lo que podemos hacer con 1K (vale con 600 bytes libres):

  • Con un tipo int, cada nodo ocupará 4 bytes: 600 / 4 = 150 nodos (alguno menos en realidad). Pero estos nodos pueden indicar posiciones en un mapa, un pequeño informe de lo que ha transcurrido, lecturas, estado de periféricos, etc
  • Con un tipo:
    1
    2
    3
    4
    5
    struct Map
    {
      int key;
      int value;
    }

    : podemos almacenar 600/6 = 100 elementos con clave y valor, por ejemplo, una matriz o vector disperso que nos pueden ayudar para un cálculo matemático

  • Lo malo son las cadenas, no podemos escribir un libro, pero si hacemos un tipo que almacene unas 28 letras, cuyo nodo ocupará 30 bytes, podremos almacenar 600/30=20 mensajes distintos, aunque recomiendo que esos mensajes sean generados por el programa, ya que si los mensajes son predefinidos, podemos almacenarlos en la memoria flash, que tendremos más sitio.

Ahí va el código para los copy-paste aunque el archivo lo podéis bajar de aquí (9.5Kb). Por cierto, al final del archivo hay un código de ejemplo para probar que todo funcione bien.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
/**
*************************************************************
* @file ArduinoList.pde
* Another implementation of linked lists to use in Arduino.
*    Copyright (C) 2011 Gaspar Fernández
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
* @brief Linked list implementation for Arduino
*
* Features:
*   - Can access to head and tail directly
*   - Can pop back and front from this class
*   - Can access any item from the item number
*   - Can set a callback function when an error occurs
*
* @author Gaspar Fernández <blakeyed@totaki.com>
* @web http://totaki.com/poesiabinaria
* @version 0.1
* @date 27 nov 2011
* Change history
*
* To-do:
*   - Better control over non-critical errors, maybe another callback
*   - Pre-defined error methods: by serial, by output leds...
*************************************************************/


#define LLIST_ERROR_NOMEMORY     1 // Not enough memory to insert element
#define LLIST_ERROR_NOTAIL       2 // Can't get last items
#define LLIST_ERROR_NOFRONT      3 // Can't get head items
#define LLIST_ERROR_NOITEMS      4 // List empty, can't read items
#define LLIST_ERROR_NEITEMS      5 // Not enough items (pos>=sz)

template <typename Type>
class LList
{
public:
  typedef void (*ErrorCallback)(int);

  // We can add more error types
  enum ListErrorType
    {
      NO_ERROR_REPORTING,   // No error reporting
      CALLBACK          // Errors will trigger a callback
    };

  // Destruction. Clear data
  ~LList();

  // Initialization
  LList();

  // Inserts new item in the last position
  void push_back(const Type item);
  // Is the list empty?
  inline bool isEmpty() const;
  // Returns the size of the list
  inline int size();
  // Returns the last item
  inline Type back();
  // Returns the first item
  inline Type front();
  // Return the last item and delete it
  Type pop_back();
  // Return the first item and delete it
  Type pop_front();

  // Get the item at position pos
  Type getElement(unsigned pos);
  // Insert a new item at position pos
  void insert(int pos, Type item);
  // set error callback function
  void setErrorCallback(ErrorCallback cback);
  // no error reporting
  void setNoErrorReporting();
  // clears the list
  void clear();
private:
  // Reports an error
  void report_error(int err);
 
  // Deletes a list when there is only one element
  void delete_list();

  typedef struct node
  {
    Type item;
    node *next;
  } node;

  node *head;           // Pointer to the head
  node *tail;           // Pointer to the tail
  int sz;           // List size
  ListErrorType errorType; 
  ErrorCallback ecback;     // Callback when reporting an error
};

template <typename Type>
LList<Type>::LList()
{
  head=NULL;
  tail=NULL;
  sz=0;
  errorType=NO_ERROR_REPORTING;
}

template <typename Type>
LList<Type>::~LList()
{
  clear();          // Clears the list before destroying
}

template <typename Type>
void LList<Type>::push_back(const Type item)
{
  node *aux=tail;
 
  // Reserve memory for a new node
  tail=(node*)malloc(sizeof(node));
  if (tail==NULL)
    {
      report_error(LLIST_ERROR_NOMEMORY);
      return;
    }

  // Stores the item information
  tail->item=item;
  tail->next=NULL;

  // Link to the list
  if (isEmpty())
    head=tail;
  else
    aux->next=tail;

  sz++;

}

template <typename Type>
bool LList<Type>::isEmpty() const
{
  return (sz==0);       // (sz==0) = EMPTY
}

template <typename Type>
int LList<Type>::size()
{
  return sz;
}

template <typename Type>
inline Type LList<Type>::back()
{
  if (isEmpty())        // List empty = No last item
    {
      Type t;
      report_error(LLIST_ERROR_NOTAIL);
      return t;
    }

  return tail->item;
}

template <typename Type>
inline Type LList<Type>::front()
{
  if (isEmpty())        // List empty = No first item
    {
      Type t;
      report_error(LLIST_ERROR_NOFRONT);
      return t;
    }

  return head->item;
}

template <typename Type>
Type LList<Type>::pop_back()
{
  node *aux=head;
  Type t;

  if (isEmpty())        // List empty = No last item
    {
      report_error(LLIST_ERROR_NOTAIL);
      return t;
    }
 
  if (head==tail)       // If there is only 1 item
    {
      t=head->item;
      delete_list();
      return t;
    }
  else
    {               // More than one item
      while (aux->next!=tail)   // Searches for the item previous to the last
    aux=aux->next;
     
      t=tail->item;     // I will return the tail item
      free(tail);      
      tail=aux;         // Define the new tail
      tail->next=NULL;
      sz--;         // 1 item less

      return t;
    }
}

template <typename Type>
Type LList<Type>::pop_front()
{
  Type t;
  node *aux=head;
 
  if (isEmpty())        // List empty = No head
    {
      report_error(LLIST_ERROR_NOFRONT);
      return t;
    }

  t=aux->item;          // I will return the head
  head=head->next;      // The new head is the item after the head
  free(aux);
  if (head==NULL)       // If I had deleted the last item, replace the tail
    tail=NULL;          // too.
  sz--;

  return t;
}

template <typename Type>
void LList<Type>::delete_list()
{
  free(head);           // I will call this method when deleting
  head=NULL;            // a list with only one element, so all
  tail=NULL;            // the variables are set.
  sz=0;
}

template <typename Type>
void LList<Type>::insert(int pos, Type item)
{
  node *newitem;
  node *aux=head;
  int i;

  // Allocate memory for the new item
  newitem=(node*)malloc(sizeof(node));
  if (newitem==NULL)
    {
      report_error(LLIST_ERROR_NOMEMORY);
      return;
    }
  // Stores the item information
  newitem->item=item;
  newitem->next=NULL;

  // Link the item to the list
  if (isEmpty())
    {               // If the list is empty there will be only
      head=newitem;     // one item, so it will be the head and the
      tail=newitem;     // tail.
    }
  else if (pos==0)      // If the item is going to be inserted at the beginning
    {
      newitem->next=head;   // we will move the head
      head=newitem;
    }
  else if (pos>=sz)     // If the position is greater than the last item's position
    {               // it will be inserted after this item, at the tail.
      tail->next=newitem;      
      tail=newitem;
    }
  else
    {               // If not, we will have to find the position of the
      i=0;          // previous item to link its next pointer to the new
      while (i<pos-1)       // element.
    {
      aux=aux->next;
      ++i;
    }
      newitem->next=aux->next;  // And then link the next pointer of our new element
      aux->next=newitem;    // to where the next pointer of the previour element
    }               // was pointing.
  sz++;
}

template <typename Type>
Type LList<Type>::getElement(unsigned pos)
{
  int i=0;
  Type t;
  node *aux=head;

  if (isEmpty())        // If the list is empty = No data
    {
      report_error(LLIST_ERROR_NOITEMS);
      return t;
    }

  if (pos>=sz)          // If the item asked for is greater than the
    {               // last item's position. There is no valid element
      report_error(LLIST_ERROR_NEITEMS);
      return t;
    }
 
  if (pos==sz-1)        // If the item we asked for is the last, we can
    return tail->item;      // return it inmediately


  while (i<pos)         // If not, we must look for it
    {
      aux=aux->next;
      i++;
    }
  return aux->item;
}

template <typename Type>
void LList<Type>::setErrorCallback(ErrorCallback cback)
{
  ecback=cback;         // This method sets the error callback to invoke
  errorType=CALLBACK;       // when an error occurs.
}

template <typename Type>
void LList<Type>::report_error(int err)
{
  if (errorType==CALLBACK)  // If the error reporting is through a callback,
    {               // call it.
      ecback(err);
    }
}

template <typename Type>
void LList<Type>::clear()
{
  node *aux;

  while (head!=NULL)        // Delete all elements
    {
      aux=head;
      head=head->next;
      free(aux);
    }
 
  tail=NULL;            // Restore variable stats
  sz=0;
}  

template <typename Type>
void LList<Type>::setNoErrorReporting()
{
  errorType=NO_ERROR_REPORTING;
}

// Test program
// LList<int> l;

// void blink(int err)
// {
//   while (1)
//     {
//       digitalWrite(11,HIGH);
//       delay(1000);
//       digitalWrite(11, LOW);
//       delay(1000);  
//     }
// }

// void setup()
// {
//   pinMode(11, OUTPUT);
//   Serial.begin(9600);
//   l.setErrorCallback(blink);
//   l.push_back(45);
//   l.push_back(42);
//   l.push_back(47);
//   l.push_back(89);
//   l.insert(0, 33);
//   l.insert(1, 53);
//   l.insert(9, 73);
//   // 33
//   // 53
//   // 45
//   // 42
//   // 47
//   // 89
//   // 73
// }

// void loop()
// {
//   char r;
//   if (Serial.available()>0)
//     {
//       while (Serial.available()>0)
//  {
//    r=Serial.read();
//    delayMicroseconds(10000);
//  }

//       for (unsigned i=0; i<l.size(); i++)
//  {
//    Serial.println(l.getElement(i), DEC);
//  }
//       Serial.print("Elements: ");
//       Serial.println(l.size(), DEC);
//       Serial.print("Last: ");
//       Serial.println(l.pop_front(), DEC);
//     }
// }

Crear un archivo PHP que sólo contenga un array (desde un programa PHP)

Domingo, 9 de Enero de 2011 Gaspar Fernández Sin comentarios

Aunque puede parecer redundante, pero es una idea curiosa. Sobre todo cuando creamos un sitio web con muchas opciones. Tenemos varias opciones:

  • Guardarlas en base de datos. Con lo cual cada página que carguemos tiene que hacer una petición, y si hay muchas visitas podemos saturar el sistema. Además una petición es una tarea un poco lenta
  • Guardarlas en un archivo de texto. Por lo que tendremos que hacer un programa que lea el fichero y lo interprete. Puede llegar a ser lento si el formato de nuestro archivo es complicado.
  • Guardarlas en un fichero binario. Los problemas son parecidos a los del fichero de texto, aunque a lo mejor ahorramos algo en sintaxis al representar todo en bloques; pero si tenemos un problema podemos tardar mucho tiempo depurando y leyendo el fichero de configuración con un editor hexadecimal.
  • Archivo de texto con el array serializado (serialize()) y des-serializarlo (unserialize()) cuando vayamos a leer… Aunque no tengo claro cuánto tiempo invierten serialize() / unserialize(), pero supongo que más tiempo que una lectura simple de un array.
  • Crear un archivo PHP que contenga la configuración. Es lo más rápido ya que no tenemos que crear texto que interprete y se lee de forma nativa (hay que tener en cuenta que PHP es un lenguaje interpretado, sólo tiene que interpretar el código y no un lector que interprete la configuración como en los casos anteriores).

Vamos con este último. Estas rutinas las creé con esa idea en mente. Lo que hago es insertar un array en un archivo PHP, que luego leeré desde el programa con include()/require()/etc… y si tenemos que guardar la configuración, podemos utilizar esta función array_to_phpcode():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?php

function var2php(&$var)
{
  if (is_numeric($var))
    $o=$var;
  else
    $o='\''.addslashes($var).'\'';

  return $o;
}

function array2php(&$array)
{
  $o='';
  if (is_array($array))
    {
      $o.=' array (';
      foreach ($array as $key=>$value)
    {
      $o.=var2php($key).' => '.array2php($value).",\n";
    }
      $o.=')';
    }
  else
    $o.=var2php($array);
  return $o;
}

/**
******************************************************************
* @brief Crea código PHP con un array como contenido
*
* @param $array    Array a insertar
* @param $phpvar   Variable PHP donde vamos a asignarlo
* @param $precode  Código PHP a incluir antes
* @param $postcode Código PHP a incluir después
*
* @return Código PHP
*
******************************************************************/

function array_to_phpcode(&$array, $phpvar, $precode, $postcode, $comment=false)
{
  $o ="<?php\n/* Fichero generado automáticamente por LYB el ".date("Ymd")." a las ".date("H:i").
    " */\n".$comment."\n\n".$precode."\n\n/* Array a incluir */\n";
  $o.=$phpvar.' = '.array2php($array).';';
  $o.="\n/* Información incluida */\n".$postcode."\n?>";

  return $o;
    }

$array=array('user' => 'Gaspy',
         'blog' => 'Poesía Binaria',
         'URL'  => 'http://totaki.com/poesiabinaria/'
         );

echo array_to_phpcode($array, 'usuario', "echo \"Hola Mundo\";", "/* DESPUÉS OTRO COMENTARIO */", "/* COMENTARIO INICIAL */");
?>

Aunque a la hora de salvar la información sea poco óptimo (ya que machacamos el archivo entero con contenidos nuevos cuando tal vez sólo cambia un elemento), pero la lectura de las opciones será muy rápida (y es lo que nos interesa porque tendremos que hacerlo a cada página que se cargue). De todas formas, siempre podemos desarrollar una nueva versión que busque las diferencias y sea capaz de ubicarlas en el archivo y sustituirlas, pero seguramente nos de un código bastante más lento.

El método más rápido para traer un valor pasado por $_GET [ PHP ]

Miércoles, 15 de Diciembre de 2010 Gaspar Fernández 1 comentario

Tal vez por paranoia, por ganas de perder el tiempo, o por optimizar aún más el código fuente nos encontremos ante esta cuestión.

He realizado una serie de pruebas para ver cuál es el método que mejor funciona para traer un valor de $_GET basándome en el peor de los casos: cuando este valor no existe. Tal vez las pruebas no tengan excesiva validez por el método utilizado, aunque he intentado hacer todas las pruebas en las mismas condiciones.

Las medidas de tiempo han sido tomadas con el comando time de la siguiente manera:

$ time php myscript.php

Se han hecho un total de 7 pruebas, 1 de ellas de control. Las pruebas se han basado en la generación de cadenas aleatorias (que más tarde serán nuestro parámetro $_GET), además, la obtención del parámetro ha sido hecha unas 500000 veces con un bucle for. Se detallan las pruebas realizadas.
Cada prueba se ha repetido tres veces y se ha capturado el tiempo de las tres veces. Estamos usando un software complicado que hará muchas cosas mientras se realiza el test, es más, el sistema operativo puede requerir hacer una tarea que puede influir en nuestro resultado, por lo que así minimizamos el error (y si en alguna ocasión sale un dato desorbitado podemos descartarlo).

Prueba de control [TEST 0]

La generación de parámetros es lo que más tarda. Se ha utilizado uniqid() para obtener cadenas de la misma longitud y contenido arbitrario y diferente cada vez. Además, tanto la obtención del parámetro (en las siguientes pruebas) como la generación del nombre del parámetro se hace un número N de veces gracias a un bucle (que también invierte un tiempo).

Con esta prueba de control podremos ver en qué grado son significativos los valores obtenidos luego (cuando hagamos la obtención de datos), ya que vemos que el bucle y la generación tardan de media 4.493 segundos.

El código fuente del script para esta prueba es el siguiente:

1
2
3
4
5
echo "Probando 500000 peticiones GET";
for ($i=0; $i<500000; $i++)
{
$getVar=uniqid();
}

Anulando error_reporting() [TEST 1]

Lo que nos impide hacer un $variable=$_GET['parametro']; es el error_reporting, ya que si éste no existe, nos devolverá un feo error por pantalla. Así que probemos primero desactivando esto y obteniendo el valor de la variable.

1
2
3
4
5
6
7
echo "Probando 500000 peticiones GET";
error_reporting(E_NONE);
for ($i=0; $i<500000; $i++)
{
$getVar=uniqid();
$dato=$_GET[$getVar];
}

Con @$_GET[...] [TEST 2]

Otra manera de impedir que PHP muestre sus errores en pantalla, es colocando una @ delante de lo que creemos que puede fallar, y en este caso es la sentencia de $_GET[$getVar].

1
2
3
4
5
6
7
echo "Probando 500000 peticiones GET";

for ($i=0; $i<500000; $i++)
{
$getVar=uniqid();
$dato=@$_GET[$getVar];
}

if…else [TEST 3]

No es nada intuitivo, PHP es un lenguaje de scripting, así que cuanto más escribamos, peor, pero merece la pena hacer la prueba, y nos podemos llevar una sorpresa. Ahora, hacemos la obtención del dato, pero primero comprobamos que éste existe con isset().

1
2
3
4
5
6
7
8
9
echo "Probando 500000 peticiones GET";
for ($i=0; $i<500000; $i++)
{
$getVar=uniqid();
if (isset($_GET[$getVar]))
$dato=@$_GET[$getVar];
else
$dato=false;
}

Operador ternario ? [ TEST 4 ]

Tal vez vaya mejor que el if…else (TEST 3), por aquella razón de escribir menos que comentaba…

1
2
3
4
5
6
echo "Probando 500000 peticiones GET";
for ($i=0; $i<500000; $i++)
{
$getVar=uniqid();
$dato=(isset($_GET[$getVar]))?$_GET[$getVar]:false;
}

Creando una función auxiliar gval() [ TEST 5 ]

Tal vez escribimos más que con el TEST 2 pero, vamos a probar, qué tal va. Tal vez si la diferencia de tiempo no es significativa podemos utilizar este método…

1
2
3
4
5
6
7
8
9
10
11
function gval($getVar)
{
return (isset($_GET[$getVar]))?$_GET[$getVar]:false;
}

echo "Probando 500000 peticiones GET";
for ($i=0; $i<500000; $i++)
{
$getVar=uniqid();
$dato=gval($getVar);
}

Función auxiliar y parámetro por referencia [ TEST 6 ]

Una nueva prueba, ya que hemos hecho una función auxiliar, veamos cómo influye el pasar el parámetro por valor o pasarlo por referencia. En este caso podríamos hacerlo sin problema.

1
2
3
4
5
6
7
8
9
10
11
function gval(&amp;$getVar)
{
return (isset($_GET[$getVar]))?$_GET[$getVar]:false;
}

echo "Probando 500000 peticiones GET";
for ($i=0; $i<500000; $i++)
{
$getVar=uniqid();
$dato=gval($getVar);
}

Resultados obtenidos

Tipo de prueba Primer resultado Segundo resultado Tercer Resultado Media
TEST 0 4,4530 4,5960 4,4310 4,4933
TEST 1 6,7560 6,1390 5,8350 6,2433
TEST 2 7,3880 7,3350 7,3120 7,3450
TEST 3 5,5270 4,9900 5,0610 5,1927
TEST 4 5,4480 4,9820 5,1060 5,1787
TEST 5 6,2860 6,4050 5,8160 6,1690
TEST 6 5,9840 5,6290 5,7440 5,7857

Con estos resultados podemos observar que:

  • Efectivamente, el test de control es el más lento, incluye generación del nombre aleatorio del parámetro, el bucle y la carga del programa (PHP), pero más o menos partimos de que el tiempo mínimo para esta prueba son cerca de 4.5 segundos. El tiempo de obtención de dato, será más o menos la diferencia entre el tiempo total del test y los 4.5 segundos aproximadamente obtenidos aquí.
  • TEST 1: error_reporting(E_NONE) nos entorpecería en tareas de depuración (cuando realmente deseemos tenerlo en otro valor), de todas formas no es una opción, comparado con los demás. Deberíamos estar pendientes del valor de error_reporting() cuando queramos depurar el script, no ahorraríamos mucho código y tampoco es el mejor con respecto al tiempo.
  • TEST 2: Estaría bien que un carácter nos solucionara la vida, pero no es así… conseguimos el peor tiempo de todos. Se ve que a PHP le sienta mal cuando no encuentra una variable…
  • TEST 3 y TEST 4: Parece que un if…else nos puede ayudar a hacer nuestro código más rápido, un poco más elegante y funcionar mejor. También vemos que el operador ? puede ayudar. Y vemos, además, que el tiempo entre if…else y entre ? es el mismo (la diferencia es despreciable en este caso)
  • TEST5: Visto lo visto con TEST3 y TEST4 en los que escribimos más que con TEST2, vamos a crear una función auxiliar, a ver cómo se comporta con respecto a lo que tenemos. Comprobamos que el uso de una función, hace nuestro código más claro (punto positivo) y aún tarda menos que TEST1 y TEST2 aunque tarda algo más que TEST3 y TEST4. TEST5 tarda aproximadamente lo que TEST1.
  • TEST6: Parece que el hecho de no copiar el valor de una variable de verdad influye significativamente en el tiempo cerca de medio segundo; podemos seguir utilizando un método elegante con nuestra función auxiliar y al mismo tiempo aproximarnos a un tiempo mínimo.
  • Sé que 500000 iteraciones puede parecer exagerado aunque en momentos de carga de servidor y cuando esperamos que nuestra página tenga un número importante de visitas tal vez se agradezca un poco de agilidad, sobre todo en recepción de formularios (con variables $_POST). De todas formas, queda claro con estas pruebas que el hecho de poner una @ en un comando o asignación, sólo hace que PHP se trague los errores, pero es un método lento, con lo que podemos empezar a optimizar nuestro código por ahí.

Separar palabras de una cadena de caracteres en un array [ C ]

Jueves, 2 de Diciembre de 2010 Gaspar Fernández Sin comentarios

Si conocéis PHP, habréis visto la función explode(). Tiene una utilidad curiosa, separa cadenas de caracteres en elementos de un array. Es como hacer un corte a la cadena cada vez que encontremos un carácter delimitador. Por ejemplo:

“Bienvenido al blog Poesía Binaria”, si usamos como delimitador ” ” (un espacio), los elementos del array quedarían así:

[0] = “Bienvenido”
[1] = “al”
[2] =”blog”
[3] =”Poesía”
[4] = “Binaria”

Pero en C, no encontramos ninguna función que haga esto, tenemos que currar un poco; aunque tenemos una función de string.h que hace algo que se parece en cierto modo: strtok(), esta función, va a separar una cadena en fragmentos modificando la propia cadena, es decir, introduciremos caracteres terminadores al final de cada palabra.

La solución intuitiva, sería hacer una copia en una variable de la original, y utilizar la copia para strtok() aunque el acceso a cada palabra no es tan flexible, es decir, strtok() nos devolverá un puntero a donde empieza la palabra, pero en cuanto queramos conocer la siguiente palabra que encontremos no podremos volver fácilmente a la anterior.

Por ello, planteo la siguiente solución:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#define MAX_CADENA 100

/**
 ******************************************************************
 * @brief Extrae palabras de una cadena y las coloca en un array
 *
 * @param orig     Cadena inicial
 * @param delim    Las palabras vendrán delimitadas por uno de los
 *                 caracteres de esta cadena            
 * @param args     Array donde separaremos cada palabra
 * @param max_args Número de palabras como máximo a extraer (tamaño
 *                 del array)
 *
 * @return Número de palabras extraídas. (max_args+1) si en la
 *         cadena hay más de max_args palabras.
 *
 ******************************************************************/

int extrae_argumentos(char *orig, char *delim, char args[][MAX_CADENA], int max_args)
{
  char *tmp;
  int num=0;
  /* Reservamos memoria para copiar la candena ... pero la memoria justa */
  char *str = malloc(strlen(orig)+1);
  strcpy(str, orig);

  /* Extraemos la primera palabra */
  tmp=strtok(str, delim);
  do
    {
      if (num==max_args)
    return max_args+1;  /* Si hemos extraído más cadenas que palabras devolvemos */
                /* El número de palabras máximo y salimos */

      strcpy(args[num], tmp);   /* Copiamos la palabra actual en el array */
      num++;

      /* Extraemos la siguiente palabra */
      tmp=strtok(NULL, delim);
    } while (tmp!=NULL);

  return num;
}

Con esta función ejecutaremos strtok para extraer palabras de una cadena, como máximo (max_args+1) veces. El objetivo es rellenar el Array, debido a las limitaciones de los Arrays tenemos que decir el número máximo de elementos que vamos a extraer, la cadena podrá tener 1000 palabras, pero si nuestro array sólo tiene capacidad para 10 no podremos extraer todo; y la función debe estar preparada para eso; por tanto cuando no hayamos podido extraer todo, devolveremos max_args+1, si todo va bien, devolveremos el número de palabras extraídas.

Lo curioso es que cambiando sólo una línea podemos hacer que admita en lugar de un array de cadenas de caracteres (char args[][MAX_CADENA]) un array de punteros de cadenas de caracteres (char *args[]) por lo que en cierto modo, depende del uso que vayamos a darle es algo más flexible, en este caso repartiremos en el array anterior los valores de las posiciones de memoria donde empieza cada palabra en la cadena copiada de la original:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int extrae_argumentos_d(char *orig, char *delim, char *args[], int max_args)
{
  char *tmp;
  int num=0;
  /* Reservamos memoria para copiar la candena ... pero la memoria justa */
  char *str = malloc(strlen(orig)+1);
  strcpy(str, orig);

  /* Extraemos la primera palabra */
  tmp=strtok(str, delim);
  do
    {
      if (num==max_args)
    return max_args+1;  /* Si hemos extraído más cadenas que palabras devolvemos */
                /* El número de palabras máximo y salimos */

      args[num]=tmp;            /* Copiamos la dirección de memoria tmp en args[num] */
      num++;

      /* Extraemos la siguiente palabra */
      tmp=strtok(NULL, delim);
    } while (tmp!=NULL);

  return num;
}

Ahora acompaño un programa para probar esto:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define MAX_ARGS 90
#define MAX_CADENA 100

// Aquí copiamos extrae_argumentos o extrae_argumentos_d
int main()
{
  char cadena[]="Una ristra de palabras separadas por espacios";

  char args[MAX_ARGS][MAX_CADENA]; // Para extrae_argumentos
//  char *args2[MAX_ARGS];                  // Para extrae_argumentos_d

  int nargs = extrae_argumentos(cadena, " ", args, MAX_ARGS);
  int i;

  if (nargs>MAX_ARGS)
    {
      printf ("Se han devuelto más palabras del máximo\n");
      nargs=MAX_ARGS;
    }

  printf("CADENA: %s\n", cadena);
  for (i=0; i<nargs; i++)
    printf("Palabra %d: \"%s\"\n", i, args[i]);

  return 0;
}

Ordenación en PHP. Ordenar provincias, pero Madrid y Barcelona ponerlas primero.

Sábado, 4 de Septiembre de 2010 Gaspar Fernández Sin comentarios

En el desarrollo del registro de una página web para España, es normal que la mayoría de las personas vengan de Madrid, Barcelona, Valencia o Sevilla (las provincias podemos cambiarlas).

El algoritmo para ello, en PHP es el siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php
$provincias = array ("Almería",   "Cádiz",       "Córdoba",  "Granada",     "Huelva",     "Jaén",           "Málaga",
"Huesca",    "Teruel",      "Zaragoza", "Asturias",    "Cantabria",  "Sevilla",        "Zamora",
"Ávila",     "Burgos",      "León",     "Palencia",    "Salamanca",  "Segovia",        "Soria",
"Albacete",  "Ciudad Real", "Cuenca",   "Guadalajara", "Toledo",
"Barcelona", "Girona",      "Lleida",   "Tarragona",   "Alicante",   "Castellón",      "Valencia",
"Badajoz",   "Cáceres",     "A Coruña", "Lugo",        "Ourense",    "Pontevedra",     "La Rioja",
"Murcia",    "Navarra",     "Álava",    "Guipúzcoa",   "Vizcaya",    "Islas Baleares", "Las Palmas",
"Santa Cruz de Tenerife",   "Ceuta",    "Melilla",     "Valladolid", "Madrid");

function ordena_provincias ($a, $b)
{
static $primeras = array ("Madrid", "Barcelona", "Valencia", "Sevilla");

$iaa = in_array($a, $primeras);
$iab = in_array($b, $primeras);

/* Si las dos provincias están en el array $primeras miramos en qué posición están */
if ( ($iaa) &amp;&amp; ($iab) )
{
$iaa = array_search($a, $primeras);
$iab = array_search($b, $primeras);

return ($iaa<$iab)?-1:1;
}
/* Si sólo está $a en el array $a va primero */
elseif ($iaa)
return -1;
/* Si sólo está $b, $b va después */
elseif ($iab)
return 1;
/* Si no está ninguna de las dos, miramos cuál va primero alfabéticamente */
else
return strcmp($a,$b);
}

sort($provincias);

print_r($provincias);

usort($provincias, 'ordena_provincias');
print_r($provincias);
?>

Primero se muestra el array de las provincias ordenadas por orden alfabético, y luego nuestra ordenación especial (con cuatro provincias como las primeras).

La ordenación la basamos en la función usort(), y como función de intercambio utilizará ordena_provincias() esta función tiene otro array con las provincias que deben ir primero.

Fibonacci recursivo en C [ intentemos no repetir operaciones! ]

Lunes, 21 de Junio de 2010 Gaspar Fernández Sin comentarios

fibo
Es un ejercicio muy típico cuando se está aprendiendo con funciones recursivas es la sucesión de Fibonacci, aquella en la que F(n)=F(n-1)+F(n-2).
Aunque esta técnica podemos (y deberíamos) utilizarla para una gran cantidad de algoritmos. A veces es necesario sacrificar un poco la memoria del sistema (sin pasarnos) para agilizar y hacer más rápido el programa. Debemos adoptar una solución que beneficie al usuario, no es plan de dejarlo sin memoria, pero tampoco es plan de que la solución se eternice.

Una primera implementación puede ser la siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int fibonacci_simple(int n)
{
  /* La función normal, recursiva */
  if (n>2)
    return fibonacci_simple(n-1) + fibonacci_simple(n-2);
  /* Este es el caso más común,  */
  else if (n==2)        /* Ponemos esto para agilizar un poco el proceso */
    return 1;
  else if (n==1)       
    return 1;
  else if (n==0)
    return 0;
  else
    return -1;          /* Error */
}

Si creamos un programa de ejemplo como este:

1
2
3
4
5
6
7
8
9
10
int main(int argc, char *argv[])
{
    unsigned int numero;

    /* Generamos Fibonaccis */
    for (numero=0; numero<48; numero++)
      printf("%u\n", fibonacci_simple(numero));

    return 0;
}

Podemos observar que para generar la secuencia de 48 números (he escogido este valor, porque a partir de ahí no tenemos suficiente de 32bits para almacenar el valor obtenido), tardamos varios minutos, en concreto, en mi equipo (Pentium M 1.73MHz), echó algo más de 10 minutos. ¡ 10 minutos ! Es un buen rato para lo que ha hecho.
Si lo estudiamos detenidamente (en negrita pongo las veces que llamamos a fibonacci_simple() sin necesidad):

  • fibonacci_simple(0)
  • fibonacci_simple(1)
  • fibonacci_simple(2)
  • fibonacci_simple(3) (que llama a fibonacci_simple(2) y fibonacci_simple(1) )
  • fibonacci_simple(4) (que llama a fibonacci_simple(3) ( que llama a fibonacci_simple(2) y fibonacci_simple(1) ) y a fibonacci_simple(2) )
  • fibonacci_simple(5) (que llama a fibonacci_simple(4) ( que llama a fibonacci_simple(3) ( que llama a fibonacci_simple(2) y fibonacci_simple(1) ) y a fibonacci_simple(2) ) y a fibonacci_simple(3) ( que llama a fibonacci_simple(2) y fibonacci_simple(1) )

Yo creo que con esto nos hacemos una idea de lo que quiero decir, se repiten demasiadas operaciones, seguro que hay alguna forma de solucionar esto.

Propongo lo siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
int fibonacci_buffer(int n)
{
  static int *fbuffer=NULL; /* Buffer */
  static int bufsize=0; /* Tamaño de buffer */
  int n3;           /* n-2 */
  int i;            /* Recorre buffers */

  if (n>2)
    {
      n3=n-2;
      if (fbuffer==NULL)
    {
      fbuffer = (int*) malloc((n3 * sizeof(int)));
      for (i=0; i<n3; i++)
        fbuffer[i]=0;   /* Ponemos buffer a 0 */

/*    printf("Creo buffer: %d -- %d\n", n3*sizeof(int), errno); */

      bufsize=n3;
    }
      else if (bufsize<n3)
    {
      fbuffer = (int*) realloc(fbuffer, n3 * sizeof(int));
/*    printf("Amplio buffer\n"); */

      for (i=bufsize; i<n3; i++) /* Ponemos a 0 la memoria nueva */
        fbuffer[i]=0;

      bufsize=n3;
    }
/*       printf("-%d--%d\n", n, fbuffer[n3-1]); */
      if (fbuffer[n3-1]!=0) /* Si se encuentra el fibonacci del número que buscamos  */
    return fbuffer[n3-1];   /* en memoria, lo devolvemos directamente */
      else
    {
      /* Si el fibonacci no se encuentra, vamos a generarlo */
      fbuffer[n3-1] = fibonacci_buffer(n-1) + fibonacci_buffer(n-2);
      //      printf("He generado con éxito fbuffer[%d]\n", n3-1);
      return fbuffer[n3-1];
    }

/*       return fibonacci_simple(n-1) + fibonacci_simple(n-2); */
    }
  else if (n==2)        /* Ponemos esto para agilizar un poco el proceso */
    return 1;
  else if (n==1)
    return 1;
  else if (n==0)
    return 0;
  else if (n==-1)       /* Un caso nuevo n==-1 */
    free(fbuffer);      /* Liberamos memoria */

  return -1;            /* Error */
}

Es algo más enrevesado, ya que usamos variables estáticas (podríamos hacer variables globales, con cuidado), así evitamos que la información que vamos almacenando se pierda entre ejecuciones de la función; por otra parte, utilizamos malloc() y realloc() para reservar sólo la memoria que vamos necesitando.
Por otra parte para reducir la memoria utilizada, hacemos que fibonacci(3) se almacene en la posición 0 del buffer (porque el de 2, 1 y 0, ya nos los sabemos); por otra parte… la variable n3 vale n-2; esto es simplemente porque si tenemos que calcular fibonacci(5), y sabemos que fibonacci(3) está almacenado en fbuffer[0], fibonacci(4) estará en fbuffer[1] y fibonacci(5) estará en fbuffer[2]; es decir tenemos que almacenar 3 valores de fibonacci (5-2=3).
Por otra parte, siempre que reservamos memoria ponemos los nuevos espacios a 0, ya que comprobamos que no hay ningún valor almacenado si éstos son 0.

Con el almacén de los números conseguimos que el esquema anterior se quede en:

  • fibonacci_buffer(0)
  • fibonacci_buffer(1)
  • fibonacci_buffer(2)
  • fibonacci_buffer(3) (que llama a fibonacci_buffer(2) y fibonacci_buffer(1)
  • fibonacci_buffer(4) (que llama a fibonacci_buffer(3) ( que saca el valor del buffer ) y a fibonacci_simple(2) )
  • fibonacci_buffer(5) (que llama a fibonacci_buffer(4) (que saca el valor del buffer) y a fibonacci_buffer(3) (que saca el valor del buffer)

Con este algoritmo consigo generar todos los números en 4ms.

¿Será posible simplificarlo un poco más?
Sólo hay que ver cómo se hace la generación de valores de forma recursiva. Si lo pensamos bien, cuando llamamos a fibonacci_buffer(5) la primera vez y lo queremos almacenar en fbuffer[2] llamaremos a fibonacci_buffer(4) (que se almacenará en fbuffer[1], y éste llamará a fibonacci_buffer(3) (que se almacenará en fbuffer[0]); es decir, lo primero que se generará será fbuffer[0], luego fbuffer[1] y por último fbuffer[2]… ¡ vamos por orden ! Sabiendo esto, nos podremos ahorrar el código de poner a 0 los elementos del buffer sabiendo cuál es el último elemento generado.

Para los incrédulos, podemos eliminar el comentario de la línea:

1
//    printf("He generado con éxito fbuffer[%d]\n", n3-1);

y ejecutar… veremos con qué orden se van generando los números.

Bien, intentemos eliminar las puestas a 0 de los elementos del buffer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
int fibonacci_buffer2(int n)
{
  static int *fbuffer=NULL; /* Buffer */
  static int bufsize=0;         /* Tamaño de buffer */
  static int bpos=0;            /* Recorre buffers */
  int n3;           /* n-2 */

  if (n>2)
    {
      n3=n-2;
      if (fbuffer==NULL)
    {
      fbuffer = (int*) malloc((n3 * sizeof(int)));
      bufsize=n3;   /* Mantenemos bufsize por si redimensionamos el buffer */
    }
      else if (bufsize<n3)
    {
      fbuffer = (int*) realloc(fbuffer, n3 * sizeof(int));
      bufsize=n3;      
    }

      /* Utilizamos bpos para saber cuál es el último elemento del buffer */
      /* que hemos rellenado. */
      if (bpos>=n3)         /* Si se encuentra el fibonacci del número que buscamos  */
    return fbuffer[n3-1];   /* en memoria, lo devolvemos directamente */
      else
    {
      /* Si el fibonacci no se encuentra, vamos a generarlo */
      fbuffer[n3-1] = fibonacci_buffer(n-1) + fibonacci_buffer(n-2);
      bpos=n3;
      return fbuffer[n3-1];
    }

/*       return fibonacci_simple(n-1) + fibonacci_simple(n-2); */
    }
  else if (n==2)        /* Ponemos esto para agilizar un poco el proceso */
    return 1;
  else if (n==1)
    return 1;
  else if (n==0)
    return 0;
  else if (n==-1)       /* Un caso nuevo n==-1 */
    free(fbuffer);      /* Liberamos memoria */

  return -1;            /* Error */
}

Con este algoritmo, consigo generar la secuencia en 2ms. Bastante más rápido que con el primer algoritmo, parece que la puesta a 0 del buffer llevaba su tiempo.

Para concluir, quiero proponer otra solución basada en arrays, supongo que muchos estaréis más familiarizados con estos:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
int fibonacci_buffer3(int n)
{
  static int fbuffer[MAX_FIB];
  static int bufsize=0;         /* Tamaño de buffer */
  int n3;           /* n-3 */

  if (n>2)
    {
      n3=n-3;

      if (bufsize>n3)           /* Si se encuentra el fibonacci del número que buscaos  */
    return fbuffer[n3]; /* en memoria, lo devolvemos directamente */
      else
    {
      /* Si el fibonacci no se encuentra, vamos a generarlo */
      fbuffer[n3] = fibonacci_buffer(n-1) + fibonacci_buffer(n-2); /* y lo guardamos */
      bufsize=n3+1;
      /* Al ser fibonacci recursivo, y asignar el tamaño después de la generación */
      /* el buffer se rellenará por orden, es decir, el primero que se rellenará */
      /* será el [0], luego el [1], luego el [2]... */
      return fbuffer[n3];
    }

    }
  else if (n==2)        /* Ponemos esto para agilizar un poco el proceso */
    return 1;
  else if (n==1)
    return 1;
  else if (n==0)
    return 0;

  return -1;            /* Error, seguramente me hayan pasado un n negativo*/
}

Esta solución es muy parecida a las anteriores, aunque nos quitamos del medio la reserva de memoria, reducimos las líneas de código, ya que no vamos a alojar tantos valores diferentes… para esta secuencia serían unos 48 * 4 (sizeof(int)) = 192 bytes, y bufsize la utilizamos para ver cuál es el último elemento del buffer con información válida (como vimos antes, se rellenarán por orden, así que sólo tendríamos que llevar la cuenta). Las variables estáticas, como dije, podemos hacerlas globales (y quitarles el static, aunque queda más elegante como static).

Con esta solución tardo unos 5ms de media en generar la secuencia de números.

Foto: Eustaquio Santimano (Flickr)

Curioso e interesante: Google se derrumba, K-Maps, Buenhabit, y más

Jueves, 3 de Junio de 2010 Gaspar Fernández 2 comentarios

Quiero dedicar este post a cosas que me han llamado la atención estos últimos días:

  1. Google se derrumba: Lo descubrí gracias a novatillasku. Es una tontería chulísima, la página principal de Google se cae a pedazos, aunque para verlo tendréis que utilizar un navegador decente. Abstenerse Internet Explorer. Click aqui para verlo.
  2. Mapas de Karnaugh: Via Acerca de Ubuntu veo una serie de programas para resolver mapas de Karnaugh y ser el terror de la electrónica digital :)
  3. Piedra, papel, tijeras, lagarto, Spock: Últimamente estoy viendo la serie The Big Bang Theory, ya estaba tardando en empezarla, y hace poco vi este post. Pocos días antes de llegar hasta el capítulo 2×08 donde ocurre todo :)
  4. Acceder a un Array como un objeto [PHP]: El post ya tiene tiempo. Leído en el blog de Sergi Quiñonero. Nos cuenta cómo haciendo $objeto = (object) $array podemos acceder a cada elemento del array con -> (flechas) como si fuera un objeto.
  5. El sistema Solar en HTML5. Muy currado, demostrando qué es capaz de hacer HTML5, y demasiado cool para IE, aunque está gracioso ver cómo se dibujan en Internet Explorer las órbitas cuadradas :) Ya sé que tampoco son redondas, pero es una demostración sólo…
  6. Dos artículos de un blog muy interesante: Hace mucho tiempo que sigo el blog Buenhabit, y quiero destacar dos de sus últimos posts: Sólo dos cosas a la vez, que habla de una reciente investigación acerca de la limitación humana de la multitarea; y Si gritas no convences, merece la pena leerlo aunque el título lo diga todo.

Hasta aquí una pequeña selección de estos últimos días.

A los de Linares les gusta la programación

Viernes, 28 de Mayo de 2010 Gaspar Fernández 3 comentarios

04082009574 Es un pequeño chiste (muy malo, por cierto).

Vemos cómo en la pantalla, en la línea 6 de autobuses de Linares aparece Array.-Geriatric.

Categories: General Tags: , ,

Funciones que devuelven arrays

Jueves, 28 de Mayo de 2009 blakeyed 2 comentarios

Al principio, cuando empezaba a programar, yo me quejaba mucho de esa tonta manía de C que no me dejaba devolver arrays como resultado de una función.

Aunque al final, devolver arrays es una tontería, gasta memoria, gasta tiempo y termina siendo más fácil solucionarlo todo con un puntero.

Pero bueno, si realmente queremos devolver un array, siempre podemos meterlo dentro de un registro. Probando con una cadena de caracteres:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <string.h>

typedef struct
{
    char cadena[1000];
} t_array;

t_array devuelvo()
{
    t_array otroarr;
    printf("\nDirección de cadena: %X\nCadena: %s\n", &otroarr, otroarr.cadena);
    strcpy(otroarr.cadena, "ESTE STRING APARECE VARIAS VECES EN EL VOLCADO DE MEMORIA");
    printf("\nDirección de cadena: %X\nCadena: %s\n", &otroarr, otroarr.cadena);
    return otroarr;
}

int main()
{
    t_array array;
    array=devuelvo(&mem);
    printf("\nDirección de cadena: %X\nCadena: %s\n", &array, array.cadena);
}
Categories: C/C++ Tags: , ,

Visita otras webs de la red