PHP, ordenando un arreglo de Objetos, y utilizando funciones dentro de funciones.

No fue hasta que programe en Python que me habia pillado que podia definir funciones dentro de funciones en PHP. Hoy tuve que arreglar un defecto en el home de wedoit4you.com del cual algunos bloggers se estaban aprovechando para permanecer en el home. Los articulos aparecen ordenados por fecha de publicacion, y algunos estaban publicando con fechas en el futuro, inclusive abusando y poniendo fechas a fin de mes.

So, en mi clase “BlogPost”, puse un metodo “getTimestamp()”, si la fecha interna en el objeto esta en el futuro, y no es uno de los blogs a los cuales les paso la gracia…
Penalizo la fecha del post, y le resto 24 horas a la ultima hora en que se actualizo el Blog.

Pero esto no me resuelve por completo el problema puesto que los posts se leen direct tv de una tabla, y el query ordena por timestamp, el resultado de mi funcion “getPosts” es un arreglo de Objetos, y lo ideal era tener esos objetos ordenados dependiendo de la funcion “getTimestamp()” en cada objeto “BlogPost”

La foto que ven arriba muestra como utilizo la funcion “usort” para ordenar (por referencia) el arreglo resultante. La funcion recibe una referencia al arreglo de objetos, y el nombre de una funcion a la cual hacer callback al momento de comparar. La funcion de callback debe recibir 2 objetos como parametro, y luego devolver un numero que represente si el primer parametro es menor o mayor que el segundo parametro. ( < que cero si es menor, cero si son iguales, > que cero si es mayor).

En mi caso llame esta funcion “cmp”, pero no la defini afuera de la clase nisiquiera, la defini ahi mismo dentro de la funcion. Si ven la logica de mi funcion el resultado es alrevez puesto que quiero ordenar de manera decrescente.

Espero que alguien haya leido esto si necesita ordenar arreglos.

En cuanto al scoping de las funciones internas, no se que sucederia si hubiese otra funcion llamada igual en el path, supongo que el interprete busca primero en el stack, y encontraria la funcion definida dentro de la funcion actual. Imagino que al terminar de ejecutarse esta funcion, podria no estar diponible para mas nadie. Se que en python puedes hacer Clases dentro de clases, funciones dentro de funciones. En el caso de Clases dentro de Clases, puedes hacer. Objeto.SubObjeto.metodo(), en el caso de funciones dentro de funciones, creo que no tiene sentido hacer algo como Objeto.function.subFuncion(), ya que las funciones necesitan parametros, pero seria cuestion de probar a ver, quizas funciona, dado que en python todo es un objeto. Alguien que me eche el cuento para PHP.

(el screenshot fue tomado de emacs en el terminal de OSX Tiger)

