Java实用任务-用于课程和其他活动

Java实用任务-用于课程和其他活动


几句话


在过去的几年中,我一直在教Java编程。 随着时间的流逝,它发生了变化-有时添加或扔掉了不同的部分,主题的顺序发生了变化,构建课程计划的方法本身也发生了变化,等等。 即,该过程已得到改善。 准备课程是遇到的主要问题之一。 关于它们,将进行讨论。

事实是,我的每个班级都包含两个部分。 首先,我是一名讲师-我通过代码示例向您介绍一些新主题(类,继承,泛型等)。 第二部分是实用的。 显然,仅谈论编程是没有意义的,您需要进行编程。 课堂上的首要任务是解决问题,即以某种方式进行编程。 在课堂上进行编程与在家中进行编程不同,因为在课堂上您可以提出问题,显示代码,快速评估代码,对改进进行注释,对书面内容进行纠正。 在第一堂课中很容易找到任务。 循环,条件语句和OOP的任务(例如,编写“ Dog”类或“ Vector”类)。 诸如leetcode之类的服务甚至都允许您在线上验证解决此类问题的正确性。 但是,在专门针对收藏的课程中,应该给学生什么任务呢? 流? 注释呢? 几年来,我提出或修订了一些此类任务,实际上,本文是这些问题的集合(一些问题附有解决方案)。

当然,所有任务已经出现在某个地方。 但是,本文针对的是编程课程的老师(对于类似于Java的语言,大多数任务都可以完成),或者是专门教编程的老师。 这些任务可以在您的课程中“开箱即用”使用。 Java学习者也可以尝试解决它们。 但是,此类决定需要第三方验证和评估。

我在本文中还介绍了每个人几十年来一直使用的一些最简单的任务。 也许是为了不立即从抽象类开始。

欢迎任何想法和建议!

任务清单


基础知识


1.0。 最大值,最小值和平均值
1.1数组排序
1.2寻找素数
1.3从阵列中删除

OOP基础


2.0设计和创建描述向量的类
2.1生成具有权重的随机元素
2.2链表

递归


3.0二进制搜索
3.1找到方程的根
3.2二进制搜索树

传承


4.0实现描述三维形状的类层次结构
4.1实现描述三维图形的类层次结构-2
4.2实施描述三维形状的类层次结构-3
4.3实现描述三维形状的类层次结构-4

线数


5.0字母频率词典

抽象类和接口


6.0。 温度转换器
6.1。 具有撤消支持的Stringbuilder
6.2。 Statebuilding Stringbuilder(观察者模式)
6.4。 函数填充数组

馆藏


7.0 频率词词典
7.1。 没有重复的收藏
7.2。 ArrayList和LinkedList
7.3。 在数组上编写迭代器
7.4。 在二维数组上编写迭代器
7.5。 更加复杂的迭代器
7.6。 迭代两个迭代器
7.7。 计数元素
7.8。 更改地图中的键和值

多线程


8.0。
8.1。 线程同步
8.2。 消费品制造商

注解


9.0。 自定义注释-创建并与反射配合使用

最后的任务和其他任务


10.0 道路限制数量
10.1。 维基百科搜索。 在控制台程序中
10.2。 最后任务-控制台实用程序,用于通过HTTP下载文件
10.3。 最终任务-Weather Telegram-bot
10.4。 最后的任务-手写识别

基础知识


1.0。 最大值,最小值和平均值


挑战:

用随机数填充数组,并打印最大值,最小值和平均值。

要生成一个随机数,请使用Math.random()方法,该方法返回间隔[0,1]中的值。

解决方案:

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]; //      double min = array[0]; double avg = 0; for (int i = 0; i < array.length; i++) { if(max < array[i]) max = array[i]; if(min > array[i]) min = array[i]; avg += array[i]/array.length; } System.out.println("max = " + max); System.out.println("min = " + min); System.out.println("avg = " + avg); } 

1.1。 实现气泡排序算法以对数组进行排序


解决方案:

  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。 搜索素数


挑战:

编写一个在[2,100]之间将质数打印到控制台的程序。
使用运算符(除法余数)并循环解决此问题。

解决方案:

  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); } } 

或使用标签循环

  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。 从阵列中删除


挑战:

给定一个整数数组和另一个整数。 从数组中删除所有出现的该数字(应该没有空格)。

解决方案:

  public static void main(String[] args) { int test_array[] = {0,1,2,2,3,0,4,2}; /* Arrays.toString: . https://docs.oracle.com/javase/7/docs/api/java/util/Arrays.html */ 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]; } } // Arrays.copyOf     nums    //   nums.length - offset return Arrays.copyOf(nums, nums.length - offset); } 

您可以自己编写一种用于切掉数组尾部的方法,但是值得注意的是,标准方法可以更快地工作:

 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; } 

