Полезное для программистов:

Фриланс
Новости
Статьи
   
Рубрики:

Error. Page cannot be displayed. Please contact your service provider for more details. (26)


Часть 1 - Коллекции

Поиск:
Простая реализация - используем коллекции

Как уже можно было видеть из описания классов класс Управляющая Система использует два списка - список групп и список студентов.
Рекомендуем: Здесь не будет приведена подробная информация о коллекциях. Для хорошего ознакомления с ними отсылаем вас к документации по JAVA.
В первую очередь это конечно оригинал от SUN:
http://java.sun.com/j2se/1.5.0/docs/guide/...ions/index.html
А также ссылки статей на форуме Vingrad
http://vingrad.ru/JAVA-FAQ-000104
http://vingrad.ru/JAVA-FAQ-002980

Для каждого описанного класса создаем отдельный класс на JAVA

Student.java
Group.java
ManagementSystem.java

Замечание Для класса ManagementSystem сделаем метод main, который решает две задачи:
1. Запускает класс без каких-либо дополнительных
2. Позволяет проверить функциональность системы на тестовых данных

На данном этапе у нас не будет красивого графического интерфейса - мы просто пытаемся описать необходимые функции и проверить их работоспособность.
Настоятельно рекомендую Вам внимательно просмотрите код метода main класса ManagementSystem - там преведены вызовы всех функций, которые мы будем использовать.

Классы Student и Group - эти классы содержат только описания необходимях полей, а также методы для доступа к этим полям. Так называемые сеттеры и геттеры (от слов set/get - установить/получить). Это уже по сути стандартный подход к работе с полями класса - поле для хранения объявляется как private, а для доступа используются методы set/get.
Принцип именования следующий:
имя переменной начинается со строчной буквы - например ИД студента будет выглядеть как studentId. А методы будут выглядеть вот так: 
Код

private int studentId;

public void setStudentId(int studentId)
{
  this.studentId = studentId;
}

public int getStudentId()
{
  return this.studentId;
}

Как видите методы имеют слова set/get и дальше уже с заглавной буквы идет имя переменной. Также при установке поля в метод передается параметр такого же типа. Я люблю именовать его так же, хотя это не обязательно. Многие системы разработки (Eclipse, JBuilder, IDEA) содержат специальные меню, которые позволяют создать set/get автоматически по списку переменных.
Такой принцип именования был предложен SUN для того, чтобы можно было легко определять, какие свойства и методы есть у класса - об этом подробно описывается в спецификации JavaBeans.

Также отметим, что прародитель всех классов Object (все классы в JAVA наследуются от Object, даже если Вы не указали это явно) имеет метод toString(). Этот метод используется очень часто и к нему обращаются в случае если хотят вывести объект на экран. Самая простая строка System.out.println(s); на самом деле вызовет у объекта s метод toString() и его результат уже покажет на экране.
Сам Object делает это не очень красиво - он выводит имя класса и некое число (адрес в памяти). Часто это плохо информативно, да и не красиво.
Т.к. наши классы Student и Group будут выводится на экран мы изменим поведение toString() для них - посмотрите их в коде.

И еще - вряд ли нас устроит, что студенты в списке будут идти не по алфавиту. Коллекции позволяют делать сортировку, но для этого надо выполнить условие - то, что находится в коллекции должно иметь способы для сравнения. Ведь по сути при составлении списка студентов мы располагаем их по алфавиту - вот такой прицип и будет использован.

Для того, чтобы студенты имели "возможность сравниваться друг с другом" мы должны реализовать интерфейс Comparable, который имеет только один метод - public int compareTo(Object obj)
Как видите метод возвращает целое число 1, если объект obj меньше, 0 - если они равны, и -1 - если obj больше.
Класс String имеет такой метод и мы можем им воспользоваться. Мы используем не только фамилию, но и имя, отчество, дату рождения и группу. Реализацию смотрите в коде.

А теперь приведем код наших классов с комментариями

Student.java
Код

package students.logic;

import java.text.DateFormat;
import java.util.Date;

public class Student implements Comparable
{
    // поле ИД СТУДЕНТА
    private int studentId;

    // поле ИМЯ
    private String firstName;

    // поле ФАМИЛИЯ
    private String surName;

    // поле ОТЧЕСТВО
    private String patronymic;

