Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Tema 5-Tratamientos Secuenciales Básicos
Tema 5-Tratamientos Secuenciales Básicos
1. Introducción ................................................................................................................................................ 2
2. Fuentes de datos básicas y mecanismos para la generación de los datos del agregado ......................... 5
2.1 Arrays .................................................................................................................................................. 5
2.2 Secuencias Numéricas......................................................................................................................... 5
2.3 El tipo Iterable ..................................................................................................................................... 6
3. Acumuladores ............................................................................................................................................. 6
3.1 Acumuladores con una función salir que devuelve siempre falso..................................................... 6
3.1.1 Suma (sum).................................................................................................................................. 6
3.1.2 Producto (multiply) ..................................................................................................................... 7
3.1.3 Contador (count) ......................................................................................................................... 8
3.1.4 Acumula Lista (toList) ................................................................................................................. 8
3.1.5 Acumula Conjunto (toSet)........................................................................................................... 8
3.1.6 Acumula Array (toArray) ............................................................................................................ 9
3.2 Acumulador que no acumula............................................................................................................ 10
3.2.1 Ejecuta Para Todo ..................................................................................................................... 10
3.3 Acumuladores con función salir ....................................................................................................... 10
3.3.1 Para Todo (all) ........................................................................................................................... 10
3.3.2 Existe (any) ................................................................................................................................ 11
3.3.3 Busca Primero (first) ................................................................................................................. 11
3.3.4 Posición del Primero (index) ..................................................................................................... 12
3.4 Esquemas que dependen de órdenes ............................................................................................... 13
3.4.1 Máximo (max) ........................................................................................................................... 13
3.4.2 Mínimo (min)............................................................................................................................. 14
4. Ejemplos y metodología de trabajo ......................................................................................................... 14
5. Ejercicios.................................................................................................................................................... 17
2 Introducción a la Programación
1. Introducción
Al tratar con agregados de datos (listas, conjuntos, arrays, cadenas de caracteres, secuencias de valores y, en
general, todos los tipos que implementen la interfaz Iterable<E> o Stream<E>) es muy común buscar
información sobre cantidades agregadas: máximos o mínimos, posiciones de elementos en el agregado, etc.
Estos tratamientos se repiten continuamente. Nuestro objetivo es estudiar estos tratamientos sobre
agregados de datos y hacer un catálogo lo más extenso posible de los mismos y, a ser posible,
implementarlos en métodos que puedan ser reutilizados. En este tema se estudiará un primer bloque de
tratamientos secuenciales, que más tarde ampliaremos con otro bloque.
Java 8 ya proporciona, de forma muy eficiente y clara, la mayoría de estos métodos asociados al tipo de
datos Stream<E>. El objetivo de este capítulo no es estudiar los métodos de Stream<E>, sino desarrollar las
ideas en las que se basan para poder implementarlas en lenguajes que no dispongan de ese tipo de datos o,
simplemente, para comprender su funcionamiento interno.
¿cuánto vale la suma de una expresión que se aplica sobre los objetos del agregado?
¿y el producto de los que verifican una propiedad?
¿existe algún objeto que verifique una determinada condición?
¿cuáles son los objetos que cumplen una determinada propiedad?
¿cuál es el valor más grande de los que cumplen una condición?
según un orden dado, ¿cuál es mayor elemento de los que cumplen una condición?
actualiza todos los objetos que cumplen una condición
almacena en una lista los objetos que cumplen una condición
etc.
Cada una de esas preguntas y otras similares pueden ser respondidas con métodos que tienen una
estructura común.
Un ejemplo de este tipo de pregunta es: ¿Cómo se calcula la suma de los cuadrados de los números enteros
contenidos en una lista de Enteros?
Como el agregado es una lista usamos un for extendido para recorrerla. El código lo concretamos en un
método que tomando una lista como parámetro, devuelve el resultado esperado, un número entero.
Ejemplo 1. Método que calcula la suma de los cuadrados de los enteros contenidos en una lista
Igualmente podemos preguntarnos por la suma de los cuadrados de todos los enteros que van desde un
entero dado, a, hasta otro b (mayor que a) en pasos de c (positivos). Por ejemplo la secuencia 10, 12, 14, 16,
18, 20 sería especificada por a=10, b=20, c=2.
Para generar la secuencia de números usamos un for clásico. El método resultante es:
Integer suma=0;
for (Integer e=a; e<=b; e=e+c) {
suma = suma + e*e;
}
return suma;
}
El método anterior eleva una excepción si los parámetros recibidos no cumplen lo especificado (que b sea
mayor que a y que c sea mayor que cero). Salvando la comprobación de la idoneidad de los parámetros
ambos métodos tienen, como vemos, la misma estructura.
Veamos ahora un problema más general: sumar los cuadrados de los enteros contenidos en una lista que
son múltiplos de 3.
Ejemplo 3. Método que suma los cuadrados de los enteros de una lista que son múltiplos de 3.
Integer suma=0;
for(Integer e: v){
if(Enteros.esMultiplo(e,3)) {
suma = suma + e*e;
}
}
return suma;
}
También se puede aplicar este esquema general en la secuencia definida por a, b, c. Es decir, los enteros
desde a hasta b con incrementos de c.
Ejemplo 4. Método que suma los cuadrados de los enteros de una secuencia que son múltiplos de 3.
Integer suma=0;
for (Integer e=a; e<=b; e=e+c) {
if(Enteros.esMultiplo(e,3)){
4 Introducción a la Programación
En estos métodos aparece una estructura que se repetirá. Esta estructura tiene varios elementos a destacar:
una fuente de datos y un mecanismo de recorrido de los datos, un acumulador, un filtro, una
transformación, y un criterio de parada.
Fuente de datos: Es el origen de los datos. Puede ser una variable de un tipo agregado de datos (Set,
List, …), un agregado virtual de datos (los enteros pares entre los valores a y b, …), una secuencia
descrita por un primer elemento y el siguiente de uno dado, etc. El tipo del agregado será Ag y
asumimos que los elementos que lo componen son de tipo E.
Mecanismo de generación de los datos: La forma concreta de generar, uno tras otro, los datos del
agregado. Por ejemplo, los elementos de una lista se pueden recorrer hacia arriba o hacia abajo. Una
opción para implementar este recorrido es generar los índices de la lista con un for y,
posteriormente, obtener los contenidos de las casillas. Otro ejemplo sería la generación de los
enteros pares entre a y b mediante un for.
Acumulador: Es una variable que acumula el resultado calculado hasta ese momento. Un
acumulador es una variable que es de un tipo A que hay que especificar. Necesita un valor inicial y
una función acumula(a,e) capaz de acumular el elemento e en el acumulador a. Dependiendo de que
el tipo A sea mutable o inmutable la función acumula tendrá un diseño u otro. Un acumulador
concreto es suma. En ese caso A es un tipo numérico, se inicializa a cero y la función acumula es de
la forma a = a+e. Algunos acumuladores tienen, además, un criterio de parada.
Un criterio de parada es una expresión lógica, que podemos denominar salir(a), que depende del
valor del acumulador y nos indica si el valor actual del acumulador es ya el valor acumulado final y,
por lo tanto, salimos del bucle de la iteración.
Filtro: Es una expresión lógica sin efectos laterales que nos sirve para escoger los elementos del
agregado sobre los que vamos a hacer el cálculo. El filtro lo concretaremos en una expresión lógica
que llamaremos filtra(e). En los ejemplos 3 y 4 el filtro es la expresión Enteros.esMultiplo(e,3).
Transformación: Es una expresión que a partir de cada objeto del agregado calcula otro valor que es
el que queremos acumular. La transformación la concretaremos en una expresión que llamaremos
transforma(e) que puede devolver un tipo T distinto a E. En los ejemplos 1, 2, 3 y 4 la transformación
es la expresión e*e.
Resultado: Una función que a partir del valor del acumulador devuelve el resultado buscado. La
concretaremos en una función que llamaremos resultado(a) que devolverá un valor del tipo R.
Con estas ideas los esquemas iterativos cuyo objetivo es acumular un valor a partir de un agregado tienen la
siguiente forma:
R esquemaSecuencial(Ag ag) {
A a = valor inicial;
Para cada e en ag {
if (filtra(e)) {
T e1 = transforma(e);
acumula(a,e1);
5. Tratamientos secuenciales básicos
5
}
if(salir(a)) break;
}
return resultado(a);
}
En el esquema anterior vemos entonces los elementos: acumulador, fuente de datos y mecanismo concreto
para ir recorriendo los datos de la fuente de datos.
Como vemos, el acumulador es una variable de tipo A, y tiene un conjunto de operaciones asociadas:
acumula(a,e)
salir(a)
resultado(a)
valorinicial(a)
Dependiendo de que el tipo A sea mutable o inmutable las funciones anteriores pueden tomar una forma u
otra. La funciones anteriores se pueden considerar separadas unas de otras, si estamos programando en un
lenguaje como C, o considerarlas asociadas al tipo A si usamos un lenguaje como Java u otro orientado a
objetos.
En este capítulo nos concentraremos en los esquemas secuenciales y los detalles de su implementación en
diferentes tipos de lenguajes.
2. Fuentes de datos básicas y mecanismos para la generación de los datos del agregado
Las fuentes de datos que veremos en este tema son: los arrays, las secuencias numéricas (de enteros o
reales), las listas, conjuntos, y en general, cualquier agregado que sea iterable. Más adelante, en el tema de
tratamientos secuenciales avanzados, vemos el tipo Stream y los arrays de dos dimensiones.
2.1 Arrays
El recorrido de los elementos de un array o cualquier otro agregado indexado sigue el esquema:
E[] ag = ...;
...
for (int i = 0; i < ag.length; i++) {
T e = ag[i];
...
}
...
…
for(i = a; i < b; i = i+c ) { // secuencia aritmética con a <= b, c > 0
…
}
for(i = a; i > b; i = i-c ) { // secuencia aritmética con a >= b, c > 0
…
}
for(i = a; i < b; i = i*c ) { // secuencia geométrica con a <= b, c > 1
…
}
Iterable<E> ag = ...;
...
for (E e : ag) {
...
}
3. Acumuladores
En este apartado se detallará un conjunto básico de acumuladores que son de uso muy general. Más
adelante se verá otro grupo de acumuladores más avanzados. Para describir los acumuladores se usará una
plantilla en la que se indicará el objetivo, el tipo de la variable del acumulador y los detalles de las funciones
que tiene asociadas.
Los acumuladores básicos los podemos agrupar, teniendo en cuenta si hay que recorrer el agregado
completamente o no, si acumulan realmente un valor o no, y si tienen relación con criterios de ordenación.
En los siguientes apartados se estudiarán los distintos tipos de acumuladores agrupados en relación a estas
características.
3.1 Acumuladores con una función salir que devuelve siempre falso
Este grupo de acumuladores tienen una función salir que devuelve siempre falso. Esto indica que para
devolver su resultado es necesario recorrer todos los elementos del agregado sobre el que se están
acumulando los valores. Note también que al devolver siempre falso la función salir, la línea de código
if(salir(a)) break; que aparece en el esquema general puede omitirse.
Objetivo: Suma de los valores del agregado cuyos elementos deben ser de tipo numérico.
5. Tratamientos secuenciales básicos
7
Un ejemplo de un acumulador suma de tipo Double en un tratamiento secuencial genérico con una fuente
de datos de tipo Iterable<T> es el siguiente:
En el código anterior hemos asumido que el acumulador es de tipo Double y que el agregado de datos es de
tipo Iterable<T>. El esquema sería similar si el tipo del valor a acumular fuese cualquier otro tipo numérico.
Por ejemplo, si el valor a acumular es Integer, éste será el tipo de la variable res, que se inicializará a 1, y
Expresion devolverá un valor de tipo Integer.
Objetivo: Multiplicar los valores del agregado cuyos elementos deben ser de tipo numérico.
Tipo del acumulador: Double, Integer u otro tipo numérico.
Valor Inicial: Uno.
Expresión acumuladora: a = a*e.
Salir(a): Falso
Resultado(a): a
Un ejemplo de un acumulador producto de tipo Double en un tratamiento secuencial genérico con una
fuente de datos de tipo Iterable<T> es el siguiente:
Un ejemplo de un acumulador contador en un tratamiento secuencial genérico con una fuente de datos de
tipo Iterable<T> es el siguiente:
Un ejemplo de un acumulador lista en un tratamiento secuencial genérico con una fuente de datos de tipo
Iterable<T> es el siguiente:
Un ejemplo de un acumulador lista en un tratamiento secuencial genérico con una fuente de datos de tipo
Iterable<T> es el siguiente:
Objetivo: Devuelve un array de elementos tipo E con los elementos del agregado colocados en las
casillas del array y el número de elementos de este array.
Tipo del acumulador: (E[] a, i)
Valor Inicial: (new E[m], 0). Con m suficiente para asegurar m mayor que el tamaño del agregado.
Expresión acumuladora: (a[i] = e, i++)
Salir(a): Falso
Resultado(a,i): (a,i)
Un ejemplo de un acumulador array en un tratamiento secuencial genérico con una fuente de datos de tipo
Iterable<T> es el siguiente:
Iterable<T> agregado = …;
int num = … //número de elementos del agregado
int i = 0;
E []a = new E[num];
for (T e : agregado) {
if (Filtro(e)) {
E b = Expresion(e);
a[i] = b;
i++;
}
}
return a;
10 Introducción a la Programación
El esquema es más complejo que en el caso de seleccionar listas o conjuntos. La razón es que los arrays son
agregados de datos de tamaño constante. Por lo tanto, debemos conocer su tamaño para poder crearlos.
Para poder hacerlo debemos o bien contar los elementos que pasan el filtro, o bien contar los elementos del
agregado. Luego debemos crear un array de ese tamaño y, por último, colocar los sucesivos valores
calculados en las casillas que tienen un índice i dado. El índice se irá actualizando a medida que vayamos
encontrando objetos que pasan el filtro.
Objetivo: Ejecuta una acción sobre cada elemento del agregado. Una acción es una sentencia
construida sobre la variable e que puede producir efectos laterales sobre la misma, es decir, cambiar
su estado. Los elementos deben ser de un tipo mutable.
Tipo del acumulador: No hay
Valor Inicial: No hay
Expresión acumuladora: op(e)
Salir(a): Falso
Resultado(a): no hay
Un ejemplo de un ejecuta para todo en un tratamiento secuencial genérico con una fuente de datos de tipo
Iterable<T> es el siguiente:
Objetivo: Decide si todos los elementos del agregado cumplen una propiedad p(e). p(e) es una
función booleana que se aplica sobre e.
Tipo del acumulador: Boolean
5. Tratamientos secuenciales básicos
11
Un ejemplo de un acumulador para todo en un tratamiento secuencial genérico con una fuente de datos de
tipo Iterable<T> es el siguiente:
Objetivo: Decide si alguno de los elementos del agregado cumple la propiedad p(e).
Tipo del acumulador: Boolean
Valor Inicial: false
Expresión acumuladora: a = p(e)
Salir(a): a
Resultado(a): a
Un ejemplo de un acumulador existe en un tratamiento secuencial genérico con una fuente de datos de tipo
Iterable<T> es el siguiente:
Objetivo: Buscar, si existe, el primer objeto que cumple una propiedad p(e). Si no existe eleva la
excepción NoSuchElementException.
Tipo del acumulador: E
Valor Inicial: null
12 Introducción a la Programación
Un ejemplo de un acumulador busca primero en un tratamiento secuencial genérico con una fuente de
datos de tipo Iterable<T> es el siguiente:
Note que en este caso, la función salir será cierta en el momento en el que se cumpla la propiedad p(e). Esto
hace que la comprobación explícita de la función de salida se haya omitido. Se ha optado por combinar la
operación de acumulación y la de salida en el mismo bucle if.
Objetivo: buscar, si existe, la posición del primer objeto que cumple una propiedad p(e). Si no existe
devuelve -1.
Tipo del acumulador: (a,i): (Integer, Integer)
Valor Inicial: (-1,0)
Expresión acumuladora: if (p(e)) {a=i;} i++;
Salir(a): a != -1
Resultado(a): a
Un ejemplo de un acumulador posición del primero en un tratamiento secuencial genérico con una
fuente de datos de tipo Iterable<T> es el siguiente:
}
i++;
}
return res;
Note que en este caso, la función salir será cierta en el momento en el que se cumpla la propiedad p(e). Esto
hace que, igual que ocurría en el caso anterior, la comprobación explícita de la función de salida se haya
omitido. Se ha optado por combinar la operación de acumulación y la de salida en el mismo bucle if.
Objetivo: Calcula el máximo de los elementos del agregado. El máximo se calcula con respecto a un
orden dado. En este tema nos ceñiremos al orden natural de los tipos, pero, posteriormente, cuando
veamos los tratamientos secuenciales avanzados, el orden se representará por un Comparator<E>. Si
el agregado es vacío devuelve la excepción NoSuchElementException.
Tipo del acumulador: E
Valor Inicial: null
Expresión acumuladora1:
Salir(a): false
Resultado(a): a
Un ejemplo de un acumulador máximo en un tratamiento secuencial genérico con una fuente de datos de
tipo Iterable<T> y en el que interviene el orden natural es el siguiente:
1
Esta expresión se generalizará en el tema de tratamientos secuenciales avanzados para trabajar con Comparator<T>
14 Introducción a la Programación
Objetivo: Calcula el mínimo de los elementos del agregado. El máximo se calcula con respecto a un
orden dado representado por el Comparator<E> ord. Si el agregado es vacío devuelve la excepción
NoSuchElementException.
Tipo del acumulador: E
Valor Inicial: null
Expresión acumuladora2:
Salir(a): false
Resultado(a): a
Un ejemplo de un acumulador mínimo en un tratamiento secuencial genérico con una fuente de datos de
tipo Iterable<T> y en el que interviene el orden natural es el siguiente:
Los esquemas anteriores se repiten muy frecuentemente. Para poder usarlos debemos identificar en primer
lugar cada uno de sus elementos:
2
Esta expresión se generalizará en el tema de tratamientos secuenciales avanzados para trabajar con Comparator<T>
5. Tratamientos secuenciales básicos
15
Para los ejemplos siguientes suponemos implementados los tipos propuestos como ejercicios en el tema
anterior.
Si queremos diseñar un método static de la clase Personas, tomando como parámetro una lista de Persona y
una edad que debe ser positiva, devuelva como resultado la suma de las edades de las personas contenidas
en la lista que sean mayores que la edad dada. No hay garantías de que los objetos en la lista sean distintos
de null. En este caso tenemos:
El código resultante, donde puede verse el código concreto del filtro y de la expresión, es:
Dado un array de String cada una de las cuales representa un Double, obtener una lista de Double.
Ahora hemos usado el esquema acumula lista, no hay filtro y new Double (e) es la transformación que
construye objetos de tipo Double.
Implementar el método getLibros en la clase Bibliotecas que dada una Biblioteca y dos fechas, la segunda
mayor que la primera, devuelva todos los libros distintos que fueron adquiridos entre esas dos fechas.
16 Introducción a la Programación
return res;
}
Ahora hemos usado el esquema acumula conjunto, y el filtro es que la fecha esté comprendida
estrictamente entre las dos proporcionadas. Suponemos que todos los libros son distintos de null. El origen
de datos tiene una lista de libros. Las listas son iterables.
Dado un array de Punto, obtener cuántos hay con sus coordenadas x e y positivas.
Ahora hemos usado el esquema contar, el filtro es que el punto sea distinto de null y tenga las coordenadas
x e y positivas.
Dada una lista de Viaje calcular cuántas ciudades distintas pueden visitarse si se hacen todos los viajes de la
lista que sean posibles para un grupo formado por un número dado de personas.
return sr.size();
5. Tratamientos secuenciales básicos
17
Ahora hemos usado el esquema acumula conjunto, el filtro es que el grupo sea mayor o igual al entero dado.
El número de objetos distintos es el cardinal del conjunto construido. Podemos observar que el origen de
datos está compuesto de una lista cada uno de cuyos objetos tiene una propiedad que es otra lista.
Ejemplo
Dada una lista de Circulo diseñar un método static que nos devuelva el que tenga un área mayor de entre los
que tengan un radio mayor a un número dado.
5. Ejercicios
1. Dada una lista de números enteros, que se toma como parámetro, implemente un método de utilidad en
la clase Enteros para responder a cada una de las siguientes cuestiones:
a) ¿Todos los elementos de la lista son impares?
b) ¿Existe alguno que sea impar y primo? Supóngase implementado previamente el método
esPrimo.
c) ¿Cuántos enteros impares y primos hay?
d) ¿Cuál es el valor de la suma de los enteros que son primos? ¿Y del producto de los que son
impares?
e) ¿Hay alguno que divida a un número n (siendo n un parámetro del método)?
f) Busque todos los enteros pares que estén en la lista de entrada (que el método devuelva el
resultado en otra lista)
2. Dado un Set<List<Integer>> que se toma como parámetro, implemente un método en la clase de utilidad
Enteros para responder a cada una de las siguientes cuestiones:
a) ¿Cuánto vale la suma de todos los elementos que son impares?
b) ¿Cuál es el entero más pequeño? ¿y el más grande?
c) ¿Existe algún elemento que sea primo?
d) ¿Existe algún elemento que sea primo y que ocupe la primera posición de cada lista del
conjunto?
e) Devuelva un List<Integer> con todos aquellos enteros que ocupan la primera posición en cada
lista del conjunto.
18 Introducción a la Programación
3. Dado un Set<List<Integer>> que se toma como parámetro, implemente un método en la clase de utilidad
Enteros para resolver cada una de las siguientes cuestiones:
a) ¿Cuál es la lista de mayor tamaño?
b) ¿Existe alguna lista que contenga algún entero con valor negativo?
c) ¿Todas las listas son de tamaño mayor que 2?
d) ¿Cuánto suman todos los números enteros del conjunto de listas?
4. Dada una colección de libros, implemente un método estático para resolver cada uno de los siguientes
apartados:
a) Encontrar el título del libro más barato escrito por “George R. R. Martin”.
b) Calcular el total de páginas de todos los libros del autor anterior.
c) Calcular la media de páginas de los libros del autor anterior.
d) Cambiar el tipo de préstamo a MENSUAL de todos los libros que tengan al menos 1000 páginas.
a) Un método que dada una compañía aérea devuelva el nombre del piloto del primer vuelo (de
salida más pronto) con plazas libres con destino París.
b) Un método que dada una compañía aérea devuelva el código del vuelo que más distancia recorre
de entre los que salen hoy.