但是,如果采用这种方式,则可以先创建所需长度的数组,然后填充它:

 public static int[] removeElement(int[] nums, int val) { int count = 0; //      for (int i = 0; i < nums.length; i++) { if (nums[i] != val) { count++; } } int[] newArray = new int[count]; int offset = 0; //      , //       for(int i = 0; i< nums.length; i++){ if(nums[i] == val){ offset++; } else{ newArray[i - offset] = nums[i]; } } return newArray; } 


2.0。 设计和创建向量


挑战:

创建一个描述向量的类(在三维空间中)。

他必须具备:

  • 参数的构造函数,其形式为坐标x,y,z
  • 计算向量长度的方法。 可以使用Math.sqrt()计算根:

     sqrtx2+y2+z2

  • 标量乘积的计算方法:

    x1x2+y1y2+z1z2

  • 计算与另一个向量的向量乘积的方法:

    y1z2z1y2z1x2x1z2x1y2y1x2

  • 一种计算向量之间的夹角(或角度的余弦)的方法:向量之间的夹角的余弦等于向量的标量乘积除以向量的模块乘积(长度):

     fracab|a| cdot|b|

  • 求和与求和的方法:

    x1+x2y1+y2z1+z2

    x1x2y1y2z1z2


  • 一个采用整数N并返回大小为N的随机向量数组的静态方法。

如果该方法返回一个向量,则它应返回一个新对象,而不更改基对象。 也就是说,您需要实现“ 不可变对象 ”模板

解决方案:

 public class Vector { //    private double x; private double y; private double z; //    public Vector(double x, double y, double z) { this.x = x; this.y = y; this.z = z; } //  .     public double length() { return Math.sqrt(x * x + y * y + z * z); } // ,    public double scalarProduct(Vector vector) { return x * vector.x + y * vector.y + z * vector.z; } // ,    public Vector crossProduct(Vector vector) { return new Vector( y * vector.z - z * vector.y, z * vector.x - x * vector.z, x * vector.y - y * vector.x); } //     public double cos(Vector vector) { //       // multiply  length return scalarProduct(vector) / (length() * vector.length()); } public Vector add(Vector vector) { return new Vector( x + vector.x, y + vector.y, z + vector.z ); } public Vector subtract(Vector vector) { return new Vector( x - vector.x, y - vector.y, z - vector.z ); } public static Vector[] generate(int n){ Vector[] vectors = new Vector[n]; for(int i =0; i < n; i++){ vectors[i] = new Vector(Math.random(), Math.random(), Math.random()); } return vectors; } @Override public String toString() { return "Vector{" + "x=" + x + ", y=" + y + ", z=" + z + '}'; } } 

您可以像这样使用此类:

 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])); } 

