Colecciones (autor Andrés Otaduy)

 

En este Workspace se presentan las clases mas emblematicas de Colecciones de la libreria de clases de Squeak en particular y Smalltalk en gral.
Al terminar de jugar en este Workspace Ud. tendra los conocimientos fundamentales del manejo de colecciones de objetos en el Ambiente Smalltalk.
Que es una Coleccion ?
Una coleccion es un objeto que se usa para guardar otros objetos, de diferentes maneras.
Para relacionarlos con conceptos de otros paradigmas podriamos menicinar algunos equivalentes, como por ejemplo los arreglos en programacion estructurada, las tablas en el modelo relacional, las pilas en logica, o las listas en procesamiento de listas.
La mayoria de las colecciones se encuentran en la jerarquia de la clase Collection.
"Evaluar la siguiente expresion para ver la jerarquia con (Alt-d)"
Collection browseHierarchy.
Para nuestra suerte existen muchos tipos de colecciones en Smalltalk, cada una de ellas apropiada para diferentes usos.
Ademas las colecciones comparten un protocolo en comun, que nos permite realizar diversas operaciones sobre ellas como por ejemplo, preguntar su tamanio, realizar una accion para cada uno de sus elementos, seleccionar una subcoleccion de elementos que respondan a algun criterio, generar una coleccion con el resultado de algun calculo para cada elemento de la coleccion original, buscar un elemento, etc. Mas tarde volveremos sobre estos temas.
"Para saber la cantidad exacta de tipos de colecciones disponibles evaluar:"
Collection allSubclasses size. "Alt-p"
Como vemos existen VARIOS tipos de Colecciones en Smalltalk
Vamos a ver algunas de las mas utilizadas en el sistema.

Array.
Interval.
OrderedCollection.
SortedCollection.
Set.
Bag.
Dictionary.

1) Array
Los objetos Array son colecciones de lonigtud fija que pueden contener cualquier tipo de objeto, y que los mantienen indexados por un numero entero, comenzando por el uno . Los arrays ademas pueden ser creados de manera literal, si solo contienen objetos literales.
Ej:
Array literal:
#( 1 2 'unArray' #pepe $&).
Creacion del mismo Array programaticamente.
|unArray|
unArray _ Array new: 5.
unArray at: 1 put: 1;
at:2 put: 2;
at: 3 put: 'unArray';
at: 4 put: #pepe;
at: 5 put: $&.
unArray . "Alt-p"
Si intentamos acceder a un elemento que no este dentro de los limites del array este se quejara con un error
Ej:
#('pepe' 'juan' Squeak) at: 4. "Alt-d"

2) Interval
Interval es una coleccion que guarda un intervalo de numeros con un limite superior uno inferior y un paso.
"Creacion de un Interval programaticamente"
|unInterval|
unInterval _ Interval from: 10 to: 20 by: 0.1.
unInterval size. "Alt-p"

"Algunas alternativas"
|unInterval|
unInterval _ 10 to: 20 by: 0.1.
unInterval size. "Alt-p"

|unInterval|
unInterval _ Interval from: 20 to: 10 by: -0.5.
unInterval size. "Alt-p"
3) OrderedCollection
Una OrderedCollection es una coleccion que tiene un orden, o secuencia, pero este orden responde solo al orden en que se agregaron y quitaron los elementos, ademas la OrderedCollection no tiene limites de tamanio, simplemente crecera, o se achicara tanto como lo necesitemos. Mantiene a sus elementos indexados por enteros comenzando desde el 1 al Igual que Array. Eso si si le pedimos un elemento que no tiene tambien se queja.
"Creacion de una OrderedCollection programaticamente"
|unaOrderedCollection|
unaOrderedCollection _ OrderedCollection new.
unaOrderedCollection add: 1 ;
add: 2 ;
add: 'Squeak' ;
add: #Smalltalk ;
add: $%.
unaOrderedCollection . "Alt-p, o Alt-i ".
4) SortedCollection
Una SortedCollection es analoga a la OrderedCollection con la excepcion de que el orden de la SortedCollection esta determinado por un criterio definido por nosotros, llamado sortBlock. El bloque de ordenacion por defecto es de menor a mayor.
Ej:
|unaSortedCollection|
unaSortedCollection _ SortedCollection new.
unaSortedCollection add: 'Objectos' ;
add: 'Squeak' ;
add: 'SqueakRos' ;
add: 'Smalltalk' ;
add: 'SortedCollection';
add: 'Bag';
add:'Array'.
unaSortedCollection ."Alt-p o Alt-i ".

