Показаны сообщения с ярлыком java. Показать все сообщения
Показаны сообщения с ярлыком java. Показать все сообщения

пятница, 16 августа 2019 г.

Я учусь решать задачи по программированию

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


Исторически первым я пришел на Codewars (мой аккаунт там). И он мне чем-то напомнил изучение иностранных языков на Duolingo: можно точно так же с полного нуля начать с самых простых задач, где требуется определить четное ли число или написать простейший for loop, минимум теории – максимум практики:

Конечно, тем, кто вообще никогда не программировал (вот у нас в школе, которую я закончил в 2002 году, программирования не было; на химфаке тоже; брат все учил по книжкам, но комп у нас был, поэтому что такое for loop, я смутно представлял), придется даже с простыми задачами лезть в гугл, на StackOverflow, но это будет не чтение теории, которую непонятно, куда применить, а поиски ответа на конкретный вопрос. Зато потом, после своего кривого алгоритма, можно будет посмотреть решение других людей и выучить из них еще больше. Я вот прямо скажу, что решив на Codewars 50 простейших задач по питону, я узнал о нем больше, чем от чтения 350-страничного учебника по Python для начинающих.

Задачи на codewars придумывают другие юзеры, и за несколько лет их накопилось несколько тысяч, самого разного уровня сложности (они обозначаются цифрами от 8 до 1: “восьмерки” – самые простые). На каких-то языках больше, на каких-то меньше. Некоторые задачи универсальны и могут быть переведены почти на любой язык. Другие – полностью зависят от фич определенного языка или группы языков. Этим Codewars очень понравился моему брату, который провел на этом сайте даже больше времени, чем я, и вышел на 21-е место в общей таблице пользователей (я где-то в районе 900-го места обитаю):


Мой брат глубоко изучил JavaScript, подтянул ассемблер, а когда в этом году на Codewars добавили языки формальных доказательств – тема, тесно связанная с его научной работой – такие как Coq, он зауважал этот сайт еще больше.

Я же начал прошлой весной с C#, потому что мы планировали переходить на кросс-платформенный движок Unity для создания приложений. Прорешал на нем несколько сотен задач, но год назад брат решил, что Unity нам не подходит и посоветовал мне учить нативную для андроида Java (на популярном нынче Kotlin там мало задач еще). Вот я так с тех пор на Java и решаю (а C# забыл). Недавно стал еще Python и JS решать – самые простые задачи 8-го и 7-го уровней. Для общего развития: на Python брат пишет скрипты по обработке файлов, на JS есть свой мобильный движок ReactNative. На Duolingo я так когда-то учил пять языков параллельно. Без практики почти все забыл, но основы в мозгу остались.

Но проблема с Codewars была и остается в том, что я не могу заставить себя решать сложные задачи (4 и сложнее). С простыми задачами я почти сразу вижу, как их решать и бросаюсь писать код. Через десять, максимум двадцать минут, моя программа проходит тесты, и я получаю моральное вознаграждение. Размышлять часами над сложными задачами, которые я, вполне возможно, и не могу решить в силу ограниченности моих познаний, мне скучно.

И вот недавно я завел аккаунт на LeetCode – еще одном сайте по решению задач, даже более известном, чем Codewars, потому что задачи оттуда часто используются компаниями для интервьюирования программистов. Там тоже есть (приблизительное) деление задач по сложности на простые, средние и трудные, но даже самая простая задача потребует уже определенных познаний. Правда, задачи при этом не привязаны к конкретному языку программирования, а тестируют абстрактные алгоритмы, которые фундаментально одинаковы и на Java, и на Python, и на C++.


Я снова рисковал завязнуть только на самых простых задачках (их там не тысячи, как на Codewars, но сотни – хватит на несколько месяцев), если бы не такая вещь как contests (соревнования). Раз в неделю, субботним вечером (по тихоокеанскому времени; в Китае утром) всем желающим предлагается решить за полтора часа 4 задачи (обычно 1 простая, 2 средние, 1 сложная).

Я никогда не участвовал в олимпиадах по программированию, но мне стало любопытно, решу ли я хотя бы одну задачу, и я записался на свой первый контест. После тренировок на Codewars я справился с простой задачей за 12 минут, у меня оставалось еще больше часа, но вот “средние” задачи для меня оказались не по зубам. Возможно, я допустил стратегическую ошибку, потратив все оставшееся время на задачу, которая объективно была сложнее другой. Но та другая была о “деревьях” – популярной структуре данных – а я решал о них всего одну задачу до этого и испугался деревьев. В итоге я больше так ничего и не решил и финишировал в районе 2000-го места.