您可以推广此解决方案,并为任意维度编写Vector类:

 public class Vector { //    ,    private double values[]; public Vector(double[] values) { this.values = values; } //  .     public double length() { double sum = 0; for (int i = 0; i < values.length; i++) { sum += values[i] * values[i]; } return Math.sqrt(sum); } // ,    public double scalarProduct(Vector vector) { double res = 0; for (int i = 0; i < values.length; i++) { res += values[i] * vector.values[i]; } return res; } //     // public double crossProduct(Vector vector) { // // } //     public double cos(Vector vector) { return scalarProduct(vector) / (length() * vector.length()); } public Vector add(Vector vector) { double[] another = new double[values.length]; for (int i = 0; i < values.length; i++) { another[i] = values[i] + vector.values[i]; } return new Vector(another); } public Vector subtract(Vector vector) { double[] another = new double[values.length]; for (int i = 0; i < values.length; i++) { another[i] = values[i] - vector.values[i]; } return new Vector(another); } //   private static double[] generateRandomArray(int length) { double[] array = new double[length]; for (int i = 0; i < array.length; i++) { array[i] = Math.random(); } return array; } public static Vector[] generate(int n, int dimension) { Vector[] vectors = new Vector[n]; for (int i = 0; i < n; i++) { vectors[i] = new Vector(generateRandomArray(dimension)); } return vectors; } } 


2.1。 生成具有权重的随机项目


挑战:

写一个类,其构造函数采用两个数组:一个值数组和一个值权重数组。
该类必须包含一个方法,该方法将考虑到其权重从第一个数组中随机返回一个元素。
一个例子:
给定一个数组[1、2、3]和一个权重数组[1、2、10]。
平均而言,值“ 1”应比值“ 2”2倍,比值“ 3”少10倍。

解决方案:

 /*     :  ,   —    .   ""     ,       ,        : |-|--|----------| 0-1--3----------13 ^ */ class RandomFromArray { private int[] values; //  private int[] weights; //  private int[] ranges; //    private int sum; //     public RandomFromArray(int[] values, int[] weights) { this.values = values; this.weights = weights; ranges = new int[values.length]; //     sum = 0; for (int weight : weights) { sum += weight; } //  ranges,   int lastSum = 0; for (int i = 0; i < ranges.length; i++) { ranges[i] = lastSum; lastSum += weights[i]; } } /*  ranges  ,        [0;sum],   ,   : */ public int getRandom() { int random = (int) (Math.random() * (sum - 1)); int ourRangeIndex = 0; for (int i = 0; i < ranges.length; i++) { if (ranges[i] > random) { break; } ourRangeIndex = i; } return values[ourRangeIndex]; } } 

但是,由于ranges数组已排序,因此您可以(并且应该)使用二进制搜索:

 public int getRandom() { int random = (int) (Math.random() * (sum - 1)); int index = Arrays.binarySearch(ranges, random); return values[index >= 0 ? index : -index - 2]; } 

这个问题还有另一种解决方案。 您可以创建一个大小等于所有权重之和的数组。 然后,随机元素的选择归结为生成随机索引。 也就是说,如果给定值数组[1、2、3]和权重数组[1、2、10],则可以立即创建数组[1、2、2、3、3、3、3、3、3、3, 3、3、3、3]并从中提取随机元素:

 class RandomFromArray { private int[] extended_values; //  public RandomFromArray(int[] values, int[] weights) { //     int sum = 0; for (int weight : weights) { sum += weight; } extended_values = new int[sum]; int cursor = 0; for(int i = 0; i < weights.length; i++){ for(int j = 0; j < weights[i]; j++){ extended_values[cursor++] = values[i]; } } } /*  extended_values  ,        [0; extended_values.length) */ public int getRandom() { int random = (int) (Math.random() * ( extended_values.length - 1)); return extended_values[random]; } } 

与先前解决方案中的log(n)相比,此解决方案具有一个优势-提取随机元素O(1)的时间。 但是,它需要大量内存:

O sumn



2.2。 链表


我经常给出的另一项任务是实现链表。 它可以以最简单的形式给出(仅实现add()get() ),也可以要求实现java.util.List
我不会详细介绍这一点,在Habr中有很多关于Java中链表实现的文章,例如:
图片中的数据结构。 链表

3.0 二进制搜索


挑战:

编写一个方法,检查给定元素是否在数组中。
使用枚举和二进制搜索来解决此问题。
比较大型阵列的两种解决方案的运行时间(例如100000000个元素)
解决方案:

  /*  ,   .    ,  -1 */ 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); } /** *    {@link #binarySearchRecursively(double[], double)} * *    ,   ,   " ", *      .    low  high * * @param sortedArray   * @param key   * @param low     * @param high     * @return   */ private static int binarySearchRecursively (double[] sortedArray, double key, int low, int high) { int middle = (low + high) / 2; //  if (high < low) { //    return -1; } if (key == sortedArray[middle]) { //   return middle; } else if (key < sortedArray[middle]) { //     return binarySearchRecursively( sortedArray, key, low, middle - 1); } else { return binarySearchRecursively( //     sortedArray, key, middle + 1, high); } } //     private static double[] generateRandomArray(int length) { double[] array = new double[length]; for (int i = 0; i < array.length; i++) { array[i] = Math.random(); } return array; } public static void main(String[] args) { double[] array = generateRandomArray(100000000); Arrays.sort(array); //    /*  ,       ,   benchmarks . https://habr.com/ru/post/349914/     */ long time = System.currentTimeMillis(); //  , unix-time bruteForce(array, 0.5); System.out.println(System.currentTimeMillis() - time); time = System.currentTimeMillis(); binarySearchRecursively(array, 0.5); System.out.println(System.currentTimeMillis() - time); } 

3.1。 找出方程的根


挑战:

找出方程的根

Ç ø 小号X 5 + X 4 - 345.3 * X - 23 = 0

在段[0; 10]的x精度不低于0.001。 已知根在此间隔中是唯一的。
为此使用减半方法 (和递归)。

解决方案:

  //   public static double func(double x){ return Math.cos(Math.pow(x, 5)) + Math.pow(x, 4) - 345.3 * x - 23; } //   public static double solve(double start, double end){ if(end - start <= 0.001){ return start; } double x = start + (end - start) / 2; if(func(start) * func(x) > 0){ return solve(x, end); } else { return solve(start, x); } } public static void main(String[] args) { System.out.println(solve(0, 10)); } 

注意:如果我们想在x中而不是y中获得所需的精度,则应重写方法中的条件:

  if(Math.abs(func(start)- func(end)) <= 0.001){ return start; } 

一个小技巧:考虑到双精度值的集合是有限的(有两个相邻的值之间没有双精度值),请重写退出递归的条件,如下所示:

  double x = start + (end - start) / 2; if(x == end || x == start){ return x; } 

因此,我们获得了使用这种方法通常可以获得的最大精度。

3.2。 二叉搜索树


实现二进制搜索树是一项出色的任务。 当涉及到递归时,我通常会给它。
我不会对此写太多,有很多不同的文章/实现:
数据结构:二叉树。
二叉树,快速实现
哈希二叉树的Java实现

传承


4.0 实现描述三维形状的类层次结构


挑战:

实现一个类层次结构:


Box类是一个容器;它可以包含其他形状。 add()方法将Shape作为输入。 我们需要添加新的形状,直到在Box中为它们占据空间为止(我们只会考虑体积,而忽略形状。假设我们倒入液体)。 如果没有足够的空间来添加新形状,则该方法应返回false

解决方案:

 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 { //   public Ball(double radius) { super(Math.PI * Math.pow(radius, 3) * 4 / 3, radius); } } class Cylinder extends SolidOfRevolution { //   private double height; public Cylinder(double radius, double height) { super(Math.PI * radius * radius * height, radius); this.height = height; } } class Pyramid extends Shape{ private double height; private double s; //   public Pyramid(double height, double s) { super(height * s * 4 / 3); this.height = height; this.s = s; } } class Box extends Shape { private ArrayList<Shape> shapes = new ArrayList<>(); private double available; public Box(double available) { super(available); this.available = available; } public boolean add(Shape shape) { if (available >= shape.getVolume()) { shapes.add(shape); available -= shape.getVolume(); return true; } else { return false; } } } public class Main { public static void main(String[] args) { Ball ball = new Ball(4.5); Cylinder cylyinder = new Cylinder(2, 2); Pyramid pyramid = new Pyramid(100, 100); Box box = new Box(1000); System.out.println(box.add(ball)); // ok System.out.println(box.add(cylyinder)); // ok System.out.println(box.add(pyramid)); // failed } } 

为了不返回该任务,下面描述该任务的更多变型。

4.1。 实现描述三维形状的类层次结构-2


挑战:

实现相同的类层次结构,但将某些类抽象化。

解决方案:

 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 { //   @Override public double getVolume() { return Math.PI * Math.pow(radius, 3) * 4 / 3; } public Ball(double radius) { super(radius); } } class Cylinder extends SolidOfRevolution { //   private double height; public Cylinder(double radius, double height) { super(radius); this.height = height; } @Override public double getVolume() { return Math.PI * radius * radius * height; } } class Pyramid extends Shape { private double height; private double s; //   public Pyramid(double height, double s) { this.height = height; this.s = s; } @Override public double getVolume() { return height * s * 4 / 3; } } class Box extends Shape { private ArrayList<Shape> shapes = new ArrayList<>(); private double available; private double volume; public Box(double available) { this.available = available; this.volume = available; } public boolean add(Shape shape) { if (available >= shape.getVolume()) { shapes.add(shape); available -= shape.getVolume(); return true; } else { return false; } } @Override public double getVolume() { return volume; } } public class Main { public static void main(String[] args) { Ball ball = new Ball(4.5); Cylinder cylyinder = new Cylinder(2, 2); Pyramid pyramid = new Pyramid(100, 100); Box box = new Box(1000); System.out.println(box.add(ball)); // ok System.out.println(box.add(cylyinder)); // ok System.out.println(box.add(pyramid)); // failed } } 

4.2。 实现描述三维形状的类层次结构-3


挑战:

实现相同的类层次结构,但使用接口。
此外,鼓励学生实施可比接口。

解决方案:

 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 { //   @Override public double getVolume() { return Math.PI * Math.pow(radius, 3) * 4 / 3; } public Ball(double radius) { super(radius); } } class Cylinder extends SolidOfRevolution { //   private double height; public Cylinder(double radius, double height) { super(radius); this.height = height; } @Override public double getVolume() { return Math.PI * radius * radius * height; } } class Pyramid implements Shape { private double height; private double s; //   public Pyramid(double height, double s) { this.height = height; this.s = s; } @Override public double getVolume() { return height * s * 4 / 3; } } class Box implements Shape { private ArrayList<Shape> shapes = new ArrayList<>(); private double available; private double volume; public Box(double available) { this.available = available; this.volume = available; } public boolean add(Shape shape) { if (available >= shape.getVolume()) { shapes.add(shape); available -= shape.getVolume(); return true; } else { return false; } } @Override public double getVolume() { return volume; } public ArrayList<Shape> getShapes() { return shapes; } } public class Main { public static void main(String[] args) { Ball ball = new Ball(4.5); Cylinder cylyinder = new Cylinder(2, 2); Pyramid pyramid = new Pyramid(100, 100); Box box = new Box(1000); System.out.println(box.add(ball)); // ok System.out.println(box.add(cylyinder)); // ok System.out.println(box.add(pyramid)); // failed // Sorting: ArrayList<Shape> shapes = box.getShapes(); Collections.sort(shapes); // sorted by Volume! } } 

4.3。 实现描述三维形状的类层次结构-4


挑战:

将旋转形状添加到类层次结构以获取任意函数。 您可以使用某个积分来计算近似体积。 由于绕x轴旋转的图形的体积为

V x = p i i n t b a f 2 x d x  


积分是
图片


然后,您可以编写矩形方法的实现:

 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()); } 

当然,这里我们没有考虑计算的准确性,也没有选择分区数量来达到必要的准确性,但这是编程任务,而不是数值方法,因此我们在课堂上忽略了这一点。

线数


每行可以找到很多任务。 我通常给这些:

  • 编写一种方法来查找数组中最长的字符串。
  • 编写一种方法来检查单词是否是回文
  • 编写一种方法,将文本中所有出现的大写“ byaka”替换为“ [cut out
    审查]。
  • 有两行。 编写一个返回另一行出现次数的方法。

我不会描述此类问题的解决方案,并且字符串也有大量任务。

在更有趣的一个中,我喜欢这个:

5.0。 俄语(或英语)字母的频率字典。


挑战:

建立一个俄语(或英语)字母频率字典。 我们忽略了选择和分析语言主体的问题,这足以获取较短的文本。

解决方案:

  /** *      ,  *      , *    Map. * *    Map.Entry<Character, Integer>, *     (Character) * * @param text -  */ 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); //     - if((ch >= '' && ch <= '') || ch == ''){ map.compute(ch, (character, integer) -> integer == null ? 1 : integer + 1); } } ArrayList<Map.Entry<Character, Integer>> entries = new ArrayList<>(map.entrySet()); entries.sort((o1, o2) -> Character.compare(o1.getKey(), o2.getKey())); for(Map.Entry<Character, Integer> entry : entries){ System.out.println(entry.getKey() + " " + entry.getValue()); } } 

大概:

  /** *   Map. *    ,    *      *  * @param text */ 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]); } } 