"Otro ejemplo ordenando por la segunda letra de cada palabra"
Ej:
|unaSortedCollection|
unaSortedCollection _ SortedCollection sortBlock:[:a :b| a second < b second] .
unaSortedCollection add: 'Objectos' ;
add: 'Squeak' ;
add: 'SqueakRos' ;
add: 'Smalltalk' ;
add: 'SortedCollection';
add: 'Bag';
add:'Array'.
unaSortedCollection . "Alt-p, o Alt-i ".
Vale decir que dentro del sortBlock, podemos poner cualquier comparacion que se nos ocurra, a experimentar !!
5) Set
Un Set es como un conjunto matematico, no tiene ningun orden, y no se admiten elementos repetidos, ademas no tiene indexados sus elementos, crecera y se achicara de acuerdo nuestras necesidades como la Ordered y SortedCollection.
|unSet|
unSet _ Set new.
unSet add: 1 ;
add: 2;
add: 'Squeak' ;
add: #Smalltalk ;
add: $%;
add: $%;
add: 'Squeak' ;
add: 'Pepe';
remove: #Smalltalk.
unSet . "Alt-p, o Alt-i ".

7) Bag
Un Bag, del ingles bolsa, es un contenedor de objetos que crece y decrece de tamanio a voluntad como los Set y demases, pero esta optimizada para guardar elementos repetidos y contar las ocurrencias de alguno de estos, como las bolsas no mantiene ningun orden, los elementos se mezclan al entrar en ella.
|unBag|
unBag _ Bag new.
unBag add: 1 ;
add: 2;
add: 'Squeak' ;
add: #Smalltalk ;
add: $%;
add: $%;
add: 'Squeak' ;
add: 'Pepe';
remove: #Smalltalk.
unBag inspect ; occurrencesOf: 'Squeak' . "Alt-p ".
8) Dictionary
un Dictionary es una coleccion que mantiene a sus elementos indexados por cualquier otro objeto, manteniendo pares de claves (keys) y valores (values), como las tablas en el modelo relacional. La diferencia es que la clave es un unico objeto y este no puede estar repetido en el mismo Dictionary, si se agrega un elemento con la misma clave se sobreescribira sobre el elemento anterior.
|unDictionary|
unDictionary _ unDictionary new: 5.
unDictionary at: 'juan' put: 1;
at:'pepe' put: 2;
at: 10000 put: 'unArray';
at: #200 put: #pepe;
at: $c put: $&.
unDictionary . "Alt-p".

"Podemos pedirle las claves"
|unDictionary|
unDictionary _ unDictionary new: 5.
unDictionary at: 'juan' put: 1;
at:'pepe' put: 2;
at: 10000 put: 'unArray';
at: #200 put: #pepe;
at: $c put: $&.
unDictionary keys. "Alt-p"
"O los valores"
|unDictionary|
unDictionary _ unDictionary new: 5.
unDictionary at: 'juan' put: 1;
at:'pepe' put: 2;
at: 10000 put: 'unArray';
at: #200 put: #pepe;
at: $c put: $&.
unDictionary values. "Alt-p"
"el elemento correspondiente a una clave"
|unDictionary|
unDictionary _ unDictionary new: 5.
unDictionary at: 'juan' put: 1;
at:'pepe' put: 2;
at: 10000 put: 'unArray';
at: #200 put: #pepe;
at: $c put: $&.
unDictionary at:'pepe'. "Alt-p"
Los diccionarios se usan muchisimo en Smalltalk, con decir que el mismisimo Smalltalk es un Dictionary un poco especial, sino proba de evaluar los siguiente:
Smalltalk class "Alt-p".
o mejor.
Smalltalk "Alt-i".

Bueno hasta aca todo muy lindo, tenemos un monton de colecciones para complicarnos la vida a la hora de elejir cual vamos a usar para guardar nuestros objetos preferidos.
Pero cuales eran esas cosas que se podian hacer con cualquiera de las colecciones ??!.
Bueno, de primera cualquier coleccion de las que nombramos se puede convertir en cualquiera de las otras (con excepcion de Dictionary), haciendo que la nueva coleccion cumpla con la definicion de la coleccion que nosotros queramos.
por ejemplo:
"Array -> OrderedCollection"
#( 1 2 3 'pepe' Squeak $p) asOrderedCollection add: 'Juan' ; yourself "Alt-p o Alt-i"
"Array -> Set -> SortedCollection "
#( 110000 1 1 9999 1 1 1 1 1 2 2 2 2 2 2 2 5 5 5 5 5 5 6 54 33 6 4 ) asSet asSortedCollection: [:a :b | a>b]. "Alt-p o Alt-i"

