Skip to content

Proyecto y ejercicios

En este proyecto se llevará acabo una serie de ejercicios sobre la programación funcional.

IMPORTANTE

Sólo se creará un único proyecto, para cada ejercicio se deberá crear una nueva versión del mismo a través de GitHub, teniendo que usar tags para cada versión cuyo nombre sea vX.0 dónde X es el número de ejercicio

Ejercicio 1

Crear una interfaz Predicate, que tenga un único método llamado test(entero) que reciba un entero y retorne un booleano.

Crea una clase llamada IsPrime que implemente la interfaz Predicate y cuyo método test(entero) retorne true si el entero recibido es un número primo.

Crea una clase SuperFunctions, que posea los siguientes métodos estáticos:

  1. count(list, predicate): Retorna el número de elementos de la lista que cumplen con el predicado recibido. En el main llama a dicho método estático para obtener cuántos números hay que sean primos en una lista pasada como argumento. Así, si la lista es [5, 7, 2, 4, 9, 5, 15, 3], el método estático debe retornar 5, ya que contiene los siguientes números primos 5, 7, 2, 5, 3.
  2. allMatch(list, predicate): Retorna true si todos los elementos de la lista cumplen con el predicado recibido. En el main llama a dicho método estático para obtener si todos los números de la lista son primos.
  3. noneMatch(list, predicate): Retorna true si ningún elemento de la lista cumple con el predicado recibido. En el main llama a dicho método estático para obtener si ninguno de los números de la lista es primo.
  4. anyMatch(list, predicate): Retorna true si algún elemento de la lista cumple con el predicado recibido. En el main llama a dicho método estático para obtener si alguno de los números de la lista es primo.
  5. takeWhile(list, predicate): Retorna una nueva lista creada a partir de los elementos de la lista recibida como argumento, de manera que va tomando valores de la lista original mientras éstos cumplan con el predicado recibido. En cuanto un elemento no cumple el predicado se dejan de incluir elementos en la lista resultante. Por ejemplo, si le pasamos la lista [5, 7, 2, 4, 9, 5, 15, 3] y el predicado de que el número sea primo, la función deberá retornar la lista [5, 7, 2], ya que el 4 ya no es primo. Prueba el ejemplo anterior desde el main.
  6. dropWhile(list, predicate): Retorna una nueva lista creada a partir de los elementos de la lista recibida como argumento, de manera que el primer elemento que es incluido en la lista resultante es aquel que no cumple con el predicado recibido como argumento, y a partir de dicho elemento el resto de elementos de la lista original son incluidos sin realizar ninguna comprobación. Por ejemplo, si le pasamos la lista [5, 7, 2, 4, 9, 5, 15, 3] y el predicado de que el número sea primo, la función deberá retornar la lista [4, 9, 5, 15, 3], porque el primer número no primo de la lista original es 4. Prueba el ejemplo anterior desde el main .
Ejercicio 2

Crea una interfaz Function, que tenga un único método llamado apply(entero)que reciba un entero y retorne un entero.

Crea una clase llamada Triple que implemente la interfaz Function y cuyo método apply(entero)retorne el triple del entero recibido.

Crea una clase llamada LessThan100 que implemente la interfaz Predicate y cuyo método test(entero) retorne true si el entero recibido es >= 0 y menor que 100.

En la clase SuperFunctions agrega los siguientes métodos estáticos:

  1. iterate(integerSeed, count, function): Retorna una lista con count elementos (se debe lanzar una excepción si count <= 0), donde el primer elemento corresponde a integerSeed y el resto de elementos se calcula aplicando la función recibida al elemento anterior. Por ejemplo, si integerSeed es 1, count es 4 y la función es calcular el triple, el método debe retornar la lista [1, 3, 9, 27]
  2. iterate(integerSeed, max, predicate, function): Retorna una lista donde el primer elemento corresponde a integerSeed y el resto de elementos se calcula aplicando la función recibida al elemento anterior. En cuanto un elemento generado no cumple con el predicado dicho elemento no es incluido en la lista resultante y ésta se da por concluida. Si la lista resultante alcanza el número máximo de elementos max la lista se da por concluida. Por ejemplo, si integerSeed es 1, max es 10, el predicado es que el número sea menor que 100 y la función es calcular el triple, el método debe retornar la lista [1, 3, 9, 27, 81], ya que 243 (81 * 3) ya no es menor que 100. Si el predicado se cumple siempre, como por ejemplo si en el caso anterior integerSeed es 0 , se retornará una lista cuyo tamaño corresponderá a max, en nuestro caso [0, 0, 0, 0, 0, 0, 0, 0, 0, 0].
Ejercicio 3

Crea una interfaz ConsumerBinary, que tenga un único método accept(entero1,entero2) que reciba dos enteros y no retorne nada.

Crea una clase PrinterWithIndex que implemente la interfaz ConsumerBinary y cuyo método accept(entero1, entero2) muestre por consola el mensaje "El valor {entero2} tiene el índice {entero1}" sustituyendo {entero2} por el valor de entero2 y {entero1} por el valor de{entero1}

Crear una interfaz FunctionBinary, que tenga un único método apply(entero1, entero2) que reciba dos enteros y retorne un entero.

Crea una clase Subtractor que implemente dicha interfaz y cuyo método apply() retorne el resultado de restarle entero1 a entero2.