    // поле ДАТА РОЖДЕНИЯ
    private Date dateOfBirth;

    // поле ПОЛ
    private char sex;

    // поле ИД ГРУППЫ
    private int groupId;

    // поле ГОД ОБУЧЕНИЯ
    private int educationYear;


    // get/set для ДАТА РОЖДЕНИЯ
    public Date getDateOfBirth()
    {
      return dateOfBirth;
    }
    public void setDateOfBirth(Date dateOfBirth)
    {
      this.dateOfBirth = dateOfBirth;
    }

    // get/set для ГОД ОБУЧЕНИЯ
    public int getEducationYear()
    {
      return educationYear;
    }
    public void setEducationYear(int educationYear)
    {
      this.educationYear = educationYear;
    }

    // get/set для ИД ГРУППЫ
    public int getGroupId()
    {
      return groupId;
    }
    public void setGroupId(int groupId)
    {
      this.groupId = groupId;
    }

    // get/set для ИД СТУДЕНТА
    public int getStudentId()
    {
      return studentId;
    }
    public void setStudentId(int studentId)
    {
      this.studentId = studentId;
    }

    // get/set для ИМЯ
    public String getFirstName()
    {
      return firstName;
    }
    public void setFirstName(String firstName)
    {
      this.firstName = firstName;
    }

    // get/set для ОТЧЕСТВО
    public String getPatronymic()
    {
      return patronymic;
    }
    public void setPatronymic(String patronymic)
    {
      this.patronymic = patronymic;
    }

    // get/set для ФАМИЛИЯ
    public String getSurName()
    {
      return surName;
    }
    public void setSurName(String surName)
    {
      this.surName = surName;
    }

    // get/set для ПОЛ
    public char getSex()
    {
      return sex;
    }
    public void setSex(char sex)
    {
      this.sex = sex;
    }

    // DateFormat - класс для преобразования даты 
    // в строку в определеннном формате. 
    // Подробнее смотрите документацию по этому методу
    public String toString()
    {
      return surName
        + " "
        + firstName
        + " "
        + patronymic
        + ", "
        + DateFormat.getDateInstance(DateFormat.SHORT).format(
          dateOfBirth) + ", Группа ИД=" + groupId+ " Год:"+educationYear;
    }

    public int compareTo(Object obj)
    {
      return this.toString().compareTo(obj.toString());
    }
}


Group.java
Код

package students.logic;

public class Group
{
    // поле ИД ГРУППЫ
    private int groupId;

    // поле ИМЯ ГРУППЫ
    private String nameGroup;

    // поле КУРАТОР
    private String curator;

    // поле СПЕЦИАЛЬНОСТЬ
    private String speciality;

    // get/set для КУРАТОР
    public String getCurator()
    {
      return curator;
    }
    public void setCurator(String curator)
    {
      this.curator = curator;
    }

    // get/set для ИД ГРУППЫ
    public int getGroupId()
    {
      return groupId;
    }
    public void setGroupId(int groupId)
    {
      this.groupId = groupId;
    }

    // get/set для ИМЯ ГРУППЫ
    public String getNameGroup()
    {
      return nameGroup;
    }
    public void setNameGroup(String nameGroup)
    {
      this.nameGroup = nameGroup;
    }

    // get/set для СПЕЦИАЛЬНОСТЬ
    public String getSpeciality()
    {
      return speciality;
    }
    public void setSpeciality(String speciality)
    {
      this.speciality = speciality;
    }

    public String toString()
    {
      return nameGroup;
    }
}


Давайте немного подробнее остановимся на ManagementSystem.java. Этот класс пока не очень сложный, но все таки он дает нам некоторую функциональность. Он позволяет добавить нового студента, изменить параметры студента, перевести всех студентов одной группы в другую (частая ситуация, когда после года обучения студенты переходят в другую группу), удалить студентов из группы - для тех, кто закончил учиться.
Если посмотреть на конструктор ManagementSystem, то можно увидеть, что мы вызываем загрузку списка студентов и групп. Таким образом получится, что при каждом создании ManagementSystem мы будем делать одну и туже работу, что конечно же не очень хорошо.
Для того, чтобы избежать такого мы воспользуемся шаблоном проектирования (очень интересная область знаний, которая описывает стандартные решения для каких-либо проектных задач) Singletone. Этот шаблон показывает, как можно использовать только один экземпляр объекта. Реализация делается следующим образом:
- конструктор объявляется private (теперь напрямую создать объект нельзя)
- описывается одна статическая переменная такого же класса (т.е. один экзампляр на класс - т.е. для всех объектов класса будет только одна такая переменная)
- создается метод (обычно его называют getInstance), который возвращает ссылку на единственный объект.