"OrderedCollection -> Bag -> OrderedCollection"
|unaOrderedCollection|
unaOrderedCollection _ OrderedCollection new.
unaOrderedCollection add: 1 ;
add: #Smalltalk ;
add: 2 ;
add: #Smalltalk ;
add: #1 ;
add: #Smalltalk ;
add: $%.
unaOrderedCollection asBag asOrderedCollection. "Alt-p o Alt-i"
"Ojo,que a veces la conversion conlleva una perdida de informacion"
"Interval ->Array"
(1 to: 30 by: 0.1) asArray . "Alt-p o Alt-i"
"Etcetera -> Etcetera ! :-) " Ademas de las conversiones hay mas operaciones interesantes que se pueden realizar sobre las colecciones.
Algunas preguntas comunes podrian ser.

Si esta vacia:
#( 1 2 3) isEmpty. "Alt-p"
Set new isEmpty "Alt-p"
El tamanio:

#( 1 1 1 1 1 ) asSet size. "Alt-p"

Si incluye algun elemento:
|unBag|
unBag := #( 'Andres' 'Martin' 'Xavier' 'Alejandro' 'Diego') asBag .
unBag remove:'Andres'.
unBag includes: 'Andres'. "Alt-p"
Ademas existe una manera comun de enumerar a todas las colecciones.
1) El #do:, con este mensaje nos despedimos definitivamente de los viejos "para i _ 1 a 20". EL mensaje #do: esta implementado para todas (ahora si ) las colecciones que son subclases de Collection. Lo que hace este mensaje es evaluar un bloque para cada uno de los elementos de la coleccion que recibe el mensaje en el orden que define la coleccion (si es que lo define).
Vamos con unos ejemplos:
si queremos inspeccionar cada uno de los elementos de un Array por ejemplo podriamos hacer lo siguiente:
#( 9999999 'pepe' 'juan' ) do:[:unElemento | unElemento inspect]. "Alt-d".
" o si queremos agregar a mano los elementos de 2 colecciones a un Set para eliminar repetidos y ya que estamos ordenarlos."
|coleccion1 coleccion2 unSet|
coleccion1 _ #( 'Martin' 'Julian' 'Andres' 'Pedro').
coleccion2 _ #( 'Julian' 'Pepe' 'Andres' 'Pablo' 'Javier').
unSet _ Set new.
coleccion1 do:[:unString | unSet add: unString].
coleccion2 do:[:unString | unSet add: unString].
unSet asSortedCollection. "Alt-p"
Podemos anidar los #do: para obtener mas poder !!
|suma|
suma _ 0.
#( (1 2 3)
(4 5 6 7)
( 1 98) ) do:[:unaFila |
unaFila do:[:unElemento | suma _ suma + unElemento ]
].
suma. "Alt-p"
"Tener en cuenta que la matriz tiene filas de distintos tamanios! con unos 'para' como harias ?!?"

"Y si la coleccion esta vacia?"
#() do:[:i |i inspect ] . "Alt-p"
"Y el #do:, que de paso es muy inteligente no hace nada, asi que nada de probar si la coleccion esta vacia antes de hacerle cosas a sus elementos"
2) Despues viene el caso de las consultas, es muy comun tener que seleccionar ciertos elementos de una coleccion por algun criterio determinado, para despues hacer algo con estos objetos, para esto contamos con el inavluable #select:, este mensaje esta implementado en terminos del #do: con lo cual
esta disponible para todas las colecciones. El mensaje #select: devuelve una coleccion que suele ser del mismo tipo que la colecicon que recibe el mensaje pero que solo contiene los elementos que cumplen con el criterio espec ificado en el bloque que se le pasa como parametro.
A esta altura ya sabemos como seleccionar elementos de una coleccion, lo podriamos hacer usando simplemente el mensaje #do: de la siguiente forma
|unaColeccion coleccionFiltrada|
unaColeccion _ OrderedCollection withAll: #( 7 8 9 5 4 67 2 65 -1 -10).
"#withAll: devuelve una coleccion con los elementos pasados como parametro"
coleccionFiltrada _ OrderedCollection new.
unaColeccion do:[:unElemento | unElemento > 7 ifTrue:[ coleccionFiltrada add: unElemento] ].
coleccionFiltrada. "Alt-p"