En la clase SuperFunctions agrega los siguientes métodos estáticos:

  • actWithIndex(list, consumerBinary): Para cada elemento de la lista llama al método accept del consumidor binario pasándole como primer argumento el índice del elemento y como segundo argumento el elemento en sí. La función debe retornar una nueva lista con los elementos de la lista original. Por ejemplo, si la lista es [10, 20, 30] y el consumerBinary es un objeto de la clase PrinterWithIndex, se debe mostrar por consola:

El valor 10 tiene el índice 0
El valor 20 tiene el índice 1
El valor 30 tiene el índice 2
La función debe retornar [10, 20, 30].

  • consumeWithIndex(list, consumerBinary): Para cada elemento de la lista llama al método accept del consumidor binario pasándole como primer argumento el índice del elemento y como segundo argumento el método en sí. La función no debe retornar nada.
  • reduceFromEnd(list, functionBinary): Retorna un valor entero con el resultado de aplicar la operación de reducción indicada por la functionBinary pero tomando como primer elemento el último de la lista original. Si la lista original estuviera vacía retornará null. Por ejemplo, si la lista es [1, 3, 9, 27] y la función binaria corresponde a un objeto de la clase Subtractor, la función debe retornar 14, que corresponde a la operación 27 - 9 - 3 - 1.
  • reduceFromEnd(list, identity, functionBinary): Retorna un valor entero con el resultado de aplicar la operación de reducción indicada por la functionBinary pero tomado como primer elemento el último de la lista original. El valor inicial de acumulador corresponderá a identity. Por ejemplo, si la lista es [1, 3, 9, 27], identidad corresponde 0 y la función binaria corresponde a un objeto de la clase Subtractor, la función debe retornar -40, que corresponde a la operación 0 - 27 - 9 - 3 - 1.
Ejercicio 4

Realiza las siguientes modificaciones:

  • En el main(), realiza las llamadas anteriores a las funciones haciendo uso de las clases anónimas inline, en vez de usar las clases IsPrime, Triple, NaturalLessThan100, PrinterWithIndex o Subtractor, que puedes eliminar tranquilamente.
  • Modifica las interfaces para que en vez de trabajar con enteros, sean interfaces genéricas parametrizadas que funcionen con tipos genéricos.
  • Modifica los métodos de la clase SuperFunction para que se adapten a los cambios descritos anteriormente.
  • Vuelve a cambiar en el main(), las llamadas a las funciones, haciendo uso de las clases anónimas inline, pero en esta ocasión haciendo uso de los parametrizadas con el tipo adecuado.
Ejercicio 5

Crea la clase Stream<T>, que representa un flujo, y añádele los métodos de la clase SuperFunctions, sin que sean estáticos (excepto el método iterate). (La clase SuperFunctions podrá ser eliminada).

Haz que los métodos de la clase retornen un nuevo flujo en vez de una lista.

Agrega a la clase Stream<T> los métodos:

  • filter(predicate): retorna un nuevo flujo con los elementos de la lista original que cumplan el predicado.
  • map(function): retorna un nuevo flujo con los elementos "transformados" del flujo original. Por ejemplo, si la flujo inicial está formado por los números del 1 al 10 y se quiere transformar al cuadrado de dichos números (el valor de function sería una clase anónima inline, que la implementación de su método applyretorne el cuadrado de un número).
  • of(vararg): método estático que recibe un vararg de elementos, que retornará un flujo con los elementos recibidos.
  • from(collection): método estático que recibe una colección de elementos y retornará un nuevo flujo con los elementos recibidos.
  • recolectar(): retorna una lista con los elementos gestionados por el flujo.

Realiza un main que cree un flujo usando el método estático iterate(integerSeed, count, function), donde integerSeed, sea 1, countsea 5 y function sea una Function<Integer> cuyo método apply() retorna el valor recibido multiplicado por 5 y se le sume 1. Después el flujo generado será filtrado de manera que se obtenga un nuevo flujo con sólo los valores del anterior que sean pares. Después se deberá obtener un nuevo flujo con los valores del anterior convertidos a cadena con el formato -valor- y finalmente dicho flujo se reduzca haciendo uso del método reduceFromEnd(), de manera que se obtenga una única cadena de caracteres. Por ejemplo -156--6-.

Ejercicio 6

Realiza la versión anterior modificando lo siguiente:

  • Añade la anotación @FunctionalInterface a todas las interfaces definidas.
  • Reemplaza las clases anónimas con expresiones lambdas.
Ejercicio 7

En esta versión elimina las interfaces funcionales y reemplázalas en la clase Stream<T> por las interfaces funcionales predefinidas en Java 8.

Ejercicio 8

Crea una clase MyAppUtils con los siguientes métodos estáticos:

  • quintupleMoreOne(value): recibe un entero y retorna dicho valor multiplicado por 5 y después sumándole 1.
  • isEven(value): recibe un entero y retorna true si el número es par.
  • withDashes(value): recibe un entero y retorna la cadena -value-.
  • concat(str1, str2): retorna la concatenación de ambas cadenas recibidas.

Haciendo uso de la referencias a métodos, sustituye las expresiones lambda por la llamada a estos métodos.

Ejercicio 9

Realiza los métodos anteriores dentro de la misma clase Main sin definirlos de forma estática y eliminado la clase MyAppUtils. Además en vez de usar el método concat (que puede ser eliminado) se usará el método concat de la clase String.

Ejercicio 10

Agrega a la clase Stream<T> un método find(predicate) que encuentre el primer elemento que cumple con el predicado y lo retorne en un Optional<T>.

Agrega también, un método llamada findLast(predicate) que encuentre el último elemento que cumple con el predicado y lo retorne en un Optional<T>.