Подобная же ситуация повторилась через неделю: я решил простую задачу за 12 минут, а потом все оставшееся время бился со средними задачами. Мне стало обидно: контест на полтора часа, а я отваливаюсь через 10 минут. И я засел за чтение теории (книги по алгоритмам, которые я считал, что мне не нужны в мобильной разработке), за разбор нерешенных мной задач и за решение задач сложнее “моей зоны комфорта”.

Уже на третьем контесте моя подготовка дала плоды: я смог решить три задачи и занять 550-е место. Возможно, они были чуть проще на этот раз, но если бы сильно проще, то другие участники обогнали бы меня как и раньше (при равенстве очков за решение задач места распределяются по времени, затраченном на решение). У меня даже оставалось 30 минут, чтобы подумать над “сложной” задачей. Но тут я уже был бессилен. Я дорешал ее через неделю с подсказкой брата (“делать двойную рекурсию”). Он сам явно мог бы решить там все задачи, все же это не “научные” проблемы, а вопросы для интервью, но он вообще не любит соревноваться на время. А меня порой захватывает азарт.

Неделю назад я поучаствовал в своем 4-м контесте, за полтора часа решил две задачи, а третью дорешал только через 25 минут после окончания (но я был на правильном пути; мой брат сказал, что она простая, и я должен был решать ее на контесте, но вот не решил). Ожидаемо оказался на 940-м месте, но все равно был доволен собой. После первых двух неудачных контестов мне было принципиально научиться решать эти задачи, а время – фактор второстепенный.


И вот я оказался с этими задачами на некоем распутье. Я не собираюсь проходить интервью в компании, я не собираюсь заниматься computer science в научном плане, для мобильных приложений умение решать алгоритмические задачи на время не пригодится. Нужно ли мне продолжать тратить время на эти сайты, или это чистая прокрастинация, которая только отвлекает меня от основной цели – научиться делать приложения?

Я уверен, что потрать я на контесты еще год-два, я буду гарантировано решать там четыре задачи и, если повезет, попадать в первую сотню. Денег или славы мне это не принесет. Я никогда не смогу обогнать лидеров, которые занимаются решением подобных задач всю жизнь (я посмотрел, что один из победителей контестов neal_wu еще в 2008-2010 годах брал золотые медали на международных олимпиадах по информатике, закончил Гарвард и работает в научном отделе Google; у него, наверно, такой отдых перед сном – зайти и решить все 4 задачи за 20 минут, напугав малышей).

Как я написал в Case Study #1, есть у меня талант решать задачи на время. В школе, универе или на интервью его очень ценят. Но в реальной работе над реальными проектами требуются другие навыки. В первую очередь не бросать работу, когда стало скучно, а доводить ее до конца. И я не знаю, какой контест смог бы меня научить такому, если даже финансовая мотивация от написания мобильных приложений не помогает мне довести хотя бы один из моих проектов до публикуемого состояния.

Все эти задачи в итоге сведутся не к изучению программирования, а к “занимательной математике”. К задачам, которые тоже раньше задавали на интервью: “Дано 12 шаров, один из них чуть тяжелее или легче остальных (неизвестно, легче или тяжелее), определите какой за три взвешивания”. Эту задачу я тоже решил, она хорошая, но никак не помогает разобраться с анимациями в андроиде.

Во многом я пишу этот пост, чтобы подвести некий итог решению задач. У меня так бывает: написал пост о фильмах и перестал их смотреть. Каждый учится по-своему, но если вы начинаете учить языки программирования я готов порекомендовать Codewars, если хотите посоревноваться в придумывании алгоритмов на время, LeetCode – неплохая платформа для этого. Мне она дала мотивацию разобраться с рекурсией. Я определенно продолжу решать там задачи время от времени, чтобы не забыть Java. Но надеюсь, что когда-нибудь напишу здесь пост и о своем собственном приложении, а не только о том, как я решаю чужие задачи.

суббота, 5 января 2019 г.

Как я разбирал Chronometer и нашел “пасхальное яйцо”

Кто чем занимался 31 декабря, а я разбирал класс Chronometer из стандартной библиотеки Андроида. Как раз в 11:30 pm закончил. И вот хочу поделиться, что нового я узнал, плюс порассуждать о своем прогрессе в обучении программированию в целом.

