"Cosa" de Negro
marzo 21, 2005
 
Optimización V - FOR EACH
Acelerando los recorridos sobre un array.

Es muy común que tengamos que recorrer arrays para hacer diferentes tipos de tareas, buscar, imprimir, copiar, modificar masivamente, etc.

Además de poder hacerlo con un AEVAL(), podemos usar un bucle FOR-NEXT o un bucle DO WHILE-ENDDO.

En xHarbour se agrega un nuevo bucle exclusivo para la tarea de recorrer arrays, el FOR EACH-NEXT.

Veremos más adelante que este bucle tiene algunas particularidades muy interesantes que son su punto fuerte y razón de ser.

La sintáxis de uso es la siguiente:

FOR EACH _var_ IN _array_
[LOOP]
[HB_ENUMINDEX()]
[EXIT]
NEXT

_array_ puede ser cualquier expresión que retorne un array, un objeto o una cadena de caracteres.

_var_ puede ser cualquier variable previamente declarada, pero es recomendable que sea una variable local.
Esta variable va a apuntar a una posición del array, comenzando por la primera posición.

El FOR EACH va a recorrer todo el array completamente, a menos que se fuerze la salida anticipada con el comando EXIT.
También es posible usar el comando LOOP de la misma forma que se usa en el comando FOR-NEXT.

La pseudo-función HB_ENUMINDEX() retorna el número de posición del array que se está procesando en ese momento.

Funcionamiento:

El comando FOR EACH recorre todo el array _array_ y en cada iteración guarda en la variable _var_ una referencia a la posición actual del array.

Al finalizar el bucle FOR EACH, la variable _var_ queda con valor NIL, asi que si necesitamos preservar el valor de dicha variable para usarlo fuera del bucle, debemos copiarla a otra variable antes de salir.

Qué es eso de que la variable es una referencia ?

La variable del FOR EACH funciona de forma similar a los parámetros de una función que reciben los datos por referencia.

Es como si fuera algo así: (la siguiente sintáxis es de ejemplo, no es válida)

_var_ := @_array_[n]

Qué ventajas tiene ?

La ventaja de tener en _var_ una referencia a la posición del array es que para modificar la posición del array, solo es necesario asignar el dato que queremos a la variable _var_, y lo que realmente estaremos haciendo, es modificar el contenido del array.

Donde aplicarlo?

Este comando está especialmente diseñado para recorrer arrays rápidamente y poder usar o modificar su contenido también de la forma más rápida posible.

Todas los bucles que recorren un array pueden beneficiarse con el uso de For each.
Cualquier construcción del tipo:

for n:=1 to Len(aArray)
...
next

puede ser convertida a

for each x in aArray
...
next

Los que usan Fivewin, encontrarán muchas contrucciones que pueden optimizarse usando FOR EACH, en muchos de los fuentes del sistema.
También en otras clases como TSBrowse el desempeño mejora notablemente al usar FOR EACH.

Encontrarán ejemplos y pruebas aquí: foreach.prg objlist.prg ivarref.prg

Los siguientes ejemplos producen el mismo resultado, pero con tiempos de ejecución diferentes.
El for-next y el aeval, tardan aproximadamente lo mismo, pero el for each demora la mitad de tiempo.

aArray := arrray(500000)
nLen := Len(aArray)

for n:=1 to nLen
...aArray[n] := n
next

for n:=1 to nLen
...nSum += aArrray[n]
next

------------------------

aeval( aArray, {|a,n| aArray[n]:=n} )
aeval( aArray, {|a,n| nSum += a} )

------------------------

for each x in aArray
...x := HB_EnumIndex()
next

for each x in aArray
...nSum += x
next
------------------------
Comments:
Walter,

For Each...Next es una excelente extensión de xHarbour.

Sería interesante si también tuviera su equivalente en función, como For...Next tiene a AEval(), ya que no siempre será factible reemplazar For...Next por For Each...Next.

Un AFEEval() o similar sería estupendo para redondar esta extensión de harbour

Luis Krause
 
En realidad AEval() ya hace algo parecido al For each, porque el valor pasado al codeblock, es el dato de la posición del array.
Lo único que faltría para funcionar de una forma similar al For each, es que este dato se pasara por referencia, en lugar de pasarse por valor.
Esto sería fácilmente solucionable con un parámetro extra de la función que envíe el parámetro por referencia.

Sin embargo, nunca se obtendrá la velocidad del For each, ya que ejecutar un codeblock tiene la misma sobrecarga de trabajo que una llamada a una función y

afeeval(b,{|a| qout(a) })

sería algo como:

for each a in b
mostrar(@a)
next

func mostrar(a)
qout(a)
return
 
Walter,

Un parámetro adicional para AEval() sería estupendo, ya que como bien lo dijiste, si comparas la velocidad entre AEval(), For..Next y For Each..Next, los 2 primeros dan resultados casí idénticos, mientras que el último lo hace usualmente en la mitad del tiempo.

Esto inclusive simplificaría la migración y/o compatibilidad del código con Clipper ya que el parámetro adicional a AEval() sería ignorado por Clipper pero aprovechado por xHarbour, además de evitar el tener que reescribir mucho código para crear una función estática que haga uso de For Each..Next para reemplazar al AEval(). Sería como bien dices, no tan rápido, pero ciertamente más rápido que como funcione actualmente.

Luis Krause
 
Luis:

El parámetro adicional en AEval permitiría pasar al codeblock el parámetro por referencia y poder hacer modificaciones directas sobre el array,
sin tener que tener el array "detached".

Suponiendo que en Clipper compile con un parámetro extra (algunas funciones tienen control de la cantidad de parámetros en tiempo de compilación), no podrías usar el mismo codeblock para Clipper y xHarbour.

Ganarías tiempo respecto del AEval() en modificaciones (en otros usos tendrías la misma velocidad), pero igualmente tendrías la sobrecarga de trabajo de la ejecución del codeblock.
For each siempre sería más rápido.

En un código como el siguiente podrías ganar en velocidad

Local aArray := {1,2,3,4,5}
#ifdef __XHARBOUR__
bBlock := {|n| n++ }
#else
bBlock := {|n,s| aArray[s]++ }
#endif

AEval(aArray,bBlock,,,.t.)
 
Hola, me parece que seria bueno que FOR EACH pudiera recorrer solo parte del arreglo como lo hace aEval( aDatos, bCodigo, 250, 1000 ) por ejemplo.

o no se si ya lo haga

Gracias

Oscar
 
Publicar un comentario

<< Home

Powered by Blogger