Также Вы увидите частое обращение к классу Iterator. Это специальный класс, который позволяет работать с коллекций. Идея его очень простая - при создании он указывает на начало коллекции перемещается к следующему элементу с вызовом метода next. Этот метод также возвращает тот объект из коллекции, на который в данный момент указывает итератор. А метод hasNext проверяет есть ли еще элементы в коллекции.
Таким образом просмотреть ЛЮБУЮ коллекцию (в примере назовем ее col) можно с помощью такой конструкции
Код

for(Iterator i=col.iterator(); i.hasNext(); ) {
   Object obj = i.next();
}


После кода мы еще остановимся на некоторых функциях.
Код

package students.logic;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;

public class ManagementSystem
{
    private List groups;

    private Collection students;


    // Для шаблона Singletone
    // статическая переменная
    private static ManagementSystem instance;
    // закрытый конструктор
    private ManagementSystem() {
      loadGroups();
      loadStudents();
    }
    // метод getInstance - проверят, инициализирована ли статическая 
    // переменная (в случае надобности делает это) и возвращает
    public static synchronized ManagementSystem getInstance()
    {
      if (instance == null) {
        instance = new ManagementSystem();
      }
      return instance;
    }

    // Метод, который вызывается при запуске класса
    public static void main(String[] args)
    {
      ManagementSystem ms = ManagementSystem.getInstance();

      // Просмотр полного списка групп
      System.out.println("Полный список групп");
      System.out.println("*******************");
      List allGroups = ms.getGroups();
      for (Iterator i = allGroups.iterator(); i.hasNext();) {
        System.out.println(i.next());
      }
      System.out.println();
  
      // Просмотр полного списка студентов
      System.out.println("Полный список студентов");
      System.out.println("***********************");
      Collection allStudends = ms.getAllStudents();
      for (Iterator i = allStudends.iterator(); i.hasNext();) {
        System.out.println(i.next());
      }
      System.out.println();
  
      // Вывод списков студентов по группам
      System.out.println("Список студентов по группам");
      System.out.println("***************************");
      List groups = ms.getGroups();
      // Проверяем все группы
      for(Iterator gi=groups.iterator(); gi.hasNext();) {
        Group group = (Group)gi.next();
        System.out.println("---> Группа:"+group.getNameGroup());
        // Получаем список студентов для конкретной группы
        Collection students = ms.getStudentsFromGroup(group, 2006);
        for(Iterator si=students.iterator(); si.hasNext();) {
          System.out.println(si.next());
        }
      }
      System.out.println();

      // Создадим нового студента и добавим его в список
      Student s = new Student();
      s.setStudentId(5);
      s.setFirstName("Игорь");
      s.setPatronymic("Владимирович");
      s.setSurName("Перебежкин");
      s.setSex('М');
      Calendar c = Calendar.getInstance();
      c.set(1991, 8, 31);
      s.setDateOfBirth(c.getTime());
      s.setGroupId(1);
      s.setEducationYear(2006);
      System.out.println("Добавление студента:"+s);
      System.out.println("********************");
      ms.insertStudent(s);
      System.out.println("--->> Полный список студентов после добавления");
      allStudends = ms.getAllStudents();
      for (Iterator i = allStudends.iterator(); i.hasNext();) {
        System.out.println(i.next());
      }
      System.out.println();

      // Изменим данные о студенте - Перебежкин станет у нас Новоперебежкиным
      // Но все остальное будет таким же - создаем студента с таким же ИД
      s = new Student();
      s.setStudentId(5);
      s.setFirstName("Игорь");
      s.setPatronymic("Владимирович");
      s.setSurName("Новоперебежкин");
      s.setSex('М');
      c = Calendar.getInstance();
      c.set(1991, 8, 31);
      s.setDateOfBirth(c.getTime());
      s.setGroupId(1);
      s.setEducationYear(2006);
      System.out.println("Редактирование данных студента:"+s);
      System.out.println("*******************************");
      ms.updateStudent(s);
      System.out.println("--->> Полный список студентов после редактирования");
      allStudends = ms.getAllStudents();
      for (Iterator i = allStudends.iterator(); i.hasNext();) {
        System.out.println(i.next());
      }
      System.out.println();

      // Удалим нашего студента
      System.out.println("Удаление студента:"+s);
      System.out.println("******************");
      ms.deleteStudent(s);
      System.out.println("--->> Полный список студентов после удаления");
      allStudends = ms.getAllStudents();
      for (Iterator i = allStudends.iterator(); i.hasNext();) {
        System.out.println(i.next());
      }
      System.out.println();

      // Здесь мы переводим всех студентов одной группы в другую
      // Мы знаем, что у нас 2 группы
      // Не совсем элегантное решение, но пока сделаем так
      Group g1 = (Group)groups.get(0);  
      Group g2 = (Group)groups.get(1);
      System.out.println("Перевод студентов из 1-ой во 2-ю группу");
      System.out.println("***************************************");
      ms.moveStudentsToGroup(g1, 2006, g2, 2007);
      System.out.println("--->> Полный список студентов после перевода");
      allStudends = ms.getAllStudents();
      for (Iterator i = allStudends.iterator(); i.hasNext();) {
        System.out.println(i.next());
      }
      System.out.println();
  
      // Удаляем студентов из группы      
      System.out.println("Удаление студентов из группы:"+g2+" в 2006 году");
      System.out.println("*****************************");
      ms.removeStudentsFromGroup(g2, 2006);
      System.out.println("--->> Полный список студентов после удаления");
      allStudends = ms.getAllStudents();
      for (Iterator i = allStudends.iterator(); i.hasNext();) {
        System.out.println(i.next());
      }
      System.out.println();
  
    }

