Tâches pratiques Java - pour les cours et autres activités
Quelques mots d'introduction
Depuis quelques années, j'enseigne la programmation Java. Au fil du temps, cela a changé - parfois différentes parties ont été ajoutées ou supprimées, la séquence des sujets a changé, l'approche pour construire un plan des classes elles-mêmes a changé, et ainsi de suite. Autrement dit, le cours a été amélioré. L'un des principaux problèmes rencontrés lors de la préparation du cours concerne les tâches. À leur sujet et seront discutés.
Le fait est que chacune de mes classes se compose de deux parties. Au début, j'agis en tant que conférencier - je vous dis avec des exemples de code sur un nouveau sujet (classes, héritage, génériques, etc.). La deuxième partie est pratique. Évidemment, cela n'a aucun sens de simplement parler de programmation, vous devez programmer. La priorité en classe est la résolution de problèmes, c'est-à-dire la programmation de quelque chose. La programmation en classe est différente de la programmation à la maison, car en classe, vous pouvez poser une question, montrer le code, obtenir une évaluation rapide du code, des commentaires sur l'amélioration, la correction de l'écrit. Il était très facile de trouver des tâches pour les toutes premières leçons. Tâches pour les boucles, les instructions conditionnelles et la POO (par exemple, écrivez la classe "Dog" ou la classe "Vector"). Des services comme
leetcode vous permettent même de vérifier l'exactitude de la résolution de tels problèmes immédiatement, en ligne. Mais quelles tâches donner aux élèves dans une leçon consacrée aux collections? Streams? Et les annotations? Pendant plusieurs années, j'ai proposé ou révisé plusieurs de ces tâches, et cet article, en fait, est une collection de ces problèmes (une solution est attachée à certains problèmes).
Bien sûr, toutes les tâches sont déjà apparues quelque part. Cependant, cet article s'adresse aux enseignants de cours de programmation (pour les langages similaires à Java, la plupart des tâches le feront), ou à ceux qui enseignent la programmation en privé. Ces tâches peuvent être utilisées «prêtes à l'emploi» dans vos cours. Les apprenants Java peuvent également essayer de les résoudre. Mais de telles décisions nécessitent une vérification et une évaluation par un tiers.
Certaines des tâches les plus simples que tout le monde utilise depuis des décennies, j'ai également inclus dans cet article. Peut-être, afin de ne pas commencer immédiatement avec des classes abstraites.
Toutes les idées et suggestions sont les bienvenues!
Liste des tâches
Les bases
1.0. Maximum, minimum et moyenne1.1 Tri par tableau1.2 Trouver des nombres premiers1.3 Suppression d'une baieBases de la POO
2.0 Concevoir et créer une classe qui décrit un vecteur2.1 Génération d'un élément aléatoire avec un poids2.2 Liste liéeRécursivité
3.0 Recherche binaire3.1 Trouver la racine de l'équation3.2 Arbre de recherche binaireHéritage
4.0 Implémenter une hiérarchie de classes qui décrit les formes tridimensionnelles4.1 Mettre en œuvre une hiérarchie de classes décrivant des figures en trois dimensions - 24.2 Mettre en œuvre une hiérarchie de classes qui décrit des formes tridimensionnelles - 34.3 Implémenter une hiérarchie de classes décrivant des formes tridimensionnelles - 4Lignes
5.0 Dictionnaire de fréquence des lettresClasses abstraites et interfaces
6.0. Convertisseur de température6.1. Stringbuilder avec support d'annulation6.2. Statebuilder Stringbuilder (modèle d'observateur)6.4. Remplir un tableau avec FunctionLes collections
7.0 Dictionnaire de fréquence des mots7.1. Collection sans doublons7.2. ArrayList et LinkedList7.3. Écrire un itérateur sur un tableau7.4. Écrire un itérateur sur un tableau à deux dimensions7.5. Un itérateur encore plus complexe7.6. Itérateur sur deux itérateurs7.7. Compter les éléments7.8. Modifier les clés et les valeurs dans la carteMultithreading
8.0. États8.1. Synchronisation des threads8.2. Fabricant grand publicAnnotations
9.0. Annotation personnalisée - création et utilisation avec réflexion10,0 Nombre de restrictions routières10.1. Recherche Wikipedia. Dans le programme de la console10.2. Tâche finale - utilitaire de console pour télécharger des fichiers via HTTP10.3. Dernière tâche - Météo Telegram-Bot10.4. Tâche finale - reconnaissance de l'écriture manuscriteLes bases
1.0. Maximum, minimum et moyenne
Défi:Remplissez le tableau avec des nombres aléatoires et imprimez la valeur maximale, minimale et moyenne.
Pour générer un nombre aléatoire, utilisez la méthode
Math.random () , qui renvoie une valeur dans l'intervalle [0, 1].
Solution: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. Implémenter un algorithme de tri à bulles pour trier un tableau
Solution: 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. Recherche de nombres premiers
Défi:Écrivez un programme qui imprime des nombres premiers sur la console entre [2, 100].
Utilisez l'opérateur
% (reste de la division) et les boucles pour résoudre ce problème.
Solution: 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); } }
Ou en utilisant des
boucles d'étiquette :
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. Supprimer du tableau
Défi:Étant donné un tableau d'entiers et un autre entier. Supprimez toutes les occurrences de ce nombre du tableau (il ne doit pas y avoir d'espace).
Solution: 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]; } }
Vous pouvez écrire vous-même une méthode pour couper la queue du tableau, mais il convient de noter que la méthode standard fonctionnera plus rapidement:
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; }
Cependant, si vous procédez de cette façon, vous pouvez d'abord créer un tableau de la longueur souhaitée, puis le remplir:
public static int[] removeElement(int[] nums, int val) { int count = 0;
2.0. Conception et création d'une classe vectorielle
Défi:Créez une classe qui décrit un vecteur (dans un espace en trois dimensions).
Il doit avoir:
Si la méthode renvoie un vecteur, elle doit renvoyer un nouvel objet et ne pas changer celui de base. Autrement dit, vous devez implémenter le modèle "
Objet immuable "
Solution: public class Vector {
Vous pouvez utiliser cette classe comme ceci:
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])); }
Vous pouvez généraliser cette solution et écrire la classe Vector pour une dimension arbitraire:
public class Vector {
2.1. Générer un élément aléatoire avec un poids
Défi:Écrivez une classe dont le constructeur prend deux tableaux: un tableau de valeurs et un tableau de poids de valeurs.
La classe doit contenir une méthode qui renverra un élément du premier tableau de façon aléatoire, en tenant compte de son poids.
Un exemple:
Un tableau est donné [1, 2, 3] et un tableau de poids [1, 2, 10].
En moyenne, la valeur
«1» devrait renvoyer 2 fois moins que la valeur
«2» et dix fois moins souvent que la valeur
«3» .
Solution: class RandomFromArray { private int[] values;
Mais, puisque le tableau des
plages est trié, vous pouvez (et devez) utiliser une recherche binaire:
public int getRandom() { int random = (int) (Math.random() * (sum - 1)); int index = Arrays.binarySearch(ranges, random); return values[index >= 0 ? index : -index - 2]; }
Il existe une autre solution à ce problème. Vous pouvez créer un tableau dont la taille est égale à la somme de tous les poids. Ensuite, le choix d'un élément aléatoire revient à générer un indice aléatoire. Autrement dit, si un tableau de valeurs est donné [1, 2, 3] et un tableau de poids [1, 2, 10], vous pouvez immédiatement créer un tableau de [1, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3] et en extraire un élément aléatoire:
class RandomFromArray { private int[] extended_values;
Cette solution a un avantage - le temps d'extraire un élément aléatoire
O (1) , contrairement à log (n) dans la solution précédente. Cependant, cela nécessite beaucoup de mémoire:
O( sumn)
2.2. Liste liée
Une autre tâche que je donne souvent est la mise en place d'une liste chaînée. Il peut être donné sous sa forme la plus simple (implémenter
add () et
get () uniquement ), ou vous pouvez demander d'implémenter
java.util.List .
Je ne m'attarderai pas là-dessus en détail, il existe de nombreux articles sur l'implémentation d'une liste chaînée en Java dans Habr, par exemple celui-ci:
Structures de données en images. LinkedListDéfi:Écrivez une méthode qui vérifie si l'élément donné est dans le tableau ou non.
Utilisez l'énumération et la recherche binaire pour résoudre ce problème.
Comparez le temps d'exécution des deux solutions pour les grandes baies (par exemple 100000000 éléments).
Solution: 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. Trouver la racine de l'équation
Défi:Trouver la racine de l'équation
c o s ( x 5 ) + x 4 - 345 , 3 ∗ x - 23 = 0
sur le segment [0; 10] avec une précision en
x non inférieure à 0,001. Il est connu que la racine est unique dans cet intervalle.
Utilisez la
méthode de réduction de moitié (et la récursivité) pour cela.
Solution:
Remarque: si nous voulons atteindre la précision requise non pas en
x , en
y , alors la condition dans la méthode doit être réécrite:
if(Math.abs(func(start)- func(end)) <= 0.001){ return start; }
Une petite astuce: considérant que l'ensemble des valeurs doubles est fini (il y a deux valeurs adjacentes entre lesquelles il n'y a pas de valeurs doubles), réécrivez la condition pour quitter la récursion comme suit:
double x = start + (end - start) / 2; if(x == end || x == start){ return x; }
Ainsi, nous obtenons la précision maximale qui peut généralement être obtenue en utilisant cette approche.
3.2. Arbre de recherche binaire
L'implémentation d'un
arbre de recherche binaire est une excellente tâche. Je le donne généralement quand il s'agit de récursivité.
Je n'écrirai pas beaucoup à ce sujet, il existe de nombreux articles / implémentations d'un type très différent:
Structures de données: arbres binaires.Arbre binaire, mise en œuvre rapideImplémentation Java d'une arborescence binaire hachéeHéritage
4.0. Implémenter une hiérarchie de classes qui décrit des formes tridimensionnelles
Défi:Implémentez une hiérarchie de classes:

La classe
Box est un conteneur; elle peut contenir d'autres formes. La méthode
add () prend Shape en entrée. Nous devons ajouter de nouvelles formes jusqu'à ce que nous prenions de la place pour elles dans la boîte (nous ne considérerons que le volume, en ignorant la forme. Supposons que nous versions du liquide). S'il n'y a pas assez d'espace pour ajouter une nouvelle forme, la méthode doit retourner
false .
Solution: 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 {
Afin de ne pas revenir à cette tâche, plusieurs autres variantes de cette tâche sont décrites ci-dessous.
4.1. Implémenter une hiérarchie de classes qui décrit des formes tridimensionnelles - 2
Défi:Implémentez la même hiérarchie de classes, mais rendez certaines classes abstraites.
Solution: 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. Mettre en œuvre une hiérarchie de classes qui décrit des formes tridimensionnelles - 3
Défi:Implémentez la même hiérarchie de classes, mais en utilisant des interfaces.
De plus, les étudiants sont encouragés à implémenter l'interface
comparable .
Solution: 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. Mettre en œuvre une hiérarchie de classes qui décrit des formes tridimensionnelles - 4
Défi:Ajoutez une forme de rotation à la hiérarchie des classes pour une fonction arbitraire. Vous pouvez calculer le volume approximatif en utilisant une certaine intégrale. Étant donné que le volume de la rotation autour de l'axe
x est
V x = p i i n t b a f 2 ( x ) d x
Et l'intégrale est
Ensuite, vous pouvez écrire une implémentation de
la méthode rectangle :
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()); }
Bien sûr, nous ne prenons pas en compte la précision des calculs ici, et nous ne sélectionnons pas le nombre de partitions pour atteindre la précision nécessaire, mais c'est une tâche de programmation, pas des méthodes numériques, donc nous omettons cela en classe.
Lignes
Vous pouvez trouver beaucoup de tâches par ligne. Je donne généralement ces derniers:
- Écrivez une méthode pour trouver la chaîne la plus longue d'un tableau.
- Écrivez une méthode qui vérifie si un mot est un palindrome .
- Écrivez une méthode qui remplace dans le texte toutes les occurrences du mot
Bulk «byaka» par «[cut out]
censuré]. » - Il y a deux lignes. Écrivez une méthode qui renvoie le nombre d'occurrences d'une ligne dans une autre.
Je ne décrirai pas les solutions à de tels problèmes, et il y a aussi un grand nombre de tâches pour les chaînes.
Parmi les plus intéressants, j'aime celui-ci:
5.0. Dictionnaire de fréquence des lettres de l'alphabet russe (ou anglais).
Défi:Créez un dictionnaire de fréquences de lettres de l'alphabet russe (ou anglais). On omet le problème du choix et de l'analyse du corps de la langue, il suffira de prendre le texte de courte durée).
Solution: 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);
Ou alors:
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]); } }
Classes abstraites et interfaces
6.0. Convertisseur de température
Défi:Écrivez une classe BaseConverter pour convertir des degrés
Celsius en
Kelvin ,
Fahrenheit , etc. La méthode doit avoir une méthode de
conversion , qui
et fait la conversion.
Solution: 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)); } }
De plus, vous pouvez demander d'implémenter une méthode d'usine , quelque chose comme ceci: 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 avec support d' annulation
Tâche:écrivez votre classe StringBuilder avec la prise en charge de l'opération d' annulation . Pour ce faire, déléguez toutes les méthodes au StringBuilder standard et, dans votre propre classe, stockez une liste de toutes les opérations à exécuter par undo () . Ce sera la mise en œuvre du modèle d' équipe .Solution: 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. Statebuilder Stringbuilder (modèle d'observateur)
Tâche:écrivez votre classe StringBuilder, avec la possibilité de notifier à d'autres objets un changement de son état. Pour ce faire, déléguez toutes les méthodes au StringBuilder standard et implémentez le modèle de conception Observer dans votre propre classe .Solution: interface OnStringBuilderChangeListener { void onChange(OvservableStringBuilder stringBuilder); } class OvservableStringBuilder {
6.3. Filtrer
Problème:Méthode d' écriture filtre , qui reçoit la matrice d'entrée (de tout type), et la mise en œuvre de l'interface de filtre méthode c appliquer (Object o) , à retirer de la matrice superflu.Vérifiez comment cela fonctionne sur des chaînes ou d'autres objets.Solution:Habituellement, je donne cette tâche avant les génériques, afin que les élèves écrivent une méthode sans eux en utilisant 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]; } }
Mais c'est possible avec les génériques. Ensuite, vous pouvez utiliser la fonction standard : 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. Remplissage de tableau
Une tâche quelque peu similaire à la précédente:écrire une méthode fill qui accepte un tableau d'objets et une implémentation de l'interface Function (ou la vôtre).La méthode fill doit remplir le tableau, obtenant la nouvelle valeur par index en utilisant l'implémentation de l'interface Function. Autrement dit, vous voulez l'utiliser comme ceci: public static void main(String[] args) { Integer[] squares = new Integer[100]; fill(squares, integer -> integer * integer);
Solution: 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); } }
Les collections
7.0 Dictionnaire de fréquence des mots
voir le problème du dictionnaire de fréquence des lettres de l'alphabet7.1. Collection sans doublons
Tâche:écrire une méthode qui reçoit une collection d'objets en entrée et renvoie une collection sans doublons.Solution: public static <T> Collection<T> removeDuplicates(Collection<T> collection) { return new HashSet<>(collection);
7.2. ArrayList et LinkedList
Écrivez une méthode qui ajoute 1 000 000 d'éléments à une liste de tableaux et à une liste de liens. Écrivez une autre méthode qui sélectionne un élément au hasard 100 000 fois dans la liste remplie. Mesurez le temps passé dessus. Comparez les résultats et suggérez pourquoi ils le sont.Solution: 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. Écrire un itérateur sur un tableau
Solution: 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. Itérateur de réseau bidimensionnel
Tâche:écrire un itérateur sur un tableau à deux dimensions.Solution: 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 itérateur encore plus complexe
J'aime cette tâche. Elle ne rejoint que quelques élèves du groupe qui sont relativement faciles à gérer les tâches précédentes.Tâche:Dan itérateur. La méthode next () renvoie une chaîne ou un itérateur de la même structure (c'est-à-dire qui renvoie à nouveau soit une chaîne, soit le même itérateur). Écrivez au-dessus de cet itérateur un autre, déjà «plat».Solution sur les piles: 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 solution récursive: 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. Itérateur sur deux itérateurs
Tâche:écrire un itérateur qui passe par deux itérateurs.Solution: 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. Compter les éléments
Écrivez une méthode qui reçoit un tableau d'entrée d'éléments de type K (générique) et renvoie Map <K, Integer>, où K est la valeur du tableau et Integer est le nombre d'entrées dans le tableau.Autrement dit, la signature de la méthode ressemble à ceci: <K> Map<K, Integer> arrayToMap(K[] ks);
Solution: 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. Modifier les clés et les valeurs dans la carte
Écrivez une méthode qui reçoit Map <K, V> et renvoie Map, où les clés et les valeurs sont inversées. Étant donné que les valeurs peuvent coïncider, le type de valeur dans Map ne sera plus K , mais Collection <K>:
Map<V, Collection<K>>
Solution: 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. États
Tâche:affichez l'état du flux avant son démarrage, après son démarrage et lors de l'exécution.Solution: Thread thread = new Thread() { @Override public void run() { System.out.println(getState()); } }; System.out.println(thread.getState()); thread.start(); try {
Ajouter EN ATTENTE et BLOQUÉ: 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();
Pour TIMED_WAITING, modifiez un peu le même code: 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. Synchronisation des threads
Tâche:Écrivez un programme dans lequel deux threads sont créés et affichent tour à tour leur nom sur la console.Solution: class StepThread extends Thread {
8.2. Fabricant-consommateur
L'une des tâches multithreads classiques. Étant donné deux volets - producteur et consommateur. Le fabricant génère certaines données (dans l'exemple, des nombres). Le fabricant les «consomme».Deux flux partagent un tampon de données commun, dont la taille est limitée. Si le tampon est vide, le consommateur doit attendre que les données y apparaissent. Si la mémoire tampon est pleine, le fabricant doit attendre que le consommateur prenne les données et que l'endroit devienne libre.Constructeur:
Consommateur:
Créer et exécuter: 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. Propre annotation - création et utilisation
Je donne généralement cette tâche en ce qui concerne les annotations et la réflexion. Dans le même temps, vous pouvez parler d' exécuteurs , de ThreadPoolExecutor et d'autres.Tâche:créez votre annotation de répétition avec un paramètre entier.Étendre la classe ThreadPoolExecutor et substituer la méthode exécuter de la manière suivante: si une instance de Runnable est annotée avec la répétition , puis sa méthode run est exécuté plusieurs fois (le nombre spécifié par le paramètre dans la répétition ).Autrement dit, en écrivant cette classe: @Repeat(3) class MyRunnable implements Runnable{ @Override public void run() { System.out.println("Hello!"); } }
et en l'utilisant: public static void main(String[] strings) { CustomThreadPoolExecutor customThreadPoolExecutor = new CustomThreadPoolExecutor(10); customThreadPoolExecutor.execute(new MyRunnable()); }
Il faut voir: Hello! Hello! Hello!
Solution: @Retention(RetentionPolicy.RUNTIME) @interface Repeat { int value(); } class CustomThreadPoolExecutor extends ThreadPoolExecutor { public CustomThreadPoolExecutor(int corePoolSize) {
Tâches finales et autres
Pendant le cours, je donne aux étudiants plusieurs tâches difficiles - pour toute la leçon. Il est nécessaire d'écrire un petit programme en utilisant ce qui a été appris précédemment. Soit dit en passant, la complexité se pose souvent ici. La solution aux problèmes d'écriture d'une méthode est une chose, mais pour trouver un algorithme, rappelez-vous tout ce que vous avez étudié plus tôt et écrivez tout de suite 50 lignes en Java est complètement différent. Mais dans la leçon, je peux les pousser dans la bonne direction, aider à résoudre les problèmes, dégrader, trouver les bonnes classes et méthodes, etc. Plusieurs de ces tâches sont décrites ci-dessous. Sous cette forme, je les donne à mes élèves.De plus, à la fin du cours, tout le monde devrait terminer le travail final. C'est-à-dire, chez vous, par vous-même, écrire un programme. Un peu plus compliqué. Je vous donne la possibilité de choisir parmi plusieurs options. Soit dit en passant, un fait intéressant est que vous devez écrire au moins un programme, ou vous pouvez en écrire plusieurs à la fois. Il semble que je me souvienne d'une seule personne qui en a écrit plus d'une.10,0 Nombre de restrictions routières
Une petite tâche qui montre comment Java peut être appliqué pour résoudre des problèmes pratiques.Préparation des données:À partir du portail de données ouvertes de Saint-Pétersbourg, nous chargeons les données sur la restriction du trafic pour la période de production au format csv .Tâche:Il est nécessaire de déterminer combien de restrictions routières étaient en vigueur dans la ville à une certaine date.Le programme prend deux paramètres en argument:- Chemin d'accès au fichier de données
- Date
c'est-à-dire qu'il commence comme suit: java TrafficBlocks "PATH_TO_CSV_FILE" dd.MM.yyyy
Il est nécessaire de déduire le nombre de restrictions de circulation applicables à cette date.Algorithme exemplaireSoit dit en passant, pendant tout le temps, une seule personne a remarqué que le format de date dans les données (aaaaMMjj) est tel qu'elles ne peuvent pas être analysées, mais comparées en tant que chaînes. La solution peut donc être simplifiée. Je donne cette tâche après avoir parlé de Date, Calendar, DateFormat , donc je parle de cette simplification quand ils ont déjà tout écrit.Je n’apporte pas de solution ici, elles peuvent être très différentes et chacune doit être examinée individuellement.10.1.Recherche Wikipedia. Dans le programme de la console
Tâche:rédigez un programme qui lit la requête de recherche à partir de la console et affiche le résultat de la recherche sur Wikipedia. La tâche est divisée en 4 étapes:- Lire la demande
- Faire une demande au serveur
- Analyser la réponse
- Résultat d'impression
Les premier et quatrième points n'ont pas besoin de beaucoup d'explications, arrêtons-nous sur la demande adressée au serveur.Cette tâche peut également être divisée en plusieurs étapes:- Génération de demande
- Demande du serveur
- Préparation du traitement des réponses
- Traitement des réponses
Examinons cela plus en détail: lagénération de requêtesAPI offre la possibilité de faire des requêtes de recherche sans clés. De cette façon, environ: https://ru.wikipedia.org/w/api.php?action=query&list=search&utf8=&format=json&srsearch="Java"
Vous pouvez ouvrir ce lien dans un navigateur et consulter le résultat de la demande.Cependant, pour que la demande aboutisse, vous devez supprimer les caractères non valides du lien, c'est-à-dire effectuer un encodage en pourcentage , qui est également un encodage d'URL.Pour ce faire, en Java, vous pouvez utiliser la méthode d' encodage statique dans la classe URLEncoder , comme ceci: street = URLEncoder.encode(street, "UTF-8");
Ça y est, l'URL est prête! Il reste maintenant à faire une requête au serveur ...Demande au serveurPour les requêtes GET et POST, vous pouvez utiliser la classe HttpURLConnection . C'est le plus simple. Créez, ouvrez une connexion et obtenez un InputStream . Nous n'avons même pas besoin de le lire, Gson le fera pour nous .Vous pouvez également utiliser la modification , ou quelque chose de similaire.Préparation du traitement de la réponseLe serveur renvoie des données au format JSON .Mais nous n'avons pas besoin de l'analyser manuellement, pour cela il y a une bibliothèque Gson de Google.Des exemples sont ici:https://github.com/google/gsonhttps://habrahabr.ru/company/naumen/blog/228279/S'il reste du temps, vous pouvez écrire le reçu de l'article sélectionné lors de la recherche et ainsi de suite.10.2. Tâche finale - utilitaire de console pour télécharger des fichiers via HTTP
Utilitaire de console pour télécharger des fichiers via HTTP ... ça vous semble familier? Oui, c'est ça - L'histoire d'une tâche de test . Tout est logique - la tâche finale du cours Java est au même niveau que la tâche de test pour le poste de développeur Java junior.Et c'est une très bonne tâche - simple, mais couvre une grande variété de sujets. Et vous pouvez immédiatement voir comment l'auteur structure le code, utilise différentes approches et modèles, utilise le langage lui-même et la bibliothèque standard.10.3. Dernière tâche - Météo Telegram-Bot
Tâche:Écrivez un bot pour Telegram, qui sera:- .
- , . /subscribe. (/unsubscribe).
Vous pouvez utiliser https://openweathermap.org/api pour obtenir les prévisions .Cette tâche porte plutôt sur la capacité et la capacité de comprendre la nouvelle technologie (bot-api) et les différentes bibliothèques. Et vous devez configurer un VPN! Et vous devez bien sûr écrire le code.Soit dit en passant, un fait intéressant est que la plupart des élèves ignorent l'expression «emplacement envoyé» et la possibilité de l'envoyer. Ils écrivent un bot qui attend le nom de la ville. Je ne sais pas pourquoi. Cela fonctionne souvent mal , le code devient un peu plus compliqué, mais ils continuent de le faire.10.4. Tâche finale - reconnaissance de l'écriture manuscrite
Objectif:implémenter un programme de classification des nombres manuscrits.Cette tâche est déjà plus axée sur la mise en œuvre de l'algorithme, la capacité de les comprendre. Habituellement, le code pour les étudiants n'est pas très structuré.Description de la tâcheComme l'ensemble de données à étudier, la base des images de chiffres manuscrits MNIST sera utilisée . Les images de cette base de données ont une résolution de 28x28 et sont stockées sous la forme d'un ensemble de valeurs de niveaux de gris. La base de données entière est divisée en deux parties: formation, composée de 50 000 images, et test - 10 000 images.Pour résoudre ce problème, il est proposé d'implémenter la méthode des k plus proches voisins- algorithme métrique pour la classification automatique des objets. Le principe de base de la méthode des voisins les plus proches est que l'objet est affecté à la classe la plus courante parmi les voisins de cet élément.Les voisins sont pris sur la base de nombreux objets dont les classes sont déjà connues, et, sur la base de la valeur clé de k pour cette méthode, il est calculé quelle classe est la plus nombreuse parmi eux. Comme distance entre les objets, vous pouvez utiliser la métrique euclidienne, c'est-à-dire la distance habituelle entre les points dans l'espace.Conditions Vousdevez écrire un programme qui reconnaîtra les nombres manuscrits. Il devrait être possible d'initialiser une certaine classe avec des données pour la formation et de fournir une méthode pour reconnaître une seule image.En plus de l'implémentation de l'algorithme lui-même, vous devez écrire du code pour vérifier sa précision (calculer le taux d'erreur). Pour ce faire, utilisez 10 000 images de test.En plus de calculer la précision, il est proposé de mener une expérience: au lieu de la métrique euclidienne, utilisez la distance des blocs de ville , l'angle entre les vecteurs ou autre chose, et vérifiez la qualité de la reconnaissance.En optionSi tout fonctionne bien, vous pouvez compliquer un peu plus la tâche. En ajoutant, par exemple, l'élimination du bruit (émissions) ou l'utilisation de la méthode de la fenêtre Parzenovsky pour augmenter la précision.Encore quelques mots
Si vous avez des tâches intéressantes à proposer aux élèves (le temps de résolution approximatif est d'une heure ou deux), partagez-les dans les commentaires.