Developer PATH

Результаты прохождения курса "28 задач повышающейся сложности"

April 09, 2021 | 11 Minute Read

Что ж, вот и пришла пора написать свой первый серьёзный пост здесь. Изначально я завёл блог в VK, но там не хватало возможности форматировать текст и было решено переехать сюда. Гитхаб позволяет размещать у себя статические веб-страницы бесплатно, а чтобы с лёгкостью вести простой блог придуман Jekyll, который переводит контент из markdown в статические веб-страницы. Это позволяет один раз настроить шаблоны, которые будут применяться в нашем блоге и думать только о написании текстов.

ВКонтакте я успел описать решение половины задач курса, сюда перенес лишь моменты с различными открытиями и интересностями. Тот блог я планирую свернуть, целиком сосредоточившись на этом, потому ссылку на него не привожу. Код всех учебных задач, текущих и будущих, можно найти тут.
Курс “28 задач повышающейся сложности” Высшей школы программирования Сергея Бобровского я проходил на языке Java, с некоторым опытом программирования на C#. При решении первых задач больше времени уходило на разбирательства с синтаксисом, то есть Java для меня был чем-то новым.
Перейдём к моим наблюдениям и открытиям.

Тип данных char

Оказывается, что переменной символьного типа можно присвоить не только конкретный символ (Например char c = '0'), но и целочисленное значение (char c = 48, что равносильно предыдущему примеру) от 0 до 65535. В этом случае она опять же будет хранить символ, а целочисленное значение будет указывать на номер символа в таблице символов Unicode (UTF-16). При вычитании или сложении символов операции проводятся над их кодами (Пример: ‘5’ - ‘1’ = 4 или ‘5’ + ‘1’ = 102; код символа ‘1’ - 49, символа ‘5’ - 53; Поэтому мы и имеем такие результаты). Цифры в таблице кодировки идут по порядку от 0 до 9 и имеют коды от 48 до 57, поэтому мы и имеем такие результаты в примере.
При вычитании значений char получим код типа int, чтобы у нас в итоге был символ можно использовать преобразование типов: добавить (char) перед результатом вычитания.

Теперь немного о буквах.
Решил посмотреть коды различных символов нашего алфавита и увидел, что ‘я’ имеет код 1103, а ‘ё’- 1105. Стоит учитывать этот момент, если вы захотите сортировать строки, содержащие ‘ё’, в лексикографическом порядке.

Прерывание цепочки вложенных циклов

Есть цикл в цикле, мы дошли до какого-то значения когда нужно закончить их работу, но впереди ещё множество итераций, а простой break выводит только из внутреннего цикла. Здесь поможет break с меткой. Пишем название метки перед блоком кода, который хотим прервать, после чего используем оператор break c именем нашей метки. Результат - работа циклов прервана.

