Tareas prácticas de Java: para cursos y otras actividades
Algunas palabras introductorias
Durante los últimos años, he estado enseñando programación Java. Con el tiempo, cambió: a veces se agregaron o desecharon diferentes partes, cambió la secuencia de temas, cambió el enfoque para construir un plan de las clases, y así sucesivamente. Es decir, el curso ha sido mejorado. Uno de los principales problemas encontrados al preparar el curso son las tareas. Sobre ellos y será discutido.
El hecho es que cada una de mis clases consta de dos partes. Al principio, actúo como profesor: te digo con ejemplos de código sobre algún tema nuevo (clases, herencia, genéricos, etc.). La segunda parte es práctica. Obviamente, no tiene sentido hablar de programación, es necesario programar. La prioridad en el aula es la resolución de problemas, es decir, programar algo de alguna manera. La programación en el aula es diferente de la programación en el hogar, ya que en la clase puede hacer una pregunta, mostrar el código, obtener una evaluación rápida del código, comentarios sobre mejoras, corrección de lo escrito. Fue muy fácil encontrar tareas para las primeras lecciones. Tareas para bucles, sentencias condicionales y OOP (por ejemplo, escriba la clase "Perro" o la clase "Vector"). Servicios como
leetcode incluso le permiten verificar la exactitud de la resolución de tales problemas de forma inmediata, en línea. Pero, ¿qué tareas se les debe dar a los estudiantes en una lección dedicada a las colecciones? Corrientes? ¿Qué pasa con las anotaciones? Durante varios años, se me ocurrió o revisé varias de esas tareas, y este artículo, de hecho, es una colección de estos problemas (se adjunta una solución a algunos problemas).
Por supuesto, todas las tareas ya han aparecido en alguna parte. Sin embargo, este artículo está dirigido a profesores de cursos de programación (para lenguajes similares a Java, la mayoría de las tareas lo harán), o aquellos que enseñan programación en privado. Estas tareas se pueden usar "fuera de la caja" en sus clases. Los estudiantes de Java también pueden intentar resolverlos. Pero tales decisiones requieren verificación y evaluación de terceros.
Algunas de las tareas más simples que todos han estado usando durante décadas, también las incluí en este artículo. Quizás, para no comenzar inmediatamente con clases abstractas.
¡Cualquier idea y sugerencia es bienvenida!
Lista de tareas
Los fundamentos
1.0. Máximo, mínimo y promedio1.1 Clasificación de matriz1.2 Encontrar primos1.3 Eliminar de una matrizConceptos básicos de OOP
2.0 Diseñando y creando una clase que describe un vector2.1 Generación de un elemento aleatorio con un peso2.2 Lista vinculadaRecursividad
3.0 Búsqueda binaria3.1 Encuentra la raíz de la ecuación3.2 Árbol de búsqueda binariaHerencia
4.0 Implemente una jerarquía de clases que describa formas tridimensionales4.1 Implementar una jerarquía de clases que describa figuras tridimensionales - 24.2 Implemente una jerarquía de clases que describa formas tridimensionales - 34.3 Implemente una jerarquía de clases que describa formas tridimensionales - 4Líneas
5.0 Diccionario de frecuencia de letrasClases abstractas e interfaces
6.0. Convertidor de temperatura6.1. Stringbuilder con soporte para deshacer6.2. Statebuilding Stringbuilder (patrón de observador)6.4. Llenar una matriz con funciónColecciones
7.0 Diccionario de frecuencia de palabras7.1. Colección sin duplicados7.2. ArrayList y LinkedList7.3. Escribir un iterador sobre una matriz7.4. Escribir un iterador sobre una matriz bidimensional7.5. Un iterador aún más complejo7.6. Iterador sobre dos iteradores7.7. Elementos de conteo7.8. Cambiar claves y valores en MapaMultithreading
8.0. Estados8.1. Hilo de sincronización8.2. Fabricante de consumoAnotaciones
9.0. Anotación personalizada: creación y uso con reflexión10,0 Número de restricciones viales10.1 Búsqueda de Wikipedia. En el programa de consola10.2 Tarea final: utilidad de consola para descargar archivos a través de HTTP10.3 Tarea final - weather Telegram-bot10.4 Tarea final: reconocimiento de escritura a manoLos fundamentos
1.0. Máximo, mínimo y promedio
Desafío:Rellene la matriz con números aleatorios e imprima el valor máximo, mínimo y promedio.
Para generar un número aleatorio, use el método
Math.random () , que devuelve un valor en el intervalo [0, 1].
Solución:public static void main(String[] args) { int n = 100; double[] array = new double[n]; for (int i = 0; i < array.length; i++) { array[i] = Math.random(); } double max = array[0];
1.1. Implemente un algoritmo de clasificación de burbujas para ordenar una matriz
Solución: for (int i = 0; i < array.length; i++) { for (int j = 0; j < array.length - i - 1; j++) { if (array[j] > array[j + 1]) { double temp = array[j]; array[j] = array[j + 1]; array[j + 1] = temp; } } } for (int i = 0; i < array.length; i++) { System.out.println(array[i]); }
1.2. Buscar números primos
Desafío:Escriba un programa que imprima números primos en la consola entre [2, 100].
Use el operador
% (resto de la división) y bucles para resolver este problema.
Solución: for(int i = 2; i <= 100; i ++){ boolean isPrime = true; for(int j = 2; j < i; j++){ if(i % j == 0){ isPrime = false; break; } } if(isPrime){ System.out.println(i); } }
O usando
lazos de etiquetas :
out_for: for (int i = 2; i <= 100; i++) { for (int j = 2; j < i; j++) { if (i % j == 0) { continue out_for; } } System.out.println(i); }
1.3. Eliminar de la matriz
Desafío:Dado un conjunto de enteros y otro entero. Elimine todas las apariciones de este número de la matriz (no debe haber huecos).
Solución: public static void main(String[] args) { int test_array[] = {0,1,2,2,3,0,4,2}; System.out.println(Arrays.toString(removeElement(test_array, 3))); } public static int[] removeElement(int[] nums, int val) { int offset = 0; for(int i = 0; i< nums.length; i++){ if(nums[i] == val){ offset++; } else{ nums[i - offset] = nums[i]; } }
Puede escribir un método para cortar la cola de la matriz usted mismo, pero vale la pena señalar que el método estándar funcionará más rápido:
public static int[] removeElement(int[] nums, int val) { int offset = 0; for(int i = 0; i< nums.length; i++){ if(nums[i] == val){ offset++; } else{ nums[i - offset] = nums[i]; } } int[] newArray = new int[nums.length - offset]; for(int i = 0; i < newArray.length; i++){ newArray[i] = nums[i]; } return newArray; }
Sin embargo, si sigue este camino, primero puede crear una matriz de la longitud deseada y luego llenarla:
public static int[] removeElement(int[] nums, int val) { int count = 0;
2.0. Diseñando y creando una clase vectorial
Desafío:Cree una clase que describa un vector (en un espacio tridimensional).
El debe tener:
- constructor con parámetros en forma de una lista de coordenadas x, y, z
- Método que calcula la longitud de un vector. La raíz se puede calcular usando Math.sqrt ():
- método de cálculo de producto escalar:
- Método para calcular un producto vectorial con otro vector:
- Método que calcula el ángulo entre los vectores (o el coseno del ángulo): el coseno del ángulo entre los vectores es igual al producto escalar de los vectores dividido por el producto de los módulos (longitudes) de los vectores:
- métodos para suma y diferencia:
- Un método estático que toma un número entero N y devuelve una matriz de vectores aleatorios de tamaño N.
Si el método devuelve un vector, debería devolver un nuevo objeto y no cambiar el de base. Es decir, debe implementar la plantilla "
Objeto inmutable "
Solución: public class Vector {
Puedes usar esta clase así:
public static void main(String[] args) { Vector[] vectors = Vector.generate(10); System.out.println(vectors[0]); System.out.println(vectors[1]); System.out.println(vectors[0].length()); System.out.println(vectors[0].scalarProduct(vectors[1])); System.out.println(vectors[0].crossProduct(vectors[1])); System.out.println(vectors[0].cos(vectors[1])); System.out.println(vectors[0].add(vectors[1])); System.out.println(vectors[0].subtract(vectors[1])); }
Puede generalizar esta solución y escribir la clase Vector para una dimensión arbitraria:
public class Vector {
2.1. Generando un artículo aleatorio con un peso
Desafío:Escriba una clase cuyo constructor tome dos matrices: una matriz de valores y una matriz de pesos de valores.
La clase debe contener un método que devolverá un elemento de la primera matriz al azar, teniendo en cuenta su peso.
Un ejemplo:
Dado un conjunto [1, 2, 3] y un conjunto de pesos [1, 2, 10].
En promedio, el valor
"1" debería devolver 2 veces menos que el valor
"2" y diez veces menos frecuente que el valor
"3" .
Solución: class RandomFromArray { private int[] values;
Pero, dado que la matriz de
rangos está ordenada, puede (y debería) usar una búsqueda binaria:
public int getRandom() { int random = (int) (Math.random() * (sum - 1)); int index = Arrays.binarySearch(ranges, random); return values[index >= 0 ? index : -index - 2]; }
Hay otra solución a este problema. Puede crear una matriz cuyo tamaño sea igual a la suma de todos los pesos. Luego, la elección de un elemento aleatorio se reduce a generar un índice aleatorio. Es decir, si se proporciona una matriz de valores [1, 2, 3] y una matriz de pesos [1, 2, 10], puede crear inmediatamente una matriz [1, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3] y extraer un elemento aleatorio de él:
class RandomFromArray { private int[] extended_values;
Esta solución tiene una ventaja: el tiempo para extraer un elemento aleatorio
O (1) , en contraste con log (n) en la solución anterior. Sin embargo, requiere mucha memoria:
2.2. Lista vinculada
Otra tarea que a menudo doy es la implementación de una lista vinculada. Se puede dar en su forma más simple (implemente
add () y
get () solamente ), o puede solicitar implementar
java.util.List .
No me detendré en esto en detalle, hay muchos artículos sobre la implementación de una lista vinculada en Java en Habr, por ejemplo este:
Estructuras de datos en imágenes. LinkedListDesafío:Escriba un método que verifique si el elemento dado está en la matriz o no.
Use la enumeración y la búsqueda binaria para resolver este problema.
Compare el tiempo de ejecución de ambas soluciones para matrices grandes (por ejemplo, elementos 100000000).
Solución: public static int bruteForce(double[] array, double key) { for (int i = 0; i < array.length; i++) { if (array[i] == key) return i; } return -1; } public static int binarySearchRecursively(double[] sortedArray, double key) { return binarySearchRecursively(sortedArray, key, 0, sortedArray.length); } private static int binarySearchRecursively (double[] sortedArray, double key, int low, int high) { int middle = (low + high) / 2;
3.1. Encuentra la raíz de la ecuación
Desafío:Encuentra la raíz de la ecuación
en el segmento [0; 10] con una precisión en
x no peor que 0.001. Se sabe que la raíz es única en este intervalo.
Use el
método de reducción a la
mitad (y la recursividad) para esto.
Solución:
Nota: si queremos lograr la precisión requerida no en
x , en
y , entonces la condición en el método debe reescribirse:
if(Math.abs(func(start)- func(end)) <= 0.001){ return start; }
Un pequeño truco: considerando que el conjunto de valores dobles es finito (hay dos valores adyacentes entre los cuales no hay valores dobles), reescribe la condición para salir de la recursividad de la siguiente manera:
double x = start + (end - start) / 2; if(x == end || x == start){ return x; }
Por lo tanto, obtenemos la máxima precisión que generalmente se puede obtener con este enfoque.
3.2. Árbol de búsqueda binaria
Implementar un
árbol de búsqueda binario es una tarea excelente. Generalmente lo doy cuando se trata de recursividad.
No escribiré mucho sobre esto, hay muchos artículos / implementaciones de un tipo muy diferente:
Estructuras de datos: árboles binarios.Árbol binario, implementación rápidaImplementación de Java de un árbol binario hashHerencia
4.0. Implemente una jerarquía de clases que describa formas tridimensionales.
Desafío:Implemente una jerarquía de clases:

La clase
Box es un contenedor; puede contener otras formas. El método
add () toma forma como entrada. Necesitamos agregar nuevas formas hasta que obtengamos espacio para ellas en la Caja (solo consideraremos el volumen, ignorando la forma. Supongamos que vertimos líquido). Si no hay suficiente espacio para agregar una nueva forma, entonces el método debería devolver
falso .
Solución: class Shape { private double volume; public Shape(double volume) { this.volume = volume; } public double getVolume() { return volume; } } class SolidOfRevolution extends Shape { private double radius; public SolidOfRevolution(double volume, double radius) { super(volume); this.radius = radius; } public double getRadius() { return radius; } } class Ball extends SolidOfRevolution {
Para no volver a esta tarea, a continuación se describen varias variaciones más de esta tarea.
4.1. Implemente una jerarquía de clases que describa formas tridimensionales - 2
Desafío:Implemente la misma jerarquía de clases, pero haga algunas clases abstractas.
Solución: abstract class Shape { public abstract double getVolume(); } abstract class SolidOfRevolution extends Shape { protected double radius; public SolidOfRevolution(double radius) { this.radius = radius; } public double getRadius() { return radius; } } class Ball extends SolidOfRevolution {
4.2. Implemente una jerarquía de clases que describa formas tridimensionales: 3
Desafío:Implemente la misma jerarquía de clases, pero utilizando interfaces.
Además, se alienta a los estudiantes a implementar la interfaz
Comparable .
Solución: interface Shape extends Comparable<Shape>{ double getVolume(); @Override default int compareTo(Shape other) { return Double.compare(getVolume(), other.getVolume()); } } abstract class SolidOfRevolution implements Shape { protected double radius; public SolidOfRevolution(double radius) { this.radius = radius; } public double getRadius() { return radius; } } class Ball extends SolidOfRevolution {
4.3. Implemente una jerarquía de clases que describa formas tridimensionales - 4
Desafío:Agregue una forma de rotación a la jerarquía de clases para una función arbitraria. Puede calcular el volumen aproximado utilizando una determinada integral. Dado que el volumen de la figura de rotación alrededor del eje
x es
Y la integral es
Luego puede escribir una implementación del
método de rectángulo :
class SolidRevolutionForFunction extends SolidOfRevolution { private Function<Double, Double> function; private double a; private double b; public SolidRevolutionForFunction( Function<Double, Double> function, double a, double b) { super(b - a); this.function = function; this.a = a; this.b = b; } @Override public double getVolume() { double sum = 0; int iterations = 10000; double delta = (b - a)/iterations; for(int i = 0; i < iterations; i++){ double x = a + ((b - a) * i/iterations); sum += Math.pow(function.apply(x), 2) * delta; } return Math.PI * sum; } }
public static void main(String[] args) { Shape shape = new SolidRevolutionForFunction(new Function<Double, Double>() { @Override public Double apply(Double x) { return Math.cos(x); } }, 0, 10); System.out.println(shape.getVolume()); }
Por supuesto, no tenemos en cuenta la precisión de los cálculos aquí, y no seleccionamos el número de particiones para lograr la precisión necesaria, pero esta es una tarea de programación, no métodos numéricos, por lo que omitimos esto en el aula.
Líneas
Puede encontrar muchas tareas por línea. Generalmente doy estos:
- Escriba un método para encontrar la cadena más larga en una matriz.
- Escriba un método que verifique si una palabra es un palíndromo .
- Escriba un método que reemplace en el texto todas las apariciones de la palabra
Bulk "byaka" con "[recortar
censurado] ". - Hay dos lineas. Escriba un método que devuelva el número de ocurrencias de una fila en otra.
No describiré las soluciones a tales problemas, y también hay una gran cantidad de tareas para las cadenas.
De los más interesantes, me gusta este:
5.0. Diccionario de frecuencias de letras del alfabeto ruso (o inglés).
Desafío:Construya un diccionario de frecuencia de letras del alfabeto ruso (o inglés). Omitimos el problema de elegir y analizar el cuerpo del lenguaje, será suficiente para tomar el texto de corta duración).
Solución: void buildDictionaryWithMap(String text){ text = text.toLowerCase(); Map<Character, Integer> map = new HashMap<>(); for(int i = 0; i < text.length(); i++){ char ch = text.charAt(i);
Más o menos:
void buildDictionary(String text){ text = text.toLowerCase(); int[] result = new int['' - '' + 1]; for(int i = 0; i < text.length(); i++){ char ch = text.charAt(i); if(ch >= '' && ch <= ''){ result[ch - '']++; } } for(int i = 0; i < result.length; i++){ System.out.println((char) (i + '') + " = " + result[i]); } }
Clases abstractas e interfaces
6.0. Convertidor de temperatura
Desafío:Escriba una clase de BaseConverter para convertir de grados
Celsius a
Kelvin ,
Fahrenheit , etc. El método debe tener un método de
conversión , que
y hace la conversión.
Solución: interface Converter { double getConvertedValue(double baseValue); } class CelsiusConverter implements Converter { @Override public double getConvertedValue(double baseValue) { return baseValue; } } class KelvinConverter implements Converter { @Override public double getConvertedValue(double baseValue) { return baseValue + 273.15; } } class FahrenheitConverter implements Converter { @Override public double getConvertedValue(double baseValue) { return 1.8 * baseValue + 32; } } public class Main { public static void main(String[] args) { double temperature = 23.5; System.out.println("t = " + new CelsiusConverter().getConvertedValue(temperature)); System.out.println("t = " + new KelvinConverter().getConvertedValue(temperature)); System.out.println("t = " + new FahrenheitConverter().getConvertedValue(temperature)); } }
Además, puede solicitar implementar un método de fábrica , algo como esto: interface Converter { double getConvertedValue(double baseValue); public static Converter getInstance(){ Locale locale = Locale.getDefault(); String[] fahrenheitCountries = {"BS", "US", "BZ", "KY", "PW"}; boolean isFahrenheitCountry = Arrays.asList(fahrenheitCountries).contains(locale.getCountry()); if(isFahrenheitCountry){ return new FahrenheitConverter(); } else { return new CelsiusConverter(); } } }
6.1. Stringbuilder con soporte para deshacer
Tarea:escriba su clase StringBuilder con soporte para la operación de deshacer . Para hacer esto, delegue todos los métodos al StringBuilder estándar , y en su propia clase almacene una lista de todas las operaciones para que se ejecute undo () . Esta será la implementación de la plantilla del equipo .Solución: class UndoableStringBuilder { private interface Action{ void undo(); } private class DeleteAction implements Action{ private int size; public DeleteAction(int size) { this.size = size; } public void undo(){ stringBuilder.delete( stringBuilder.length() - size, stringBuilder.length()); } } private StringBuilder stringBuilder;
6.2. Statebuilding Stringbuilder (patrón de observador)
Tarea:escriba su clase StringBuilder, con la capacidad de notificar a otros objetos de un cambio en su estado. Para hacer esto, delegue todos los métodos al StringBuilder estándar e implemente el patrón de diseño de Observer en su propia clase .Solución: interface OnStringBuilderChangeListener { void onChange(OvservableStringBuilder stringBuilder); } class OvservableStringBuilder {
6.3. Filtro
Problema:Método de escritura de filtro , que recibe la matriz de entrada (de cualquier tipo), y la aplicación de la interfaz del filtro método c aplicar (Object o) , para eliminar de la matriz superfluoVerifique cómo funciona en cadenas u otros objetos.Solución: por logeneral, doy esta tarea antes que los genéricos, para que los estudiantes escriban un método sin ellos usando Object: interface Filter { boolean apply(Object o); } public class Main { public static Object[] filter(Object[] array, Filter filter) { int offset = 0; for(int i = 0; i< array.length; i++){ if(!filter.apply(array[i])){ offset++; } else{ array[i - offset] = array[i]; } }
Pero, es posible con genéricos. Entonces puede usar la función estándar : public class Main { public static <T> T[] filter(T[] array, Function<? super T, Boolean> filter) { int offset = 0; for (int i = 0; i < array.length; i++) { if (!filter.apply(array[i])) { offset++; } else { array[i - offset] = array[i]; } }
6.4. Llenado de matriz
Una tarea algo similar a la anterior:escriba un método de relleno que acepte una matriz de objetos y una implementación de la interfaz Function (o la suya propia).El método de relleno debe llenar la matriz, obteniendo el nuevo valor por índice utilizando la implementación de la interfaz de función. Es decir, quieres usarlo así: public static void main(String[] args) { Integer[] squares = new Integer[100]; fill(squares, integer -> integer * integer);
Solución: public static <T> void fill(T[] objects, Function<Integer, ? extends T> function) { for(int i = 0; i < objects.length; i++){ objects[i] = function.apply(i); } }
Colecciones
7.0 Diccionario de frecuencia de palabras
vea el problema sobre el diccionario de frecuencias de letras del alfabeto7.1. Colección sin duplicados
Tarea:escriba un método que reciba una colección de objetos como entrada y devuelva una colección sin duplicados.Solución: public static <T> Collection<T> removeDuplicates(Collection<T> collection) { return new HashSet<>(collection);
7.2. ArrayList y LinkedList
Escriba un método que agregue 1,000,000 de elementos a una ArrayList y LinkedList. Escriba otro método que seleccione un elemento al azar 100,000 veces de la lista completa. Mida el tiempo dedicado a ello. Compare los resultados y sugiera por qué lo son.Solución: public static void compare2Lists() { ArrayList<Double> arrayList = new ArrayList<>(); LinkedList<Double> linkedList = new LinkedList<>(); final int N = 1000000; final int M = 1000; for (int i = 0; i < N; i++) { arrayList.add(Math.random()); linkedList.add(Math.random()); } long startTime = System.currentTimeMillis(); for (int i = 0; i < M; i++) { arrayList.get((int) (Math.random() * (N - 1))); } System.out.println(System.currentTimeMillis() - startTime); startTime = System.currentTimeMillis(); for (int i = 0; i < M; i++) { linkedList.get((int) (Math.random() * (N - 1))); } System.out.println(System.currentTimeMillis() - startTime); }
7.3. Escribir un iterador sobre una matriz
Solución: class ArrayIterator<T> implements Iterator<T>{ private T[] array; private int index = 0; public ArrayIterator(T[] array) { this.array = array; } @Override public boolean hasNext() { return index < array.length; } @Override public T next() { if(!hasNext()) throw new NoSuchElementException(); return array[index++]; } }
7.4. Iterador de matriz bidimensional
Tarea:escriba un iterador sobre una matriz bidimensional.Solución: class Array2d<T> implements Iterable<T>{ private T[][] array; public Array2d(T[][] array) { this.array = array; } @Override public Iterator<T> iterator() { return new Iterator<T>() { private int i, j; @Override public boolean hasNext() { for(int i = this.i; i< array.length; i++){ for(int j = this.j; j< array[i].length; j++){ return true; } } return false; } @Override public T next() { if(!hasNext()) throw new NoSuchElementException(); T t = array[i][j]; j++; for(int i = this.i; i< array.length; i++){ for(int j = (i == this.i ? this.j : 0); j< array[i].length; j++){ this.i = i; this.j = j; return t; } } return t; } }; } }
7.5. Un iterador aún más complejo
Me gusta esta tarea Solo llega a unos pocos estudiantes en el grupo que son relativamente fáciles de hacer frente a tareas anteriores.Tarea:Dan iterador. El método next () devuelve un String o un iterador de la misma estructura (es decir, que nuevamente devuelve String o el mismo iterador). Escriba en la parte superior de este iterador otro, ya "plano".Solución en las pilas: public class DeepIterator implements Iterator<String> { private Stack<Iterator> iterators; private String next; private boolean hasNext; public DeepIterator(Iterator<?> iterator) { this.iterators = new Stack<Iterator>(); iterators.push(iterator); updateNext(); } @Override public boolean hasNext() { return hasNext; } private void updateNext(){ if(iterators.empty()){ next = null; hasNext = false; return; } Iterator current = iterators.peek(); if (current.hasNext()) { Object o = current.next(); if (o instanceof String) { next = (String) o; hasNext = true; } else if (o instanceof Iterator) { Iterator iterator = (Iterator) o; iterators.push(iterator); updateNext(); } else { throw new IllegalArgumentException(); } } else { iterators.pop(); updateNext(); } } @Override public String next() throws NoSuchElementException { if(!hasNext){ throw new NoSuchElementException(); } String nextToReturn = next; updateNext(); return nextToReturn; } @Override public void remove() { throw new UnsupportedOperationException(); } }
La solución recursiva: class DeepIterator implements Iterator<String> { private Iterator subIter; private DeepIterator newIter; public DeepIterator(Iterator iniIter) { this.subIter = iniIter; } @Override public boolean hasNext() { if (subIter.hasNext()) return true; if (newIter != null) return newIter.hasNext(); return false; } @Override public String next() { if(!hasNext()) throw new NoSuchElementException(); Object obj = null; if (newIter != null && newIter.hasNext()) obj = newIter.next(); if (subIter.hasNext() && obj == null) { obj = subIter.next(); if (obj instanceof Iterator && ((Iterator) obj).hasNext()) { newIter = new DeepIterator((Iterator) obj); } } if(obj instanceof Iterator){ obj = next(); } return (String) obj; } }
7.6. Iterador sobre dos iteradores
Tarea:escriba un iterador que pase por dos iteradores.Solución: class ConcatIterator<T> implements Iterator<T> { private Iterator<T> innerIterator1; private Iterator<T> innerIterator2; public ConcatIterator (Iterator<T> innerIterator1, Iterator<T> innerIterator2) { this.innerIterator1 = innerIterator1; this.innerIterator2 = innerIterator2; } @Override public boolean hasNext() { while (innerIterator1.hasNext()) return true; while (innerIterator2.hasNext()) return true; return false; } @Override public T next() { if(!hasNext()) throw new NoSuchElementException(); while (innerIterator1.hasNext()) return innerIterator1.next(); while (innerIterator2.hasNext()) return innerIterator2.next(); return null; } }
7.7. Elementos de conteo
Escriba un método que reciba una matriz de elementos de tipo K (genérico) como entrada y devuelva Map <K, Integer>, donde K es el valor de la matriz e Integer es el número de entradas en la matriz.Es decir, la firma del método se ve así: <K> Map<K, Integer> arrayToMap(K[] ks);
Solución: public static <K> Map<K, Integer> countValues(K[] ks) { Map<K, Integer> map = new HashMap<>(); for (K k : ks) { map.compute(k, new BiFunction<K, Integer, Integer>() { @Override public Integer apply(K k, Integer count) { return count == null ? 1 : count + 1; } }); } return map; }
7.8. Cambiar claves y valores en Mapa
Escriba un método que reciba Map <K, V> y devuelva Map, donde se invierten las claves y los valores. Como los valores pueden coincidir, el tipo de valor en Map ya no será K , pero Colección <K>:
Map<V, Collection<K>>
Solución: public static <K, V> Map<V, Collection<K>> inverse(Map<? extends K, ? extends V> map){ Map<V, Collection<K>> resultMap = new HashMap<>(); Set<K> keys = map.keySet(); for(K key : keys){ V value = map.get(key); resultMap.compute(value, (v, ks) -> { if(ks == null){ ks = new HashSet<>(); } ks.add(key); return ks; }); } return resultMap; }
Multithreading
8.0. Estados
Tarea:Imprima el estado de la secuencia antes de que comience, después de que comience y en tiempo de ejecución.Solución: Thread thread = new Thread() { @Override public void run() { System.out.println(getState()); } }; System.out.println(thread.getState()); thread.start(); try {
Añadir ESPERA y BLOQUEADO: public static void main(String[] strings) throws InterruptedException { Object lock = new Object(); Thread thread = new Thread() { @Override public void run() { try { synchronized (lock) { lock.notifyAll(); lock.wait(); } } catch (InterruptedException e) { e.printStackTrace(); } } }; synchronized (lock){ thread.start();
Para TIMED_WAITING, cambie el mismo código un poco: public static void main(String[] strings) throws InterruptedException { Object lock = new Object(); Thread thread = new Thread() { @Override public void run() { try { synchronized (lock) { lock.notifyAll(); lock.wait(3000); } } catch (InterruptedException e) { e.printStackTrace(); } } }; synchronized (lock) { thread.start();
8.1. Hilo de sincronización
Tarea:escriba un programa en el que se creen dos hilos que muestren su nombre en la consola.Solución: class StepThread extends Thread {
8.2. Fabricante-consumidor
Una de las tareas clásicas de subprocesos múltiples. Dadas dos corrientes: productor y consumidor. El fabricante genera algunos datos (en el ejemplo, números). El fabricante los "consume".Dos secuencias comparten un búfer de datos común, cuyo tamaño es limitado. Si el búfer está vacío, el consumidor debe esperar a que los datos aparezcan allí. Si el búfer está lleno, el fabricante debe esperar hasta que el consumidor tome los datos y el lugar quede libre.Fabricante:
Consumidor:
Crea y ejecuta: public static void main(String[] strings) { LinkedList<Double> sharedQueue = new LinkedList<>(); int size = 4; Thread prodThread = new Thread(new Producer(sharedQueue, size), "Producer"); Thread consThread = new Thread(new Consumer(sharedQueue), "Consumer"); prodThread.start(); consThread.start(); }
9.0. Anotación propia: creación y uso
Normalmente doy esta tarea cuando se trata de anotaciones y reflexiones. Al mismo tiempo, puede hablar sobre Ejecutores , ThreadPoolExecutor y otros.Tarea:Cree su anotación de repetición con un parámetro entero.Extender clase ThreadPoolExecutor y reemplazar el método ejecuta como sigue: si una instancia de Ejecutable se anota con la repetición , entonces su método de ejecución se ejecuta varias veces (el número especificado por el parámetro en la repetición ).Es decir, escribiendo esta clase: @Repeat(3) class MyRunnable implements Runnable{ @Override public void run() { System.out.println("Hello!"); } }
y usándolo: public static void main(String[] strings) { CustomThreadPoolExecutor customThreadPoolExecutor = new CustomThreadPoolExecutor(10); customThreadPoolExecutor.execute(new MyRunnable()); }
Deberíamos ver: Hello! Hello! Hello!
Solución: @Retention(RetentionPolicy.RUNTIME) @interface Repeat { int value(); } class CustomThreadPoolExecutor extends ThreadPoolExecutor { public CustomThreadPoolExecutor(int corePoolSize) {
Tareas finales y otras
Durante el curso, les doy a los estudiantes varias tareas difíciles, para toda la lección. Se requiere escribir un pequeño programa usando lo aprendido previamente. Por cierto, la complejidad a menudo surge aquí. La solución a los problemas de escribir un método es una cosa, y para encontrar un algoritmo, recuerde todo lo que estudió anteriormente y también escriba 50 líneas en Java de inmediato es completamente diferente. Pero en la lección, puedo empujarlos en la dirección correcta, ayudar a resolver problemas, degradar, encontrar las clases y métodos correctos, etc. Varias de estas tareas se describen a continuación. De esta forma, se los doy a mis alumnos.Además, al final del curso, todos deben completar la tarea final. Es decir, en casa, por su cuenta, escriba un programa. Un poco mas complicado. Te doy la oportunidad de elegir una de varias opciones. Por cierto, un hecho interesante es que necesita escribir al menos un programa, o puede escribir varios a la vez. Parece que solo recuerdo una persona que escribió más de una.10,0 Número de restricciones viales
Una pequeña tarea que demuestra cómo se puede aplicar Java para resolver problemas prácticos.Preparación de datos:desde el portal de datos abierto de San Petersburgo, cargamos datos sobre la restricción del tráfico para el período de producción en formato csv .Tarea:Se requiere determinar cuántas restricciones de carreteras estaban vigentes en la ciudad en una fecha determinada.El programa toma dos parámetros como argumento:- Ruta al archivo de datos
- Fecha
es decir, comienza de la siguiente manera: java TrafficBlocks "PATH_TO_CSV_FILE" dd.MM.yyyy
Es necesario deducir el número de restricciones de tráfico aplicables en esta fecha.Algoritmo ejemplarPor cierto, durante todo el tiempo solo una persona notó que el formato de fecha en los datos (aaaaMMdd) es tal que no se pueden analizar, sino que se comparan como cadenas. Entonces la solución puede simplificarse. Doy esta tarea después de hablar sobre Date, Calendar, DateFormat , así que estoy hablando de esta simplificación cuando ya escribieron todo.No traigo una solución aquí, pueden ser muy diferentes y cada una debe considerarse de forma individual.10.1Búsqueda de Wikipedia. En el programa de consola
Tarea:escriba un programa que lea la consulta de búsqueda desde la consola y muestre el resultado de la búsqueda en Wikipedia. La tarea se divide en 4 etapas:- Leer solicitud
- Hacer una solicitud al servidor
- Analiza la respuesta
- Imprimir resultado
Los puntos primero y cuarto no necesitan mucha explicación, detengámonos en la solicitud al servidor.Esta tarea también se puede dividir en varias etapas:- Solicitud de generación
- Solicitud del servidor
- Preparación para el procesamiento de respuestas
- Procesamiento de respuesta
Consideremos esto con más detalle: lageneración de solicitudes deAPI proporciona la capacidad de realizar consultas de búsqueda sin claves. De esta manera, aproximadamente: https://ru.wikipedia.org/w/api.php?action=query&list=search&utf8=&format=json&srsearch="Java"
Puede abrir este enlace en un navegador y ver el resultado de la solicitud.Sin embargo, para que la solicitud tenga éxito, debe eliminar los caracteres no válidos del enlace, es decir, realizar la codificación porcentual , que también es codificación de URL.Para hacer esto, en Java, puede usar el método de codificación estática en la clase URLEncoder , así: street = URLEncoder.encode(street, "UTF-8");
Eso es todo, la URL está lista! Queda ahora hacer una solicitud al servidor ...Solicitud al servidorPara las solicitudes GET y POST, puede usar la clase HttpURLConnection . Este es el más simple. Simplemente cree, abra una conexión y obtenga un InputStream . Ni siquiera tenemos que leerlo, Gson lo hará por nosotros .También puede usar la modificación , o algo similar.Preparación para procesar la respuestaEl servidor devuelve datos en formato JSON .Pero no necesitamos analizarlo manualmente, para esto hay una biblioteca Gson de Google.Los ejemplos están aquí:https://github.com/google/gsonhttps://habrahabr.ru/company/naumen/blog/228279/Si queda tiempo, puede escribir el recibo del artículo seleccionado durante la búsqueda, etc.10.2 Tarea final: utilidad de consola para descargar archivos a través de HTTP
Utilidad de consola para descargar archivos a través de HTTP ... ¿te suena familiar? Sí, esta es la historia de una tarea de prueba . Todo es lógico: la tarea final del curso de Java está al mismo nivel que la tarea de prueba para el puesto de desarrollador Junior Java.Y esta es una tarea realmente buena, sin complicaciones, pero cubre una amplia variedad de temas. Y puede ver de inmediato cómo el autor estructura el código, usa diferentes enfoques y patrones, usa el lenguaje en sí y la biblioteca estándar.10.3 Tarea final - weather Telegram-bot
Tarea:escriba un bot para Telegram, que será:- .
- , . /subscribe. (/unsubscribe).
Puede usar https://openweathermap.org/api para obtener el pronóstico .Esta tarea se basa más bien en la capacidad y la capacidad de comprender la nueva tecnología (bot-api) y diferentes bibliotecas. ¡Y necesitas configurar una VPN! Y tienes que escribir el código, por supuesto.Por cierto, un hecho interesante es que la mayoría de los estudiantes ignoran la frase "ubicación enviada" y la posibilidad de enviarla. Escriben un bot que espera el nombre de la ciudad. No se porque. Esto a menudo funciona mal , el código se vuelve un poco más complicado, pero continúan haciéndolo.10.4 Tarea final: reconocimiento de escritura a mano
Objetivo:Implementar un programa para clasificar números escritos a mano.Esta tarea ya está más enfocada en la implementación del algoritmo, la capacidad de comprenderlos. Por lo general, el código para los estudiantes no está muy estructurado.Descripción de la tareaComo conjunto de datos a estudiar, se utilizará la base de las imágenes de dígitos manuscritos MNIST . Las imágenes en esta base de datos tienen una resolución de 28x28 y se almacenan como un conjunto de valores de escala de grises. La base de datos completa se divide en dos partes: capacitación, que consta de 50,000 imágenes, y prueba - 10,000 imágenes.Para resolver este problema, se propone implementar el método de k vecinos más cercanos- Algoritmo métrico para la clasificación automática de objetos. El principio básico del método de los vecinos más cercanos es que el objeto se asigna a la clase que es más común entre los vecinos de este elemento.Los vecinos se toman en función de muchos objetos cuyas clases ya se conocen y, en función del valor clave de k para este método, se calcula qué clase es la más numerosa entre ellas. Como la distancia entre objetos, puede usar la métrica euclidiana, es decir, la distancia habitual entre puntos en el espacio.RequisitosDebe escribir un programa que reconozca los números escritos a mano. Debería ser posible inicializar una determinada clase con datos para el entrenamiento y proporcionar un método para reconocer una sola imagen.Además de la implementación del algoritmo en sí, debe escribir código para verificar su precisión (calcular la tasa de error). Para hacer esto, use 10,000 imágenes de prueba.Además de calcular la precisión, se propone realizar un experimento: en lugar de la métrica euclidiana, use la distancia de las manzanas de la ciudad , el ángulo entre vectores u otra cosa, y verifique la calidad del reconocimiento.OpcionalSi todo funciona bien, entonces puede complicar la tarea un poco más. Al agregar, por ejemplo, la eliminación del ruido (emisiones) o el uso del método de ventana Parzenovsky para aumentar la precisión.Unas pocas palabras mas
Si tiene tareas interesantes que puede ofrecer a los estudiantes (el tiempo aproximado de solución es una o dos horas), compártalas en los comentarios.