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}