    // Метод создает две группы и помещает их в коллекцию для групп
    public void loadGroups()
    {
      // Проверяем - может быть наш список еще не создан вообще
      if (groups == null) {
        groups = new ArrayList();
      } else {
        groups.clear();
      }
      Group g = null;

      g = new Group();
      g.setGroupId(1);
      g.setNameGroup("Первая");
      g.setCurator("Доктор Борменталь");
      g.setSpeciality("Создание собачек из человеков");
      groups.add(g);

      g = new Group();
      g.setGroupId(2);
      g.setNameGroup("Вторая");
      g.setCurator("Профессор Преображенский");
      g.setSpeciality("Создание человеков из собачек");
      groups.add(g);
    }

    // Метод создает несколько студентов и помещает их в коллекцию
    public void loadStudents()
    {
      if (students == null) {
        students = new TreeSet();
      } else {
        students.clear();
      }

      Student s = null;
      Calendar c = Calendar.getInstance();

      // Вторая группа
      s = new Student();
      s.setStudentId(1);
      s.setFirstName("Иван");
      s.setPatronymic("Сергеевич");
      s.setSurName("Степанов");
      s.setSex('М');
      c.set(1990, 3, 20);
      s.setDateOfBirth(c.getTime());
      s.setGroupId(2);
      s.setEducationYear(2006);
      students.add(s);

      s = new Student();
      s.setStudentId(2);
      s.setFirstName("Наталья");
      s.setPatronymic("Андреевна");
      s.setSurName("Чичикова");
      s.setSex('Ж');
      c.set(1990, 6, 10);
      s.setDateOfBirth(c.getTime());
      s.setGroupId(2);
      s.setEducationYear(2006);
      students.add(s);

      // Первая группа
      s = new Student();
      s.setStudentId(3);
      s.setFirstName("Петр");
      s.setPatronymic("Викторович");
      s.setSurName("Сушкин");
      s.setSex('М');
      c.set(1991, 3, 12);
      s.setDateOfBirth(c.getTime());
      s.setEducationYear(2006);
      s.setGroupId(1);
      students.add(s);

      s = new Student();
      s.setStudentId(4);
      s.setFirstName("Вероника");
      s.setPatronymic("Сергеевна");
      s.setSurName("Ковалева");
      s.setSex('Ж');
      c.set(1991, 7, 19);
      s.setDateOfBirth(c.getTime());
      s.setEducationYear(2006);
      s.setGroupId(1);
      students.add(s);
    }