De la manera anterior logramos filtrar una coleccion de una manera bastante simple, ahora examinemos como seria con el #select:
|unaColeccion |
unaColeccion _ OrderedCollection withAll: #( 7 8 9 5 4 67 2 65 -1 -10).
unaColeccion select:[:unElemento | unElemento > 7 ]. "Alt-p"
Bastante mas simple eh!
Vale decir que en el bloque de criterio podemos incluir cualquier cosa, veamos:
"Selecciona los nombres que empiezan y terminan con vocal"
|unaColeccion|
unaColeccion _ #( 'Andres' 'Martin' 'Cintia' 'Xavier' 'Irina' 'Alejandro' 'Diego' 'Ana' 'Ema' ).
unaColeccion select:[:unString| unString first isVowel and:[ unString last isVowel ] ]. "Alt-p"

Otro ejemplo, ahora de mientras calculamos el tamanio promedio de los nombres.
|unaColeccion tamanio|
unaColeccion _ #( 'Andres' 'Martin' 'Cintia' 'Xavier' 'Irina' 'Alejandro' 'Diego' 'Ana' 'Ema' ).
(unaColeccion select:[:unString| tamanio _ tamanio + unString size.
unString first isVowel and:[ unString last isVowel ] ]) inspect.
"El valor que devuelve el bloque es el de la ultima sentencia dentro de el"
tamanio/ unaColeccion size . "Alt-p"

3) Habiamos hablado antes de generar una coleccion paralela con el resultado de algun calculo para cada elemento de la coleccion. Esto se puede lograr facilmente con el mensaje #collect: este mensaje tambien esta basado en el #do:, con lo cual esta disponible para todas las colecciones, inclusive si nosotros creamos alguna nueva, solo tendriamos que implementar el #do: para nuestra clase y el #collect: y #select: vendrian gratis !
Bueno vamos con los ejemplos que son lo mas divertido!!
"Los cuadrados del 1 al 30"
| unaColeccion |
unaColeccion _ Interval from: 1 to: 30.
unaColeccion collect:[:unNumber | unNumber squared] . "Alt - p".

"Los factoriales del 1 al 30"
| unaColeccion |
unaColeccion _ Interval from: 1 to: 30.
unaColeccion collect:[:unNumber | unNumber factorial] . "Alt - p".

"Una prueba de que los factoriales calculan con precision total"
| unaColeccion |
unaColeccion _ Interval from: 1 to: 100.
unaColeccion collect:[:unNumber | unNumber factorial / (unNumber -1) factorial] . "Alt - p".

"Podemos anidar los #collect:, para obtener las tablas de multiplicar"
| unaColeccion |
unaColeccion _ Interval from: 1 to: 9.
unaColeccion collect:[:unNumber |
unNumber ->
(unaColeccion collect:[:otroNumber | unNumber * otroNumber ])
]. "Alt - p".
"Nota: el mensaje #-> crea un objeto Association, con el receptor como clave (key) y el parametro como valor (value) "
4) Una cuestion comun es la busqueda de algun elemento por algun criterio, esta puede hacerse usando el mensaje #detect: o #detect:ifNone:, este mensaje devuelve el primer elemento que encuentre que cumple con el criterio especificado y si no encuentra ninguno genera un Error, la variante #detect:ifNone: permite proporcionar un bloque que se ejecutara en el caso de que no se encuentre nungun elemento.
Ejemplos:
"Devuelve el primer nombre que empiece con consonante y que termine en la letra $a"
| unaColeccion |
unaColeccion _ #( 'Pepe' 'Jose' 'Javier' 'Pedro' 'Ana' 'Maria' 'Beatriz' 'Adriana' ).
unaColeccion detect:[:unString |
unString first isVowel not and:[ unString last = $a ]
].
| unaColeccion |
unaColeccion _ -100 to: 100 by: (0.1).
unaColeccion detect:[:unNumber |
unNumber > 0
].
"Devuelve true si la matriz es probabilistica (todas sus filas suman 1) "
| unaMatriz suma esProbabilistica|
unaMatriz _ #( ( 0.1 0.6 0.3)
( 0.2 0.1 0.7)
( 0.3 0.3 0.4) ).
esProbabilistica _ false.
"Detectamos la excepcion a la regla"
unaMatriz detect:[:unaFila |
suma _ 0.
unaFila do:[:unElemento | suma _ suma + unElemento ].
suma ~= 1 ]
ifNone:[ esProbabilistica _ true].
esProbabilistica. "Alt - p""Modificando un poco el ejemplo antrerior podriamos saber cual no cumple con la condicion "
| unaMatriz suma esProbabilistica filaMal|
unaMatriz _ #( ( 0.1 0.6 0.3)
( 0.2 0.2 0.7)
( 0.3 0.3 0.4) ).
esProbabilistica _ false.
"Detectamos la excepcion a la regla"
filaMal _ unaMatriz detect:[:unaFila |
suma _ 0.
unaFila do:[:unElemento | suma _ suma + unElemento ].
suma ~= 1 ]
ifNone:[ esProbabilistica _ true].
esProbabilistica ifFalse:[ filaMal inspect].
esProbabilistica "Alt - p"
" El #detect:ifNone: puede usarse para devolver un objeto por defecto en caso de no encontrarse lo que buscamos"
| unaColeccion nombreClase|
unaColeccion _ Bag withAll: #( Collection Integer Character Date Array ).
nombreClase _unaColeccion detect:[:nombreDeClase | 1.0 class name = elemento ] ifNone:[ #Object].
nombreClase. "Alt-p"
5) Es muy comun tambien tener que hacer calculos que sean la acumulacion del resultado de un mensaje en particular para los elementos de una coleccion, para esto se utiliza comunmente el mensaje #inject:into. Este mensaje se le envia a la coleccion proporcionando el valor inicial del acumulador y la operacion a acumular, una forma general del envio del mesaje seria algo asi:
unaColeccion inject: valorInicial into:[:acumulador :elemento | ].
para el primer elemento de la coleccion la variable 'acumulador' del bloque tomara el valor de 'valorInicial' y 'elemento' contendra el primer elemento de la coleccion.
En las sucesivas iteraciones, 'acumulador' tomara el valor del resultado del bloque y 'elemento' el elemento correspondiente a esa iteracion.
Un ejemplo:
" La sumatoria de los elementos de un arreglo"
#( 9 8 7 5 6 4 3 1 2) inject: 0 into:[:suma :elemento | suma + elemento ] "Alt -p".
" La productoria de los elementos de un arreglo"
#( 9 8 7 5 6 4 3 1 2) inject: 1 into:[:suma :elemento | suma * elemento ] "Alt -p".