...
label: //Метка перед блоком кода, который нужно будет прервать
    for (int i = 0; i < 4; i++) {
      System.out.print("Проход " + i + ": ");
      for (int j = 0; j < 50; j++) {

        if (j == 10) {
          break label; //Выйти из циклов
        }
        System.out.print(j + " ");
    }
...//После прерывания исполняем дальнейший код

Проверка на выход за границы двумерного массива

В задачах 3 и 23 проходила работа с двумерными массивами, для их решения нужно было изменять значения определённых элементов и их 4 соседей, по вертикали и горизонтали. Самое простое - сделать цикл, который будет брать и менять элемент с соседями. Но что если он находится на краю массива, а в цикле стоит изменение всех соседних элементов. При решении третьей задачи я, только начавший постигать Java, использовал много конструкций if. Даже приведу код, чтобы вы сами увидели насколько это громоздко.

    if (field[i][j] == 1 && i == 0) { //Захват клеток, у верхней границы
        if (j == 0) {
            if (field[i+1][j] == 0) {field[i+1][j] = 2;}
            if (field[i][j+1] == 0) {field[i][j+1] = 2;}
        } else if (j == field[i].length - 1) {
            if (field[i+1][j] == 0) {field[i+1][j] = 2;}
            if (field[i][j-1] == 0) {field[i][j-1] = 2;} 
        } else {
            if (field[i+1][j] == 0) {field[i+1][j] = 2;}
            if (field[i][j-1] == 0) {field[i][j-1] = 2;} 
            if (field[i][j+1] == 0) {field[i][j+1] = 2;}
        }
    }

    if (field[i][j] == 1 && i == field.length - 1) {  //Захват клеток, у нижней границы
        if (j == 0) {
            if (field[i-1][j] == 0) {field[i-1][j] = 2;}
            if (field[i][j+1] == 0) {field[i][j+1] = 2;}
        } else if (j == field[i].length - 1) {
            if (field[i-1][j] == 0) {field[i-1][j] = 2;}
            if (field[i][j-1] == 0) {field[i][j-1] = 2;} 
        } else {
            if (field[i-1][j] == 0) {field[i-1][j] = 2;}
            if (field[i][j-1] == 0) {field[i][j-1] = 2;} 
            if (field[i][j+1] == 0) {field[i][j+1] = 2;}
        }
    }

    if (field[i][j] == 1 && i !=0 && i != field.length - 1) { //Захват средних клеток
        if (j == 0) {
            if (field[i-1][j] == 0) {field[i-1][j] = 2;} 
            if (field[i+1][j] == 0) {field[i+1][j] = 2;}
            if (field[i][j+1] == 0) {field[i][j+1] = 2;}
        } else if (j == field[i].length - 1) {
            if (field[i-1][j] == 0) {field[i-1][j] = 2;} 
            if (field[i+1][j] == 0) {field[i+1][j] = 2;}
            if (field[i][j-1] == 0) {field[i][j-1] = 2;}  
        } else {
            if (field[i-1][j] == 0) {field[i-1][j] = 2;} 
            if (field[i+1][j] == 0) {field[i+1][j] = 2;}
            if (field[i][j-1] == 0) {field[i][j-1] = 2;} 
            if (field[i][j+1] == 0) {field[i][j+1] = 2;}
        }
    }

Эта махина позволяла менять только те элементы, что находятся в массиве. К 23 задаче я изучил язык получше и смог применить другое решение.

    int[] neighbors = new int[] {i,i,i+1,i-1,j,j,j+1,j-1}; //Массив индексов соседей
    for (int c = 0; c < 4; c++) { //Изменение соседних элементов
        try {
            if (tree2D[neighbors[c]][neighbors[7-c]] < '3'){ //Выполняем условия задачи
                tree2D[neighbors[c]][neighbors[7-c]] = '0'; //Выполняем условия задачи
            }
        } catch (ArrayIndexOutOfBoundsException e){
            continue; //Ловим исключение и пробуем изменить оставшихся соседей
        }
    }

Как видно, его суть кроется в использовании исключений. Я сделал массив из 8 чисел, для обозначения индексов четырёх соседних элементов, числа расположены так, что если брать крайние элементы и двигаться к середине, то мы пройдёмся по всем соседям. Если элемент находиться за границей массива, то мы просто ловим выброшенное исключение, которое, в ином случае, прекратило бы работу программы и пробуем изменить другой соседний элемент.

И это всё?

Всё, из написанного в блоге VK, чем бы я хотел поделиться тут. От самого курса я получил гораздо больше, здесь лишь та информация, которую не просто найти в интернете. Многие вещи, которые я использовал при решении задач были найдены на популярнейших сайтах, находящихся в топе поисковой выдачи и дублировать её здесь нет смысла(не хочется первым постом портить отношения с роботами поисковиков:). Мой уровень знания языка заметно подрос, благодаря этому курсу, а что ещё важнее – прокачалось мышление. Также я смог увидеть некоторые свои типичные ошибки, над которыми нужно поработать, чтобы стать лучше.

Заключение

Вот и подошёл к концу первый серьёзный пост в этом блоге. Здесь были показаны некоторые наблюдения и открытия, впечатлившие меня при прохождении курса.

Я уже начал привыкать к markdown, вспомнил как проходил курс, порадовался за свой рост, ибо несколько раз хотелось всё бросить, потому что было сложно, но я справился и теперь собираюсь двигаться дальше, к более сложным задачам и новым уровням знаний.

А на этом всё, спасибо за внимание и до следующей встречи на страницах моего блога.