    // Получить список групп
    public List getGroups()
    {
      return groups;
    }
    
    // Получить список всех студентов
    public Collection getAllStudents()
    {
      return students;
    }

    // Получить список студентов для определенной группы
    public Collection getStudentsFromGroup(Group group, int year) 
    {
      // 
      Collection l = new TreeSet();
      for(Iterator i=students.iterator(); i.hasNext();) {
        Student s = (Student)i.next();
        if(s.getGroupId()==group.getGroupId() && s.getEducationYear() == year) {
            l.add(s);
        }
      }
      return l;
    }

    // Перевести студентов из одной группы с одним годом обучения в другую группу с другим годом обучения
    public void moveStudentsToGroup(Group oldGroup, int oldYear, Group newGroup, int newYear)
    {
      for(Iterator i=students.iterator(); i.hasNext();) {
        Student s = (Student)i.next();
        if(s.getGroupId()==oldGroup.getGroupId() && s.getEducationYear()==oldYear) {
            s.setGroupId(newGroup.getGroupId());
            s.setEducationYear(newYear);
        }
      }
    }

    // Удалить всех студентов из определенной группы    
    public void removeStudentsFromGroup(Group group, int year)
    {
      // Мы создадим новый список студентов БЕЗ тех, кого мы хотим удалить.
      // Возможно не самый интересный вариант. Можно было бы продемонстрировать
      // более элегантный метод, но он требует погрузиться в коллекции более глубоко
      // Здесь мы не ставим себе такую цель
      List tmp = new ArrayList();
      for(Iterator i=students.iterator(); i.hasNext();) {
        Student s = (Student)i.next();
        if(s.getGroupId()!=group.getGroupId() || s.getEducationYear() != year) {
            tmp.add(s);
        }
      }
      students = tmp;
    }

    // Добавить студента
    public void insertStudent(Student student)
    {
      // Просто добавляем объект в коллекцию
      students.add(student);
    }
    
    // Обновить данные о студенте
    public void updateStudent(Student student)
    {
      // Надо найти нужного студента (по его ИД) и заменить поля
      Student updStudent = null;
      for(Iterator i=students.iterator(); i.hasNext();) {
        Student s = (Student)i.next();
        if(s.getStudentId() == student.getStudentId()) {
            // Вот этот студент - запоминаем его и прекращаем цикл
            updStudent = s;
            break;
        }
      }
      updStudent.setFirstName(student.getFirstName());
      updStudent.setPatronymic(student.getPatronymic());
      updStudent.setSurName(student.getSurName());
      updStudent.setSex(student.getSex());
      updStudent.setDateOfBirth(student.getDateOfBirth());
      updStudent.setGroupId(student.getGroupId());
      updStudent.setEducationYear(student.getEducationYear());
    }
    
    // Удалить студента
    public void deleteStudent(Student student)
    {
      // Надо найти нужного студента (по его ИД) и удалить
      Student delStudent = null;
      for(Iterator i=students.iterator(); i.hasNext();) {
        Student s = (Student)i.next();
        if(s.getStudentId() == student.getStudentId()) {
            // Вот этот студент - запоминаем его и прекращаем цикл
            delStudent = s;
            break;
        }
      }
      students.remove(delStudent);
    }
}


Итак, самое интересно на взгляд автора кроется в функциях
loadGroups, loadStudents, getStudentsFromGroup, moveStudentsToGroup, removeStudentsFromGroup, insertStudent, updateStudent, deleteStudent.

loadGroups и loadStudent - эти функции на сегодня делают неблагодарную работу. Они создают данные и помещают их в коллекции. Коллекции - это очень мощные структуры данных, которые являются по сути своей списками. С ними очень удобно работать - они позволяют вам хранить данные, искать, удалять, делать выборки и т.д. Разнообразие коллекций объясняется тем, что использовать один универсальный список не получается - ведь требования к списку могут различаться. Одному программисту надо, чтобы список как можно быстрее добавлял данные. Другому - чтобы имел сортировку. Третьему - чтобы имел потокозащищенность. И т.д.

Остальные функции как раз и демонстрируют в какой-то мере возможности коллекций.
Давайте рассмотрим их:
insertStudent - здесь вы можете видеть, что добавление в коллекцию - очень простая опреация. Вы просто добавляете и все. Вам не надо думать о том, как ее расширять, где взять памяти. Вы просто вызываете метод add.

