From emhn at telcel.net.ve Thu Jan 4 07:19:21 2001 From: emhn at telcel.net.ve (Ernesto Hernandez-Novich) Date: Wed Aug 4 23:59:21 2004 Subject: Ordenando e-mails (Ejemplo de SORT) Message-ID: Una pregunta frecuente es ?c?mo usar sort pero que _no_ sea para un orden num?rico ni alfanum?rico puro? La funci?n sort() de Perl ordena por defecto en forma _alfanum?rica_ ascendente, pero en ocasiones es necesario un orden diferente por cualesquiera otro criterio, en particular cuando uno trabaja con estructuras de datos complejas. Adem?s, no tiene _ning?n_ sentido implementar mysort() nuevamente, porque el sort de Perl utiliza el algoritmo QuickSort (el m?s r?pido que existe :-), de manera que cualquier otro que uno implemente ser?a m?s lento... Para este ejemplo, presento un caso concreto: ordenar una lista arbitraria de direcciones de correo en orden de _dominio_, y luego alfab?ticamente por usuario. Es decir, si tengo una lista como x@aqui.net b@dominio.com a@dominio.com c@otro.sitio.aqui.net d@uno.mas.ve Se quiere un resultado como d@uno.mas.ve a@dominio.com b@dominio.com x@aqui.net c@otro.sitio.aqui.net N?tese que el punto no es ordenar _alfab?ticamente_ los dominios, sino que todos los dominios de igual nombre esten _juntos_ y los subdominios aparezcan despu?s de su dominio principal. En el problema concreto que se quer?a resolver, la lista de correos se distribuye _much?simo_ m?s r?pido si se tiene este orden previamente; por supuesto, si se quiere ordenar los dominios alfab?ticamente se puede (se deja de ejercicio para el lector :-). (Los lectores observadores se dar?n cuenta que los dominios est?n ordenados alfanum?ricamente de "derecha a izquierda"). Este es el programa para solucionarlo: #!/usr/bin/perl # Rutina para ordenar por dominio. La rutina ser? invocada desde sort, # por lo tanto debe operar con dos variables $a y $b, retornando # 0 si son iguales, -1 si $a es menor que $b o 1 si $a es mayor que $b. sub bydomain () { my ($au,$ad) = split /@/,$a; # Se separa usuario de dominio my ($bu,$bd) = split /@/,$b; # Invierto los dominios y los comparo. # Si son _diferentes_ $ret ser? 1 o -1, y lo retorno directamente # para que sort contin?e trabajando. Como he comparado seg?n los # dominios invertidos dom.com y sub.dom.com realmente son # comparados como moc.mod y moc.mod.bus, por lo tanto los # subdominios siempre son _mayores_ que sus dominios padres y quedan # en el orden deseado ;-) if ($ret = (reverse($ad) cmp reverse($bd))) { return $ret; } else { # Pero si son iguales, debo ordenar los nombres de usuario return ($au cmp $bu); } } @all = <>; # Leo _todo_ en un arreglo, un e-mail por elemento map { chomp } @all; # Le quitamos el \n a todas las l?neas del arreglo map { $_ = ~ tr/A-Z/a-z/; # Ponemos todas las l?neas en min?scula @all = sort bydoman @all; # Ordeno el arreglo "por dominio" usando el # sort de Perl pero mi funci?n de comparaci?n. # Ya lo que queda es trabajar con la lista ordenada foreach $email (@all) { print "$email\n"; } Ciertamente, mientras mayor sea la lista m?s memoria consume porque la estoy cargando de una sola vez en memoria. Si quisiera hacer el ordenamiento sobre algo que no cabe en memoria RAM, no me queda m?s remedio que usar archivos parciales, cargarlos en memoria, ordenarlos, y luego usar un merge sort para combinar los archivos en uno final. Para el caso concreto que hab?a que resolver (una docena de listas, la m?s larga con 600+ elementos) el consumo era irrisorio. Incidentalmente, el uso de map es _much?simo_ m?s eficiente que usar un for para iterar sobre la lista y aplicar los cambios a cada uno (esto es heredado de LISP y ML); es lo que se llama un "iterador impl?cito" y es una construcci?n esencial en la programaci?n funcional. Les invito a comparar ambos map con unos simples for/foreach para que vean la diferencia. El uso de funciones de ordenamiento agrega una incre?ble flexibilidad al momento de manipular estructuras de datos complejas. Supongan que tienen una lista de referencias a hashes, y que en cada hash tienen una clave "Edad" y desean ordenar seg?n edades... entonces la funcion para ordenar recibe dos variables $a y $b que son _referencias_ a hashes: sub byage () { return ($a->{Edad} <=> $b->{Edad}); } Y si @personas es el arreglo de referencias a hashes, ordenarlo por edades es tan simple como @personas = sort byage @personas; Trivial ;-) Supongan ahora que el hash tiene varias claves num?ricas (Edad, Sueldo, etc.) y que tienen que ordenarlo a cada rato por una clave diferente. La soluci?n inocente es tener _varias_ funciones de ordenamiento separadas... sin embargo, podemos aprovechar el lenguaje de la siguiente forma: sub nicehack ($$) { my ($clave,$refarreglo) = @_; return ( sort { return ($a->{$clave} <=> $b->{$clave} ) } @$refarreglo ); } @poredad = nicehack('Edad',\@personas); @porsueldo = nicehack('Sueldo',\@personas); M?s detalles del uso de sort en man perlfunc. -- Ernesto Hern?ndez-Novich - Running Linux 2.2.18 i686 - Unix: Live free or die! -----BEGIN GEEK CODE BLOCK----- Version: 3.1 GCS/E d+(++) s+: a C+++$ UBLAVHIOSC*++++$ P++++$ L+++$ E- W+ N++ o K++ w--- O- M- V PS+ PE Y+ PGP>++ t+ 5 X+ R* tv+ b++ DI+++$ D++ G++ e++ h r++ y+ -----END GEEK CODE BLOCK----- ------------------------------------------------------------------------ Enviar e-mail a colocando en el cuerpo: "UNSUBSCRIBE caracas-pm-list" para desuscribirse. "INFO caracas-pm-list" para conocer las reglas de etiqueta. ------------------------------------------------------------------------