3 thoughts on “PHP, ordenando un arreglo de Objetos, y utilizando funciones dentro de funciones.

  1. La forma en que estás programando el ordenamiento tendrá un problema grave de escalabilidad. Quizás ahora no se note, pero si llegas a tener muchos artículos para ordenar, seguramente te va a golpear.

    Fíjate en la función cmp($a,$b). Esa función se va a invocar por CADA par de elementos del arreglo que piensas ordenar. CADA PAR. Imagina que sólo hay tres elementos en el arreglo; habrá al menos UN elemento del arreglo que participará DOS veces en cmp($a,$x), y para ESE elemento habrás tenido que calcular $x->getTimestamp() dos veces. getTimestamp() SEGURO que es una llamada costosa, porque se trata de una llamada al sistema operativo o a una base de datos… es muy triste que la llames DOS veces para obtener el MISMO resultado. Ahora extrapola el problema a un arreglo de N elementos y te darás cuenta que es una manera muy ineficiente de hacer el ordenamiento sobre todo si consideras que en usort() SEGURO hay que intercambiar elementos del arreglo; como los elementos del arreglo seguramente son estructuras complejas, tienes un golpe grande en espacio y en tiempo. La implementación de cmp es muy inocente porque obliga a repetir llamadas muy costosas a una función que en el 99% de los casos va a retornar exactamente el mismo valor.

    Lucía lindo, pero se volvía dañino.

    La solución es hacer “dos” ordenamientos encadenados (¡ah, la programación funcional!) aplicando lo que se conoce como “Schwartzian transform” (inventada en la comunidad Perl por Randal Schwartz):

    1. Crea un nuevo arreglo de parejas S, cada pareja debe llevar:

    – Una referencia (puede ser una referencia real, o el subíndice en el arreglo original O) al elemento original.
    – El valor getTimestamp() calculado para el elemento original.

    2. Ordena el arreglo S por el segundo elemento del par.

    3. Presenta el arreglo original O utilizando las referencias como quedaron en el arreglo S.

    De este modo, getTimestamp() se calcula exactamente UNA vez por cada elemento del arreglo; y el ordenamiento se hace sobre un arreglo de elementos muchísimo más pequeño y simple.

    Si estuvieras usando Perl, la instrucción sería

    @orden = map { $_->[0] }
    sort { $a->[1] $b->[1] }
    map { [ $_, $_->getTimestamp() ] }
    @results;

  2. Saludos,

    Solo una pequeña referencia con respecto a funciones dentro de funciones, cuando creas una función dentro de otra función el alcance es solo local para esa función (al menos que sea una función propia del sistema pero ese es otro cuento), por lo que teoricamente podrías tener funciones dentro de funciones con el mismo nombre (digo teoricamente por que en la pÅ•actica nunca lo he hecho, me parece mala técnica de programación 🙂 ). Igualmente las funciones se cargan únicamente cuando son invocadas no cuando son declaradas (desde php4.algo) por lo que podrías hacer un include() de miles de funciones pero el interprete solo las carga (las interpreta) justo en el momento que alguien las llama, esto ahorra espacio en memoria y en procesador, aquellas funciones que solo fueron declaradas y nunca invocadas simplemente se descartan al finalizar el interpreté (en PHP3 y los primero PHP4 las funciones siempre se interpretaban al principio y luego se corría el resto del programa sin importar si eran o no utilizadas).
    Teoricamente no tienes acceso directo a una función dentro de una función ya que el ambito es local, pero prácticamente al declarar una función generas un Object ID el cual podrías usar para navegar dentro de la función, nunca lo he hecho ya que no tiene mucho sentido para eso es mejor hacer una clase y trabajar con las funciones dentro de las clases.
    Nunca me ha dado por ordenar arreglos (generalmente trabajo con arreglos de N tamaño con N dimensiones), generalmente para ordenar una BD es mejor, yo creo que tu mejor solución es no permitir que publiquen con fechas en el futuro y listo 🙂

  3. Ernesto! (Que honor un comentario tuyo en mi blog!)

    La llamada a $x->getTimestamp() en realidad no es nada costosa en este caso, simplemente hace

    return $this->timestamp

    que viene de un timestamp que saque hace rato de la BD, estos timestamps son anotados al momento de leer los posts cuando parseo los feeds rss.

    No me preocupo mucho por el performance en este caso, pq estoy ordenando siempre 35 elementos, no mas. Creo que mi error en todo caso es confiar demasiado en usort(), estoy asumiendo que implementaron quick sort que en teoria es O(log(n))

    Voy a investigar mas sobre ese algoritmo de ordenamiento encadenado se ve sumamente interesante.

    —-

    KOSHrf:

    Definitivamente despues de escribir el articulo, me di cuenta que el scope de la funcion existe alli nada mas, si lo ves del punto de vista de la declaracion de una funcion como si fuera una variable, al ser definida dentro de la funcion, lo mas probable es que haya un apuntador a la funcion en el stack de la funcion, al terminar de ejecutarse la funcion, se libera ese stack (hopefully)

    Y si, tienes razon, la mejor solucion es no permitir publicar con fechas a futuro, esa es la raiz del problema y es mejor poner cualquier penalizacion en el momento que leemos el post, absolutamente correcto.

    Me agrada que se tomaron el tiempo de leerme, gracias.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.