抽象类和接口


6.0。 温度转换器


挑战:

编写BaseConverter类以将摄氏度转换为
开尔文华氏度 ,等等。 该方法必须具有转换方法,该方法
并进行转换。

解决方案:

 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)); } } 

另外,您可以要求实现factory方法,如下所示:

 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


任务:

编写支持undo操作的StringBuilder类为此,请将所有方法委托给标准StringBuilder,并在您自己的类中存储所有操作的列表,以供undo()执行这将是Team模板的实现

解决方案:

 /** * StringBuilder    undo * java.lang.StringBuilder —    <b>final</b>, *   ,  . */ 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; //  /** * ,   . *     append,    *  "delete".   undo()  *  . */ private Stack<Action> actions = new Stack<>(); //  public UndoableStringBuilder() { stringBuilder = new StringBuilder(); } /** see {@link java.lang.AbstractStringBuilder#reverse()}  ,   reverse(),      —  reverse().    . */ public UndoableStringBuilder reverse() { stringBuilder.reverse(); Action action = new Action(){ public void undo() { stringBuilder.reverse(); } }; actions.add(action); return this; } public UndoableStringBuilder append(String str) { stringBuilder.append(str); Action action = new Action(){ public void undo() { stringBuilder.delete( stringBuilder.length() - str.length() -1, stringBuilder.length()); } }; actions.add(action); return this; } // .....    append   (. )...... public UndoableStringBuilder appendCodePoint(int codePoint) { int lenghtBefore = stringBuilder.length(); stringBuilder.appendCodePoint(codePoint); actions.add(new DeleteAction(stringBuilder.length() - lenghtBefore)); return this; } public UndoableStringBuilder delete(int start, int end) { String deleted = stringBuilder.substring(start, end); stringBuilder.delete(start, end); actions.add(() -> stringBuilder.insert(start, deleted)); return this; } public UndoableStringBuilder deleteCharAt(int index) { char deleted = stringBuilder.charAt(index); stringBuilder.deleteCharAt(index); actions.add(() -> stringBuilder.insert(index, deleted)); return this; } public UndoableStringBuilder replace(int start, int end, String str) { String deleted = stringBuilder.substring(start, end); stringBuilder.replace(start, end, str); actions.add(() -> stringBuilder.replace(start, end, deleted)); return this; } public UndoableStringBuilder insert(int index, char[] str, int offset, int len) { stringBuilder.insert(index, str, offset, len); actions.add(() -> stringBuilder.delete(index, len)); return this; } public UndoableStringBuilder insert(int offset, String str) { stringBuilder.insert(offset, str); actions.add(() -> stringBuilder.delete(offset, str.length())); return this; } // .....    insert   (. )...... public void undo(){ if(!actions.isEmpty()){ actions.pop().undo(); } } public String toString() { return stringBuilder.toString(); } } 

