4.3 Diversión con colecciones

Una colección es un conjunto de objetos. Los arrays y las listas son colecciones. Ya sabemos que una String es una colección; concretamente, una colección de caracteres. Muchos tipos de colecciones tienen comportamientos similares.

Un Array es una colección de tamaño fijo y, a diferencia de una cadena, puede contener cualquier tipo de literal entre #( ):

"array of numbers"
#(1 3 5 7 11 1.1)
"array of mixed literals"
#(1 'friend' $& 'al')

Un Array se construye directamente utilizando elementos literales bien formados. Llegaremos al significado de esta última afirmación cuando discutamos los detalles del lenguaje Smalltalk.

Por ahora, sólo observa cómo usando expresiones no literales al construir un array no funcionará como esperas:

#(1 2/3)
⇒ #(1 2 #/ 3)

De hecho, el símbolo $/ se interpreta como un símbolo literal y obtenemos los componentes básicos de «2 / 3», pero este texto no se interpreta como una fracción. Para insertar una fracción en la matriz, se utiliza un array en tiempo de ejecución o un array dinámico, cuyos elementos son expresiones separadas por puntos y rodeadas por { }:

{1 . 2/3 . 7.5}
⇒ #(1 2/3 7.5)

Con un array relleno de números puedes obtener información y operaciones aritméticas:

#(1 2 3 4) size ⇒ 4
#(1 2 3 4) + 10 ⇒ #(11 12 13 14)
#(1 2 3 4) / 10 ⇒ #(1/10 1/5 3/10 2/5)

Las operaciones matemáticas también funcionan:

#(1 2 3 4) squared ⇒ #(1 4 9 16)
#(0 30 45 60) degreeCos
⇒ #(1.0 0.8660254037844386
0.7071067811865475 0.49999999999999994)

También se pueden utilizar directamente métodos estadísticos básicos en arrays de números:

#(7.5 3.5 8.9) mean ⇒ 6.633333333333333 
#(7.5 3.5 8.9) range ⇒ 5.4
#(7.5 3.5 8.9) min ⇒ 3.5
#(7.5 3.5 8.9) max ⇒ 8.9

Para obtener un array con los números naturales desde el 1 al 100, utilizaremos el mensaje #to:

(1 to: 100) asArray
⇒ #(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
92 93 94 95 96 97 98 99 100)

En esta línea de código, el mensaje #to: se envía a 1 con el argumento 100. Devuelve un objeto intervalo. El mensaje #asArray enviado al intervalo devuelve un array.

 CuisLogo Crea un array con los números enteros con un rango de -80 a 50.

Ejercicio 4.2: Números negativos enteros

El tamaño de un array es fijo, no puede crecer. Una OrderedCollection es una colección dinámica y ordenada. Crece cuando se añade un elemento con el mensaje #add:

| fibo | 
fibo := OrderedCollection newFrom: #(1 1 2 3).
fibo add: 5;
   add: 8;
   add: 13;
   add: 21.
fibo
⇒ an OrderedCollection(1 1 2 3 5 8 13 21)

Ejemplo 4.1: Colección de tamaño dinámico

El acceso al índice de los elementos de una colección se realiza mediante diversos mensajes. El índice abarca naturalmente desde 1 hasta el tamaño de la colección:

fibo at: 1 ⇒ 1
fibo at: 6 ⇒ 5
fibo last ⇒ 21
fibo indexOf: 2 ⇒ 3
fibo at: fibo size ⇒ 21

Jugando con enumeradores

Una colección incluye un conjunto de métodos útiles denominados enumeradores. Los enumeradores operan sobre cada elemento de una colección.

Las operaciones de conjunto entre dos colecciones se calculan con los mensajes #union:, #intersection: y #difference:.

#(1 2 3 4 5) intersection: #(3 4 5 6 7)
⇒ #(3 4 5)
#(1 2 3 4 5) union: #(3 4 5 6 7)
⇒ a Set(5 4 3 2 7 1 6) 
#(1 2 3 4 5) difference: #(3 4 5 6 7)
⇒ #(1 2)

Ejemplo 4.2: Operaciones de conjunto

 CuisLogo Construye la matriz de los números 1,...,24,76,...,100.

Ejercicio 4.3: Agujero en un conjunto

Las operaciones de conjunto funcionan con cualquier tipo de objeto. La comparación de objetos merece una sección aparte.

#(1 2 3 'e' 5) intersection: #(3.0 4 6 7 'e')
⇒ #(3 'e')

Para seleccionar los números primos del 1 al 100, utilizamos el enumerador #select:. Este mensaje se envía a una colección y, a continuación, selecciona cada elemento de la colección que devuelve verdadero a una condición de prueba:

(1 to: 100) select: [ :n | n isPrime ]
⇒  #(2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71
73 79 83 89 97)

Ejemplo 4.3: Seleccionar los números primos entre el 1 y 100

Este ejemplo introduce el mensaje #select: y un bloque de código, un elemento constitutivo primordial del modelo Cuis-Smalltalk. Un bloque de código, delimitado por corchetes, es una parte del código para su posterior ejecución. Expliquemos cómo se evalúa este script:

Un bloque de código se puede guardar en una variable, pasar como parámetro y utilizar varias veces.

| add2 |
add2 := [:n| n + 2].
{ add2 value: 2. add2 value: 7 }.
⇒  #(4 9)