Я уже писал, что в прошлом году, наконец, решил серьезно учить программирование. Вначале цель была – “выпускать собственные приложения”, но постепенно, осознав размах задачи, я поменял ее на более реалистичную – “понимать код, который пишет мой брат”. Искать работу разработчиком я не собираюсь, что сильно ограничивает набор тем и инструментов для изучения.

Моему брату надоели баги в кросс-платформенном движке cocos2d-x, на котором написаны наши приложения, поэтому он переписывает код на нативную для Андроида Java. Я установил Android Studio и принялся тоже учить Java и Android по разнообразным туториалам и официальной документации, чередуя теорию с практикой, то есть с написанием собственных небольших проектов.

Во всех наших приложениях есть “Игра на время”, где надо дать как можно больше правильных ответов за одну минуту. Поэтому я счел необходимым научиться вставлять в свои проекты отсчет времени. Разумеется, такие штуки не придумать самому, а надо искать в интернете готовые решения и подгонять их под свои задачи. Решений оказалось много разных. Я скопировал некий код в свой математический квиз, потратил пару часов, чтобы заставить его правильно работать, и еще пару часов, чтобы разобраться, как именно он работает:


Цифры “9.9” в верхнем правом углу и есть обратный отсчет времени: у вас осталось меньше десяти секунд, чтобы перемножить 27 на 22.

Для другого своего проектика “Отгадай цифры” я попробовал другое решение, которое подсмотрел в одном из чужих проектов на github, – готовый андроидный виджит Chronometer (в toolbar справа):


Брат посмотрел на мои поделки и сказал: “А вот теперь разберись, как этот хронометр работает?”. “Как же я могу это сделать?” – спросил я. Я уже знал, что в Android Studio можно щелкнуть на любой класс, зажав Cmd, и он откроет определение этого класса, но для стандартных андроидных классов он открывал мне только заголовки методов, без их внутренностей.

Брат покачал головой, насколько можно быть тупым, чтобы полгода учить Android, и даже не установить его исходный код, который, как оказалось, полностью открыт, и может быть легко дозагружен из интернета. После чего на меня напало воодушевление: я же могу сейчас почитать код, который настоящие гуглеры пишут, а не какие-то чудики с гитхаба. И 31 декабря я сел разбирать Chronometer.

В моем ученическом представлении разобрать класс – это прочитать его код, понять все слова и что какой метод делает. Код у Chronometer – всего 400 строчек с пробелами и комментариями.

Тут мне пришла аналогия из химии – разбор полного синтеза. Понять все стадии, что на них происходит, какие реагенты используются и почему. И как понимание отдельных стадий не приведет к пониманию стратегии, почему именно таким путем синтезировали молекулу, и тем более не позволит самому придумывать такие синтезы, так и мой дилетантский разбор класса не позволит писать такой же код. Но без знания реакций и базовых химических концепций, тем более не удастся ничего сделать. На то она и учеба.

Первым делом я увидел, что Chronometer наследует от TextView. Год назад я не понял бы, что это значит. Поэтому я постараюсь писать как можно проще, а если вставлять куски кода, то только для иллюстративных целей, понимать его необязательно. В общем, Chronometer – это обыкновенный кусок текста на экране, но еще с несколькими дополнительными методами, которые и определяют, что в этом тексте будет написано.


public class Chronometer extends TextView {


И более того, сам механизм обновления текста через определенные промежутки времени такой же, как и в моем математическом квизе. Те самые Runnable, которые я два часа пытался понять:


    private final Runnable mTickRunnable = new Runnable() {
        @Override
        public void run() {
            if (mRunning) {
                updateText(SystemClock.elapsedRealtime());
                dispatchChronometerTick();
                postDelayed(mTickRunnable, 1000);
            }
        }
    };


А вот время они берут разное. У меня было System.currentTimeMilles() – число миллисекунд между текущим временем и 1 января 1970 года по Гринвичу. А в Chronometer оказалась другая функция SystemClock.elapsedRealTime(), которая возвращает число миллисекунд с последней загрузки устройства, что намного логичнее, так как я не хочу, чтобы автоматическая смена часового пояса или перевод часов самим пользователем сбил мой хронометр на пару часов.

Я стал читать про разные системы времени, встретил незнакомый мне модификатор native у elapsedRealTime(), но брат, с которым я делился своими разысканиями, сказал мне не лезть в такие дебри. Native – обращение к коду, написанному не на Java. Android как операционная система является разновидностью Linux, поэтому такие фундаментальные вещи, как системное время достается через код на C.

То же самое он сказал мне о словечке synchronized, которое используется как модификатор метода updateText(long now) в Chronometer. Что это нечто, связанное с многопоточностью и что мне о таком рано знать. И вообще непонятно, что оно в хронометре делает, потому что вывод текста на экран – это основной поток. Брат решил, что это баг.

А вот следующая функция isTheFinalCountDown() багом не была:


/**
     * @return whether this is the final countdown
     */
    public boolean isTheFinalCountDown() {
        try {
            getContext().startActivity(
                    new Intent(Intent.ACTION_VIEW, Uri.parse("https://youtu.be/9jK-NcRmVcw"))
                            .addCategory(Intent.CATEGORY_BROWSABLE)
                            .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT
                                    | Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT));
            return true;
        } catch (Exception e) {
            return false;
        }
    }


Из официальной документации класса Chronometer невозможно догадаться, что она делает. Проверят финальный ли это отсчет – что это значит?

Ответ есть в исходном коде. Моих познаний в Android уже было достаточно, чтобы понять, что при вызове этот метод попытается открыть в браузере YouTube, а там песню группы Europe “The Final Countdown” 1986 года. Популярную, судя по 500m+ просмотрам.

Зачем? Этим вопросом задавался не только я. Поэтому нашел обсуждение этого метода на Reddit, где все решили, что это так называемое “пасхальное яйцо” от разработчиков Google, добавленное, кстати, весьма недавно, в API 26 Android Oreo, вышедшей в 2017 году. Именно, чтобы заставить недоумевать, а потом улыбнуться тех, кто будет читать исходный код класса Chronometer.

Пользы от такого знания никакой, но почему-то именно в этот момент я решил, что не зря читал этот код. Намного полезнее мне было узнать, почему комментарии там написаны в таком странном формате с аннотациями типа @return. Официальная документация автоматически генерится из них с помощью программы Javadoc. То есть к исполняемому коду этот @return отношение не имеет, но позволяет писать комментарии к каждому классу и методу один раз в коде, а html для сайта со всеми гиперссылками получится автоматически.

А глобальный вывод в том, что в классе Chronometer нет ничего магического, чего я не смог бы сам воссоздать через TextView или собственный класс. Тем более оказалось, что в Chronometer нельзя задать формат, который показывал бы десятые доли секунды, как мы делаем в своих эппах. А когда знаешь, как он работает, то можешь и такую функциональность добавить и многое другое. У меня есть желание еще какой-нибудь несложный класс разобрать. Это как читать настоящие статьи вместо учебника по химии.

Но если продолжать сравнения, я со своими недоприложениями, которые нельзя в таком виде публиковать, и знанием, что каждое слово значит в Java, сейчас нахожусь на той же стадии обучения, где находится химик, который знает кучу химических реакций, умеет ставить их в лабе, но не умеет делать хроматографию и разбирать ЯМР. Он может побеждать на олимпиадах, сдавать экзамены и пройти практикум по органике на третьем курсе СПбГУ, но в реальном синтезе он не уйдет дальше первой стадии. Ни продукта выделить не сможет, ни понять, какая его структура и насколько он чистый.

Можно заявить, что это уже не органика, а аналитика. Настоящая химия – это смешивать реагенты, а не гонять колонки. Но это не так. Вот и в программировании настоящая работа для меня еще и не начиналась. Я все еще барахтаюсь на учебном уровне, где все работает. А дальше надо заниматься скучными, сложными вещами: архитектурой, дизайном, тестированием.

Научиться переворачивать свое приложение из portrait в landscape, чтобы все сохранилось и не поплыло, кажется такой мелочью по сравнению с логикой игры. Но мой брат эту логику может написать за пять минут, а сидит и думает он как раз над такими вещами, как все лучше сделать, чтобы все работало быстро на всех устройствах.

Помучившись, я переворачивание сделал, но я сам знаю, что у меня есть баг: эпп вылетает, если перевернуть во время “экрана победы”. И мне под силу этот баг исправить, но так не хочется. Я же не собираюсь этот проект публиковать.


Зато сегодня с утра за полчаса добавил в toolbar сердечки-жизни, которые гаснут с заполнением каждого ряда. Вот такое я уже умею. Научусь и с багами воевать. Когда-нибудь. Главное не грузить себя до потери всякой мотивации учиться.