6.2。Statebuilding Stringbuilder(观察者模式)


任务:

编写您的StringBuilder类,并能够将其状态更改通知其他对象。为此,将所有方法委托给标准StringBuilder,并在自己的类中实现Observer设计模式

解决方案:

 /** .  ,      UndoableStringBuilder,   onChange(). */ interface OnStringBuilderChangeListener { void onChange(OvservableStringBuilder stringBuilder); } class OvservableStringBuilder { // ,     private OnStringBuilderChangeListener onChangeListener; //  private StringBuilder stringBuilder; //   onChangeListener public void setOnChangeListener(OnStringBuilderChangeListener onChangeListener) { this.onChangeListener = onChangeListener; } public OvservableStringBuilder() { stringBuilder = new StringBuilder(); } private void notifyOnStringBuilderChangeListener(){ if(onChangeListener != null){ onChangeListener.onChange(this); } } public OvservableStringBuilder append(Object obj) { stringBuilder.append(obj); notifyOnStringBuilderChangeListener(); return this; } public OvservableStringBuilder replace(int start, int end, String str) { stringBuilder.replace(start, end, str); notifyOnStringBuilderChangeListener(); return this; } public OvservableStringBuilder insert(int index, char[] str, int offset, int len) { stringBuilder.insert(index, str, offset, len); notifyOnStringBuilderChangeListener(); return this; } // .......    .......... public String toString() { return stringBuilder.toString(); } } /**   OnStringBuilderChangeListener */ class MyListener implements OnStringBuilderChangeListener { /*   onChange    stringBuilder,  "" */ public void onChange(OvservableStringBuilder stringBuilder) { System.out.println("CHANGED: " + stringBuilder.toString()); } } public class Main { public static void main(String[] strings) { OvservableStringBuilder UndoableStringBuilder = new OvservableStringBuilder(); UndoableStringBuilder.setOnChangeListener(new MyListener()); UndoableStringBuilder.append("Hello"); UndoableStringBuilder.append(", "); UndoableStringBuilder.append("World!"); } } 

6.3。 筛选条件


问题:

写入方法滤波器,其接收所述输入阵列(任何类型的),和接口的实现过滤器 C方法应用(对象O) 从数组中删除多余的
检查它如何在字符串或其他对象上工作。

解决方案:
通常,我在泛型之前执行此任务,因此学生使用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]; } } // Arrays.copyOf     array    //   array.length - offset return Arrays.copyOf(array, array.length - offset); } public static void main(String[] args) { String array[] = new String[]{"1rewf ", "feefewf", "a", null, "1"}; String[] newArray = (String[]) filter(array, new Filter() { @Override public boolean apply(Object o) { return o != null; } }); } } 