Los enumeradores implementan formas tremendamente potentes de procesar colecciones sin necesidad de un índice. Con esto queremos decir que son fáciles de usar correctamente. ¡Nos gusta lo sencillo!

Para hacerse una idea de lo útiles que son los enumeradores, eche un vistazo a la clase Collection en la categoría de métodos enumerating.

 CuisLogo Selecciona los números impares entre -20 y 45.

Ejercicio 4.4: Enteros impares

Quieres saber cuántos números primos son menores de 100. Envía el mensaje #size a la colección respondida en el Ejemplo 4.3. Los paréntesis son obligatorios para asegurarnos de que #size se envía la última colección resultante:

( (1 to: 100) select: [:n | n isPrime] ) size ⇒
25

Ejemplo 4.4: Cuenta cuántos números primos hay entre 1 y 100

Para mayor claridad, utilizamos una variable llamada primeNumbers para almacenar la lista de números primos que hemos creado:

| primeNumbers |
primeNumbers := (1 to: 100) select: [:n | n isPrime].
primeNumbers size

 CuisLogo Modifica el Ejemplo 4.4 para calcular la cantidad de números primos entre 101 y 200.

Ejercicio 4.5: Números primos entre 101 y 200

 CuisLogo Haz una lista de los múltiplos de 7 menores de 100..

Ejercicio 4.6: Múltiplos de 7

 CuisLogo Construye una colección de los interos impares en [1 ; 100] que no sean primos.

Ejercicio 4.7: Impares y no primos

Una enumeración hermana de @.msg select:@ es @.msg collect:@. Devuelve una nueva colección del mismo tamaño, con cada elemento transformado por un bloque de código. Al buscar raíces cúbicas perfectas, es útil conocer algunos cubos:

(1 to: 10) collect: [:n | n cubed]
⇒ #(1 8 27 64 125 216 343 512 729 1000)

Ejemplo 4.5: Recoger cubos

Los elementos recopilados pueden ser de diferentes tipos. A continuación, se enumera una cadena y se recopilan números enteros:

'Bonjour' collect: [:c | c asciiValue ]
⇒  #(66 111 110 106 111 117 114)

Podemos cambiar el valor ASCII, convertirlo de nuevo en un carácter y luego recopilarlo en una nueva cadena. Es un cifrado sencillo:

'Bonjour' collect: [:c | Character codePoint: c asciiValue + 1 ]
⇒ 'Cpokpvs'

Ejemplo 4.6: Cifrado sencillo

 CuisLogo Escribe un script para decodificar el cifrado ’Zpv!bsf!b!cptt’, está codificado como el Ejemplo 4.6.

Ejercicio 4.8: Decodificar cifrado

El cifrado de César se basa en desplazar las letras hacia la derecha en el orden alfabético. El método lleva el nombre de Julio César, quien lo utilizó en su correspondencia privada con un desplazamiento de 3.

 CuisLogo Escribe un script para generar el alfabeto en letras mayúsculas que representa el cifrado Caesar. La respuesta esperada es #($D $E $F $G $H $I $J $K $L $M $N $O $P $Q $R $S $T $U $V $W $X $Y $Z $A $B $C).

Ejercicio 4.9: Alfabeto del cifrado Caesar

Una vez que hayas descifrado correctamente el alfabeto, podrás codificar tu primer mensaje:

 CuisLogo Codifica la frase ’SMALLTALKEXPRESSION’.

Ejercicio 4.10: Codifica con el cifrado Caesar

Y decodifica el mensaje:

 CuisLogo Decodifica esta famosa cita atribuida a Julio Caesar ’DOHDMDFWDHVW’.

Ejercicio 4.11: Decodifica con el cifrado de Caesar

Diversión con bucles

La recopilación se puede iterar con bucles tradicionales: existe toda una familia de bucles repeat, while y for.

Un bucle for simple entre dos valores enteros se escribe con la palabra clave #to:do:, el último argumento es un bloque de código que se ejecuta para cada índice:

| sequence |
sequence := OrderedCollection new.
1 to: 10 do: [:k | sequence add: 1 / k].
sequence
⇒ an OrderedCollection(1 1/2 1/3 1/4 1/5 1/6 1/7 1/8 1/9 1/10)

Ejemplo 4.7: Un bloque for

Sin embargo, un recolecta escribe más concisamente:

(1 to: 10) collect: [:k | 1 / k]

Un paso con un valor diferente a 1, se inserta un tercer argumento numérico:

1 to: 10 by: 0.5 do: [:k | sequence add: 1 / k]

Un bucle repetido sin índice ni colección alguna se escribe con el mensaje #timesRepeat: enviado a un entero:

| fibo |
fibo := OrderedCollection newFrom: #(1 1).
10 timesRepeat: [
   fibo add: (fibo last + fibo atLast: 2)].
fibo
⇒ an OrderedCollection(1 1 2 3 5 8 13 21 34 55 89 144)

Ejemplo 4.8: Un bucle repeat

El cociente de los términos consecutivos de Fibonacci converge hacia el valor áureo.:

fibo pairsDo: [:i :j |
   Transcript show: (j / i ) asFloat ; cr]
⇒ 1.0
⇒ 1.5
⇒ 1.6
⇒ 1.6153846153846154
⇒ 1.6176470588235294
⇒ 1.6179775280898876

Notas al pie

(18)

Un identificador sólo es una palabra que comienza por una letra minúscula y está formada por letras minúsculas y mayúsculas y dígitos. Todos los nombres de variable son identificadores.