"EL promedio"
|unArreglo|
unArreglo_ #( 9 8 7 5 6 4 3 1 2) .
(unArreglo inject: 1 into:[:suma :elemento | suma + elemento ]) / unArreglo size "Alt -p".
" Este mensaje es muy practico ya que suele simplificar los metodos de calculo eliminando la necesidad de la variable temporal de acumulacion"
"Unos ejemplos mas en General"
"Obtener la frecuencia de cada uno de los caracteres de un String"
| unString unBag resultado |
unString_ ' Vamos a probar cuantas veces aparece cada letra en esta cadena, de paso vamos a usar las cosas que aprendimos hasta ahora'.
unBag _unString asBag.
resultado _ (unString asSet collect:[:unCaracter | unCaracter -> (unBag occurrencesOf: unCaracter) ]) asSortedCollection:[:a :b| a value > b value] . "Alt-p"
" Estamos usando, conversion de coleccion y #collect: "
"Interseccion de 2 colecciones"
|coleccion1 coleccion2|
coleccion1 _ 'Hola que tal '.
coleccion2 _ 'A mi bien y a vos que tal'.
(coleccion1 select:[:unCaracter | coleccion2 includes: unCaracter ]) asSet.
" Estamos usando, conversion de coleccion y #select:: "
"Aca tenemos que poner mas ejemplos"

Las Colecciones que acabamos de ver tienen ademas un protocolo mas extenso dependiendo a la familia a la que pertenezcan, cada coleccion puede pertenecer a mas de una familia
Colecciones Secuenciables
Interval
Array 
OrderedCollection
SortedCollection
Estas colecciones tienen a sus elementes ordenados de acuerdo con una determinada secuencia, se les puede pedir el primer elemento, el ultimo, el elemento x, que reemplacen el elemento de la posicion x por el elemento y, y demas cuestiones de acuerdo a su secuencia.
Mensajes interesantes :
#first, #last #at:put:, #before, #after, #, (la coma es la concatenacion), #copyFrom:to:, atRandom, #indexOf: etc.

Colecciones de Tamanio Variable.
Set
Dictionary
OrderedCollection
SortedCollection
Bag
Estas colecciones permiten agregar y quitar elementos libremente, si ademas son Colecciones Secuenciables permiten agregar despues de o antes de un elemento.
Mensajes interesantes:
#add: #remove: #addFirst: #addLast: #removeFirst #addAll: #removeAll: #add:withOccurrences:, etc
Podrian clasificarse de otras maneras, pero con esto tenemos los conocimientos basicos como para browsear la jerarquia sin tener demasiados problemas.{MiTexto}

 


1