但是,使用泛型是可能的。然后,您可以使用标准功能

 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]; } } // Arrays.copyOf     array    //   array.length - offset return Arrays.copyOf(array, array.length - offset); } public static void main(String[] args) { String array[] = new String[]{"1rewf ", "feefewf", "a", null, "1"}; String[] newArray = filter(array, s -> s != null); } } 

6.4。数组填充


一项与上一个任务有些类似的任务:
编写一个fill方法,该方法接受对象数组和Function接口(或您自己的)的实现。
fill方法应填充数组,并使用Function接口的实现按索引获取新值。也就是说,您想像这样使用它:

 public static void main(String[] args) { Integer[] squares = new Integer[100]; fill(squares, integer -> integer * integer); // 0, 1, 4, 9, 16 ..... } 

解决方案:

 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); } } 

馆藏



7.0 频率词词典


请参阅有关字母频率字典问题

7.1。没有重复的收藏


任务:

编写一个方法来接收对象的集合作为输入,然后返回一个没有重复的集合。

解决方案:

  public static <T> Collection<T> removeDuplicates(Collection<T> collection) { return new HashSet<>(collection); //   ! } 

7.2。ArrayList和LinkedList


编写一个将1,000,000个元素添加到ArrayList和LinkedList的方法。编写另一种方法,从填充列表中随机选择100,000次元素。测量花费在上面的时间。比较结果并提出原因。

解决方案:

  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。在数组上编写迭代器


解决方案:

 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。二维数组迭代器


任务:

在二维数组上编写迭代器。

解决方案:

 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。更加复杂的迭代器


我喜欢这个任务。她只接触到小组中的几名相对较容易完成先前任务的学生。

任务:

Dan迭代器。next()方法返回一个String或具有相同结构的迭代器(即再次返回String或相同的迭代器)。在此迭代器的顶部编写另一个已经“平坦”的迭代器。

堆栈上的解决方案:

 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(); } } 

递归解决方案:

 /** * @author Irina Poroshina */ 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。迭代两个迭代器


任务:

编写一个遍历两个迭代器的迭代器。

解决方案:

 /** * @author Irina Poroshina */ 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。计数元素


编写一个方法,该方法接收一个类型为K(通用)的元素数组作为输入并返回Map <K,Integer>,其中K是数组中的值,而Integer是数组中条目的数量。
也就是说,方法签名如下所示:

 <K> Map<K, Integer> arrayToMap(K[] ks); 

解决方案:

 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。更改地图中的键和值


编写一个接收Map <K,V>并返回Map 的方法,该方法的键和值是相反的。由于值可能重合,因此Map中的值类型将不再是K,而是
 集合<K>: 

 Map<V, Collection<K>> 

解决方案:

 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; } 

多线程


8.0。


任务:

在流启动之前,启动之后以及运行时,打印流的状态。