deleteStudentupdateStudent - эти функции демонстрируют некоторые сложности при работе с коллекциями. Во-первых здесь есть демонстрация того, каким образом можно простмотреть все элементы коллекции - это итератор. Его можно рассматривать, как некий указатель, который можно установить на начало и потом перемещать по списку (коллекции).
В остальном функции достаточно похожи. В случае удаления мы просто удаляем найденного студента. В случае изменения - заменяем поля студента на новые.

Рекомендуем: Для более тонкого понимания как ищутся объекты в коллекциях рекомендуем вам посмотреть описания методов hashCode и equals. Коллекции часто рассматривают объекты как одинаковые, если у них одинаковый хэшкод. Мы привели не самый интересные решения. Если переопределить для класса Student методы hashCode и equals, которые будут считать студентов "равными" если у них одинаковый ИД, то процесс удаления будет короче - просто вызов students.remove(student).

getStudentsFromGroup - здесь мы получаем список студентов для определенной группы. Как видите мы создаем еще один список, куда перемещаем всех студентов из конкретной группы и возвращаем уже его.

moveStudentsToGroup - здесь все проще в связи с тем, что при просмотре коллекции мы получаем ссылки на реальные объекты и их изменение будет сразу заметно.

removeStudentsFromGroup - здесь предложен следующий вариант - создать новый список из студентов, которые должны остаться.

Также надо обратить внимание на то, что наши списки групп и студентов - разные. ArrayList - простой список, который имеет порядок в каком были добавлены эдементы. TreeSet - список, который умеет сортировать. Но умеет сортировать объекты только такого класса, который имеет реализацию интерфейса Comparable.

Ну и конечно вам просто необходимо просмотреть код метода main (напоминаю еще раз). В нем мы сделали несколько вызовов реализованных методов и убедились, что они работают.

ВНИМАНИЕ!!!
Наши классы находятся в пакете students.logic - обратите внимание на первую строку во всех файлах - package students.logic;

Это значит, что Вам надо создать вот такую структуру каталогов:
Код

- student
   - logic
      - Student.java
      - Group.java
      - ManagementSystem.java


Для того, чтобы собрать наш проект Вам надо установить текующую директорию, в которой находится директория students.
Для сборки нашего проекта используется команда
Код

javac students/logic/*.java

Для запуска
Код

java -cp . students.logic.ManagementSystem


Если Вы при запуске увидели что-то непонятное -не расстраивайесь. Это проблема кодировки. Давайте перейдем к следующей части, где сможем насладится нормальным видом нашего приложения.
Итак Часть 2 - Введение в GUI
Автор: AntonSaburov






Просмотров: 5223

 

 

Новые статьи:


Популярные:
  1. Как сделать цикличным проигрывание MIDI-файла?
  2. Создание AVI файла из рисунков
  3. Как устройство "отключить в данной конфигурации"?
  4. Kто в данный момент присоединен через Сеть?
  5. Как узнать количество доступной памяти?
  6. Как реализовать в RichEdit разноцветный текст?
  7. Как скрыть свое приложение от ProcessViewer
  8. Как программно нажать/скрыть/показ кнопку "Start"?
  9. Модуль работы с ресурсами в PE файлах
10. Функции вызова диалоговых окон выбора
11. Проверка граматики средствами Word'а из Delphi.
12. Модуль для упрощенного вызова сообщений
13. Функции для записи и чтение своих данных в, ЕХЕ- файле
14. Рекурсивный просмотр директорий
15. Network Traffic Monitor
16. Разные модули
17. Универсальная функция для обращения к любым экспортируем функциям DLL
18. Библиотека от VladS
19. Протектор для UPX'а
20. Еще об ICQ, сообщения по контакт листу?
21. Использование открытых интерфейсов
22. Теория и практика использования RTTI
23. Работа с TApplication
24. Примеры использования Drag and Drop для различных визуальных компонентов
25. Что такое порт? Правила для работы с портами
26. Симфония на клавиатуре
27. Загрузка DLL
28. Исправление автоинкремента
29. Взаимодействие с чужими окнами
30. Проверить дубляжи в столбце


 

 

 
 
На главную