解决方案:

  Thread thread = new Thread() { @Override public void run() { System.out.println(getState()); } }; System.out.println(thread.getState()); thread.start(); try { //         TERMINATED: thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(thread.getState()); 

添加等待和阻止:

  /** *   WAITING * * @param strings * @throws InterruptedException */ 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(); //   lock.wait(); //  ,     System.out.println(thread.getState()); // WAITING lock.notifyAll(); System.out.println(thread.getState()); // BLOCKED } } 

对于TIMED_WAITING,请更改相同的代码:

  /** *   WAITING * * @param strings * @throws InterruptedException */ 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(); //   lock.wait(); //  ,     System.out.println(thread.getState()); // WAITING } } 

8.1。线程同步


任务:

编写一个程序,在其中创建两个线程,这些线程依次在控制台上显示其名称。

解决方案:

 class StepThread extends Thread { //     lock private Object lock; public StepThread(Object object) { this.lock = object; } /** *  :   ,   , *     ,  ,    . * *     lock.notify()   *   ,  , *  lock   .    ,   *  lock.wait(),      .    . */ @Override public void run() { while (true) { synchronized (lock) { try { System.out.println(getName()); lock.notify(); lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } public class Main { public static void main(String[] strings) { Object lock = new Object(); new StepThread(lock).start(); new StepThread(lock).start(); } } 

8.2。制造商-消费者


经典的多线程任务之一。给定两个流-生产者和消费者。制造商会生成一些数据(在示例中为数字)。制造商“消费”它们。
两个流共享一个公共数据缓冲区,其大小是有限的。如果缓冲区为空,则使用者必须等待数据在那里出现。如果缓冲区已满,则制造商必须等待,直到消费者获取数据并且该位置变为空闲为止。

制造商:

 // implements Runnable      class Producer implements Runnable { //   private final Queue<Double> sharedQueue; //   private final int SIZE; //  public Producer(Queue<Double> sharedQueue, int size) { this.sharedQueue = sharedQueue; this.SIZE = size; } @Override public void run() { //   while (true) { try { //     produce System.out.println("Produced: " + produce()); } catch (InterruptedException e) { e.printStackTrace(); } } } private double produce() throws InterruptedException { synchronized (sharedQueue) { //  synchronized if (sharedQueue.size() == SIZE) { //   ,   sharedQueue.wait(); } //    . double newValue = Math.random(); sharedQueue.add(newValue); //     ,    sharedQueue.notifyAll(); return newValue; } } } 

消费者:

 // implements Runnable      class Consumer implements Runnable { //   private final Queue<Double> sharedQueue; public Consumer(Queue<Double> sharedQueue) { this.sharedQueue = sharedQueue; } @Override public void run() { while (true) { try { System.out.println("Consumed: " + consume()); } catch (InterruptedException ex) { ex.printStackTrace(); } } } // ,      private Double consume() throws InterruptedException { synchronized (sharedQueue) { if (sharedQueue.isEmpty()) { //  ,   sharedQueue.wait(); } sharedQueue.notifyAll(); return sharedQueue.poll(); } } } 

创建并运行:

 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。自己的注释-创建和使用


当涉及到注释和反射时,我通常会执行此任务。同时,您可以讨论ExecutorsThreadPoolExecutor等。

任务: 使用整数参数

创建重复注释
扩展类的ThreadPoolExecutor和覆盖的方法执行如下:if的实例可运行与注释的重复,那么它的方法运行时执行多次(在由参数指定数目的重复)。

也就是说,通过编写此类:

 @Repeat(3) class MyRunnable implements Runnable{ @Override public void run() { System.out.println("Hello!"); } } 

并使用它:

 public static void main(String[] strings) { CustomThreadPoolExecutor customThreadPoolExecutor = new CustomThreadPoolExecutor(10); customThreadPoolExecutor.execute(new MyRunnable()); } 

我们应该看到:

 Hello! Hello! Hello! 

解决方案:

 @Retention(RetentionPolicy.RUNTIME) @interface Repeat { int value(); } class CustomThreadPoolExecutor extends ThreadPoolExecutor { public CustomThreadPoolExecutor(int corePoolSize) { // why Integer.MAX_VALUE, 0m and TimeUnit.MILLISECONDS? // see Executors.newFixedThreadPool(int nThreads) super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()); } @Override public void execute(Runnable command) { if (command != null) { Class<? extends Runnable> runnableClass = command.getClass(); Repeat repeat = runnableClass.getAnnotation(Repeat.class); for (int i = 0; i < (repeat != null ? repeat.value() : 1); i++) { super.execute(command); } } } } 

最后的任务和其他任务


在课程中,我为学生提供了一些艰巨的任务-整个课程。需要使用先前学习的知识编写一个小程序。顺便说一下,这里经常会出现复杂性。解决编写一种方法的问题的解决方案是一回事,提出一种算法,记住您先前学习的所有内容并立即用Java编写50行代码是完全不同的。但是在本课中,我可以将它们推向正确的方向,帮助解决问题,降低性能,找到正确的类和方法,等等。这些任务中的几个描述如下。我以这种形式将它们交给我的学生。

此外,在课程结束时,每个人都应完成最后的作业。也就是说,在家独自编写一个程序。稍微复杂一点。我给你机会选择几个选项之一。顺便说一句,一个有趣的事实是您需要至少编写一个程序,或者可以一次编写多个程序。看来我记得只有一个人写的不止一个。

10.0 道路限制数量


一个小任务,演示如何使用Java解决实际问题。

数据准备:
圣彼得堡的开放式数据门户,我们csv格式在生产期间加载有关交通限制的数据任务:需要确定特定日期该城市实施了多少条道路限制。该程序将两个参数作为参数:







  • 数据文件的路径
  • 日期

即,它开始如下:

 java TrafficBlocks "PATH_TO_CSV_FILE" dd.MM.yyyy 

有必要在此日期推断出适用的交通限制数量。

示范算法


顺便说一下,一直以来,只有一个人注意到数据中的日期格式(yyyyMMdd)使得无法对其进行解析,而是将其作为字符串进行比较。因此可以简化解决方案。在讨论Date,Calendar,DateFormat之后,我将执行此任务,因此当他们已经写完所有内容时,我正在谈论这种简化。

我在这里没有提供解决方案,它们可以有很多不同,并且每个都必须单独考虑。

10.1。维基百科搜索。在控制台程序中


任务:

编写一个程序,该程序从控制台读取搜索查询并在Wikipedia上显示搜索结果。该任务分为4个阶段:
  1. 阅读要求
  2. 向服务器发出请求
  3. 解析答案
  4. 打印结果

第一点和第四点不需要太多解释,让我们详细介绍对服务器的请求。

此任务也可以分为几个阶段:

  1. 请求生成
  2. 服务器请求
  3. 准备响应处理
  4. 响应处理

让我们更详细地考虑一下:API

请求生成
提供了无需键即可执行搜索查询的功能。这样,大约:

 https://ru.wikipedia.org/w/api.php?action=query&list=search&utf8=&format=json&srsearch="Java" 

您可以在浏览器中打开此链接,然后查看请求的结果。
但是,为使请求成功,您应该从链接中删除无效字符,即执行Percent-encoding,这也是URL编码。
为此,在Java中,您可以URLEncoder类中使用静态编码方法,如下所示:

 street = URLEncoder.encode(street, "UTF-8"); 

就是这样,URL已经准备好了!现在仍然可以向服务器发出请求...对服务器的

请求
对于GET和POST请求,可以使用HttpURLConnection。这是最简单的。只需创建,打开连接并获取InputStream即可。我们甚至不需要阅读它,Gson会为我们做
您也可以使用改造或类似方法。

准备处理响应
服务器返回JSON格式的数据
但是我们不需要手动解析它,因此有Google 提供的Gson
示例在这里:https :
//github.com/google/gson
https://habrahabr.ru/company/naumen/blog/228279/

如果还有时间,您可以写出在搜索过程中所选文章的收据,依此类推。

10.2。 最后任务-控制台实用程序,用于通过HTTP下载文件


通过HTTP下载文件的控制台实用程序...听起来很熟悉吗?是的,就是这样- 一个测试任务的故事一切都是合乎逻辑的-Java课程的最终任务与Junior Java开发人员职位的测试任务处于同一水平。
这确实是一项很好的任务-简单,但涵盖了多个主题。您可以立即看到作者如何构造代码,使用不同的方法和模式,使用语言本身和标准库。

10.3。最终任务-Weather Telegram-bot


任务:

为Telegram编写一个机器人,它将是:

  • .
  • , . /subscribe. (/unsubscribe).

您可以使用https://openweathermap.org/api来获取预测

这项任务主要在于了解新技术(bot-api)和不同库的能力。您需要设置一个VPN!当然,您必须编写代码。

顺便说一句,一个有趣的事实是,大多数学生都忽略了“已发送位置”一词以及发送它的可能性。他们写了一个期望城市名称的机器人。我不知道为什么 这通常效果不佳,代码变得有些复杂,但是他们继续这样做。

10.4。 最后的任务-手写识别


目标:

实施一个程序来对手写数字进行分类。
这项任务已经更加集中于算法的实现以及理解这些算法的能力。通常,学生的代码不是很结构化。

任务说明
作为要研究的数据集,将使用手写数字MNIST图像的基础。该数据库中的图像分辨率为28x28,并存储为一组灰度值。整个数据库分为两个部分:训练(包含50,000张图像)和测试-10,000张图像。

为了解决这个问题,提出了实现k个最近邻居方法。-用于对象自动分类的度量算法。最近邻居方法的基本原理是将对象分配给该元素的邻居中最常见的类。
邻居是根据许多已知类的对象得出的,并根据此方法的k的键值,计算出其中最多的类。作为对象之间的距离,您可以使用欧几里得度量,即空间中点之间的通常距离。

要求您

必须编写一个可以识别手写数字的程序。应该有可能使用训练数据初始化某个类,并提供一种识别单个图像的方法。

除了算法本身的实现之外,您还应该编写代码来检查其准确性(计算错误率)。为此,请使用10,000张测试图像。
除了计算准确性外,建议进行一个实验:代替城市欧几里得度量,使用城市街区距离,向量之间的夹角或其他东西,并检查识别质量。

选配

如果一切正常,那么您可以使任务复杂一些。通过添加例如消除噪声(排放)或使用Parzenovsky窗口方法来提高准确性的方法

再说几句话


如果您有可以为学生提供的出色任务(大约解决时间为一两个小时),请在评论中与他们分享。

Source: https://habr.com/ru/post/zh-CN440436/


All Articles