Дискретные структуры: матан для айтишников
Посмотришь на любую программу обучения по IT-специальности, и тут же увидишь дисциплину «Дискретная математика» (возможно, под другим названием), обычно для перво- или второкурсников. И её наличие вполне разумно, поскольку дискретная математика и непрерывная математика (представленная на первом курсе институтов с незапамятных времён математическим анализом) — две грани единой Математики, — красивой, могучей науки.
Хотя раньше такого понятия, как «дискретная математика» вовсе не было, это не значит, что не возникало дискретных задач: Абель, Дирихле, Фибоначчи, Эйлер, чьи имена возникают по ходу изучения дискретной математики, — отнюдь не наши современники! Но просто в те времена для выделения самостоятельной ветви математики ещё не сложилось критической массы задач и приёмов, не было видно взаимосвязей между ними. А большое количество плодотворных взаимосвязей между, на первый взгляд, различными понятиями, — то, что математики в своей науке очень ценят.
Ну хорошо, математикам всё математическое интересно. А зачем дискретная математика программисту?
Зачем это айтишнику
Во-первых, многие идеи, которые особенно ярко иллюстрируются на дискретных задачах, неотъемлемы и для информатики. Взять, хотя бы, фундаментальные понятия рекурсии и индукции.
Рекурсия — это, дословно, возврат, обращение к самому себе. Хорошо известные вездесущие числа Фибоначчи проще всего определяются рекурсивно: первые два числа Фибоначчи равны единице, а каждое следующее число равно сумме двух своих предшественников: 1,1,2,3,5,8,… Таким образом, для вычисления очередного числа мы обращаемся к уже рассчитанным числам такого же вида. Трудно представить, как можно изучить функциональное программирование, да и многое из других областей информатики, не освоившись хорошо с рекурсией. Очень близкий процесс к рекурсии — это индукция, способ доказательства математических утверждений, при котором в доказательстве сложных случаев мы опираемся на более простые. Параллели с рекурсией очевидны, и действительно, обычное дело, когда индуктивное доказательство существования какого-то объекта можно переформулировать в описание рекурсивного способа построения этого объекта.
Раз речь зашла о таких фундаментальных вещах, как индукция и рекурсия, не могу не сказать, что многие приёмы, которые очень хорошо видны на примерах из дискретной математики, эффективны в математике в целом. Это не только индукция, но и принцип Дирихле, принцип выбора по среднему значению и другие.
Следующий элемент, без которого информатику нельзя представить — это графы. Простейшие алгоритмы на графах обязательно входят в любой, даже самый вводный, курс по алгоритмам. Скажем, с понятием гамильтонова цикла связана одна из классических задач информатики, задача коммивояжёра.
Ещё одно архиважное умение — считать точно и оценивать приблизительно количества. Например, как вычислить количество раз, которые выполняется операция сравнения в цикле:
Или вот ещё пример. Нужно из списка из 100 товаров выбрать 20, так, чтобы их суммарная стоимость была ровно 2000 рублей («без сдачи»). Это вариант классической задачи о рюкзаке. Допустим, ваш коллега, подумав ночь, предложил решать задачу перебором: перебрать всевозможные наборы из двадцати товаров, и, как только в ходе перебора возникнет нужный набор, выдать его в качестве ответа. Между прочим, характеристика «переборный» далеко не всегда ставит клеймо на алгоритме. Всё зависит от размера входных данных. Так вот, как прикинуть, удастся ли за разумное время решить перебором эту задачу выбора 20 объектов из 100?
Наконец, для современного «дизайнера алгоритмов» обязателен к пониманию и вероятностный метод. Это общий метод, позволяющей решать многие задачи в современной комбинаторике. Очень часто наилучшие решения задач, известные на сегодняшний день, получены именно этим методом. Для практика же овладение этим методом полезно постольку, поскольку вероятностные алгоритмы прочно заняли место в современной информатике. И при анализе работы таких алгоритмов очень помогает интуиция, развитая в ходе изучения вероятностного метода.
Онлайн-курс «Дискретные структуры»
С верой в то, что перечисленные понятия из дискретной математики действительно не помешают любому программисту, а, скорее, помешает их незнание, я читаю соответствующий курс на факультете ФИВТ МФТИ. А недавно у меня появилась возможность сделать онлайн-курс, чем я с радостью воспользовался. Записаться на него можно по ссылке. Главное, чего я пожелаю всем записавшимся: не побоявшись трудностей, пройти курс до самого конца, и получить заслуженное звание Дипломированного Дискретчика. В общем, чтобы MOOC прошёл без мук и обогатил знаниями! Да и собственная корысть у меня тут тоже есть: чем больше онлайн-учеников у меня будет, тем большему я смогу научиться, читая обсуждения и наблюдая статистику решения задач. Ведь учиться учить тоже никогда не поздно!
Какие знания потребуются
Для прохождения первых двух модулей потребуются только школьные знания. Третий модуль потребует знание основ математического анализа на уровне «что такое предел» и «какая из функций x 20 или 2 x растёт быстрее (чему равны производные функций)». Для последних трёх модулей понадобится представление о том, что такое вероятность, условная вероятность, математическое ожидание, дисперсия. Также хорошо бы знать, что такое базис и размерность линейного пространства. Если с вероятностью и линейной алгеброй вы не знакомы, можно записаться заодно на эти вводные курсы. Тогда как раз, к моменту, когда нам потребуются эти знания, они у вас будут.
Post scriptum
Меня можно было бы упрекнуть в конфликте интересов, всё-таки я математик, и, естественно, хочу приобщить к своей секте как можно больше завсегдатаев Хабра. В своё оправдание могу сослаться на этот ответ на Quora. Под большей частью тем, перечисленных в этом ответе, я готов лично подписаться, в онлайн-курс многие из них вошли. Ещё сошлюсь на подборку мнений яндексоидов.
Научный форум dxdy
Нужна ли дискретная математика программисту?
Здравствуйте, Уважаемые.
Заранее понимаю, что наверняка открываю тему-холливар. Хотя учитывая математическую направленность форума — вовсе не факт.
Уже больше дня спорю со знакомым, нужна ли дискретная математика программисту?
Мне кажется, что всё-таки хотя бы какие-то основы, но нужны. Он считает, что это надо оставить математикам, да и если что — их можно нанять для решения мат. задач, которые потом будут записаны кодерами.
Однако ни он, ни я толком доказать свою точку зрения не можем — опыт крайне малый.
Хотелось бы услышать мнение знающих людей, желательно с примерами, где в программировании всё это может пригодиться.
Причём не только на примере задач с условиями из разряда «идеально гладкий полый сферический объект завис в невесомости в абсолютном вакууме», а на реальных практических задачах, с которыми приходится встречаться среднему (и не только) программисту.
Как Вы считаете: нужна или нет? Почему да, а почему нет?
Хмм.
В общем-то я имел ввиду не только конкретно дискромат, но и другие разделы, которые традиционно считаются полезными для информатики и смежного.
Логика, теория графов, автоматом, ТАУ, лямбда-исчисления, рекурсия, разработка, анализ и проектирование алгоритмов, теория кодов, функции и матрицы, деревья, сети, алгебра (имею ввиду не ту, что в школе), комбинаторика, вероятность, теория множеств, чисел и т.д. и т.п.
Большую часть слов я даже не понимаю, оттого не берусь самостоятельно судить, что да к чему.
Но для пущей конкретности упрощу вопрос: «Нужна ли математика программисту?».
Cobert , это факт. Знать синтаксис и не знать основ — это тоже самое, что знать все падежи, времена и прочие особенности синтаксиса русского (английского) языка, но при этом не уметь их применять.
Ведь в принципе все императивные языки пользуются примерно одним инструментарием — циклы, условия, арифметика, ввод/вывод и тем, что уже выводится из этих кирпичиков.
Но понимая это, я ловлю себя на мысли, что примера полезности фундаментальщины привести я всё-таки не могу.
Основы логики однозначно нужны. Основы теории алгоритмов — наверняка тоже понадобятся (сортировка там, быстрый поиск в упорядоченном массиве — эти задачи встречаются на каждом шагу).
Без деревьев и рекурсии, например, не сможете перечислить все файлы, лежащие в каком-то каталоге и всех его подкаталогах, а подобные задачи встречаются на практике (то есть можно, конечно, их решить без рекурсии, но это уже сложнее, и теории знать нужно больше ).
Если будете программировать графику (что-нибудь сложнее, чем «просто нарисовать график и всё», это тоже часто встречается на практике), то знания основ линейной алгебры очень помогают. Хотя «просто нарисовать график» можно, используя уже готовые библиотеки.
Сложные математические теории, действительно, требуются не всем. Например, если Вам доведётся моделировать какие-то физические процессы, то не обойтись без знаний в области математического и функционального анализа, дифференциальных уравнений. Если будете разрабатывать язык программирования, нужны будут разделы математики, которые часто объединяют под термином «дискретная» — конечные автоматы, формальные грамматики и т.п.
Т.е. дальше всё уже зависит от задач, которые перед Вами поставят работодатели/заказчики.
Однако следует иметь в виду, что при прочих равных условиях даже для решения относительно простых практических задач предпочтительнее будет выглядеть человек с хорошими знаниями в математике, чем без таковых. Вероятнее, он сможет решать эти задачи лучше и быстрее, чем человек с гуманитарным образованием или без (высшего) образования, хотя из этого правила бывают исключения. Кроме того, тренировка ума, даваемая математикой, может сослужить хорошую службу при изучении имеющихся инструментов и технологий программирования (хотя бы потому, что почти все эти инструменты и технологии преимущественно рассчитаны на человека математического склада ума).
Всё зависит от языка, от приложения.
Графы безусловно — в системном программировании, например в приложениях работы с сетью, это просто букварь. Алгоритмы — безусловно в системном и прикладном программировании, потому что инструментов не хватает, иногда приходиться всё делать самому. Лямда-исчисления — это уже факт, который имеется в C# и в др. языках, не быть знакомым с основами уже не серьезно.
Теория кодирования — вперёд в криптографию, архивация, конвертирование и т.д. — безусловно данные типы приложений нужны. Деревья — к теории графов.
Комбинаторика — реально нужна, например может потребоваться генерировать полный перебор значений, например я давно делал такой перебор, чтобы пройти уровень в игре «Таинственный остров». Уж логика никак не поддавалась, пришлось программно генерировать все комбинации, в итоге так и не получилось пройти, по видимому из-за неверного комбинаторного алгоритма.
Некоторые предметы очень важны, для других достаточно знать основы и где искать информацию. Для серьёзных приложений нужна соответствующая подготовка.
По поводу программистов и математиков, есть книга от человека, который учавствовал в разработке поисковой системы Рамблер, в ней по опыту он не очень отзывается о «математиках в программировании».
Математик видит абстракцию.
Программист видит код, который в конечном счёте представляет из себя набор ассемблерных директив и не всегда понятно, зачем иногда нужны математические «замудренности».
И еще, пользоваться чужими наработками не всегда полезно, т.к. их порой пишут «тяп ляп».
worm2 , спасибо за чёткий и развёрнутый ответ.
А с формулировкой того, что эти инструменты изначально были созданы для математиков, как сказано в посл. абзаце, я вообще столкнулся впервые, однако мысль сильная.
И ещё, конкретно подразумевается под основами логики?
Только логические операции, условные высказывания, таблицы истинности и упрощение лог.выражений (как мне кажется, для практики именно этого из логики и достаточно), или в том числе какие-нибудь дополнительные понятия вроде аксиоматические систем, умозаключений и предикатов тоже нужны?
Спасибо также за примеры.
AlexDem , ух. Что есть программист — это вообще отдельный разговор, причём очень часто перерастающий в многостраничный флуд чуть ли не из разряда «какой язык круче».
А вообще, конечно, это действительно два разных понятия, которые, к сожалению, очень многие не различают в принципе, отчего считают, что программирование — тривиальная задача. Пример тому — многие школьники, научившиеся делать ввод/вывод в паскале, которым потом кажется, что море по колено и вообще ничего сложного в программировании нет (ну как же, конечно! Ведь всё есть, по сути, I/O).
Впрочем, поговорка «чем меньше знаем мы, тем кажется, что больше» работала, работает, и, к сожалению, довольно долго ещё будет работать.
Будь я работодателем, если бы передо мной стоял выбор «программист» или «кодер», я бы принял положительное решение, скорее всего, в сторону первого, так как второму обязательно бы пришлось часами в рабочее время спрашивать на форумах и тематических сайтах «а как это сделать», «а как то сделать», скачивать тонны чужого кода, библиотек и так далее. Словом, суммарные издержки на кодера, если, конечно, задача не совсем тривиальная, могут оказаться намного больше (скорость, отладка, качество кода, эффективность алгоритмов и СД и т.д.), отсюда и выбор.
Что же, получается, чистый кодер умеет? Выкликивать мышкой GUI и ставить ему на ивенты скопированный из сети код? Не лучший, на мой взгляд, вариант.
Тем более, как уже сказал delphiec , не всегда код в сети оказывается достаточного качества.
EtCetera , в общем-то согласен. Первыми программистами были чистые математики, но и задачи были чисто математические, отсюда и конфуз «математик — идеальный программист», хотя на деле программист — это совершенно другая специальность, но в которой всё же нужна математическая грамотность. Никто же не говорит, что идеальнейший физик или экономист — математик, хотя первые двое тоже оперирует множеством математических понятий. И всё же математик по разуму как физику, так и программисту ближе, чем, скажем, филолог или юрист.
Да, согласен. У хорошего программиста ещё должно и быть и какое-то эстетическое чувство, чувство стиля, так как написать код — это мало, надо ещё его поддерживать и развивать. А как его будешь улучшать, если архитектура изначально спроектирована так, что чтобы хоть что-то добавить приходится перелопачивать ровно треть, а то и половину, кода? И здесь как раз очень помогает в том числе и умение правильно абстрагировать и разделять вещи по каким-то схожим характеристикам.
Совсем недавно я видел код примерно на 1000 строк, где все переменные были названы a, b, c, ab, adfg, asdf (и это не названия переменных, содержащих длины и площади фигур) и так далее, а также не было ни одного отступа, хотя код реализовывал какой-то сложный мат. метод.
Вопрос — как долго кто-то, кроме самого автора (да и он тоже через 3-4 месяца), будет разбираться в этом коде и делать его понятным хотя бы специалисту в данной области? Увы, примерно 1/2-3/4 времени, которое квалифицированному читающему понадобилось бы чтобы написать эквивалентный, но читаемый код.
Словом, для промышленного программирования только математики действительно недостаточно хотя бы только из-за того, что на рынке доминирует парадигма ООП и всего лишь написать код — мало.
Системное программирование, в свою очередь, более близко к математике, так как оно ближе и к аппаратуре.
Вот именно поэтому я с вами абсолютно согласен. И ваше мнение не должно быть скромным — вполне похоже, что вы по профессии (бывшей али текущей) и есть программист, если оценивать по сказанному вами.
70% разработчиков только этим и занимаются, на более низком уровне всего лишь подключая чужие библиотеки к этим окошкам и кнопочкам.
Да и оно неудивительно — очень многим нынешним программистам работодатель ставит задачи из разряда «клонировать 1С специально для нашей фирмы», а тут, конечно же, уже есть наработанная база, состоявшиеся методики, технологии и удобные библиотеки. Вывод очевиден.
Какая знакомая ситуация! Где-то 2/3 месяца назад факт того, что я не могу проходить игру «пятнашки» за приемлемое время, если не начинаю думать, меня так оскорбил, что я взялся писать программу для решения этой задачи методом «грубого подбора».
delphiec , вроде как-то так получается, что основная нужда программистов в математике находится в области системного программирования — компиляторов, операционных систем, работы с оборудованием, сетью и так далее, а для прикладного программирования достаточно основ?
Забыл сказать: сейчас же вроде как развивается так называемые параллельные вычисления, многоядерные процессоры и т.д. А что по этому поводу? Ведь в ближайшее время не предвидится сильно большой простоты в этом вопросе.
Да. Во всяком случае, мне из «сложной» логики пока ничего не понадобилось.
Математика для программистов
В статье пойдет речь о роли математики в жизни разработчика ПО. Мы не будем углубляться в частные области вроде машинного обучения, моделирования или же компьютерной графики, а сделаем упор на базовых математических вещах.
Этот материал предназначен в первую очередь для тех, кто уже сделал свои первые шаги в IT-индустрии, но в своем образовании уделял больше времени языкам программирования и конкретным технологиям, нежели фундаментальным вещам.
Как изучать математику
Многим людям математика кажется очень сложной для понимания наукой. Чаще всего, такое мнение складывается из-за неправильного подхода к ее изучению. На самом деле можно сильно упростить себе жизнь, следуя рекомендациям ниже.
В освоении математики есть два уровня понимания. Первый уровень — идейный. Это осознание того, для чего нужны определенные объекты, какая задача решается и где это используется. Второй уровень понимания — детальный; это подробное изучение подробностей решения задачи. Иногда нужно разобраться в задаче на детальном уровня понимания, но в большинстве случаев — достаточно идейного.
Математика не любит баззвордов. Если вы читаете книгу и видите слова, смысл которых вам непонятен, пропускать их опасно, потому как вы можете поймать себя на том, что с какого-то момента не понимаете вообще ничего. Очень важно сразу останавливать себя, когда вам что-то непонятно.
Дискретная математика
Область математики, которая занимается дискретными структурами (например: графами, автоматами, утверждениями в логике). Основное ее отличие от обычной математики, которую вы изучали в школе, — ее объекты не могут изменяться так же гладко, как и вещественные числа.
В каком-то смысле все задачи, которые решаются в программировании, так или иначе относятся к дискретной математике, поэтому ее знание очень вам пригодится.
Логика
Это наука о формальных системах и доказательствах. Она лежит в основе компьютерных наук, ведь любой язык программирования — формальная система. Но не нужно заглядывать глубоко в теорию, чтобы найти применение этой науке в написании программ, да и вообще в решении задач.
Хорошо, если вы умеете писать решение задачи. Но так же важно понимать, каким образом вы можете доказать, что ваш код работает правильно. Большинство программ решает какую-либо математическую задачу, и вам нужно уметь доказывать, что ваша задача решена правильно. Тогда на помощь приходят методы логики и в частности исчисление высказываний.
Изучение логики целесообразно начинать с простых вещей: например с того, что такое высказывание, какие есть операции между ними, что такое правила вывода. Далее можно перейти к более прикладным областям: старайтесь решать логические задачи, пробуйте оптимизировать разные проверки, которые вам приходится писать в коде. Далее, стоит обратить внимание на логику первого порядка: она может пригодиться в тестировании программ.
При этом решение, которое первым пришло вам в голову, не всегда самое правильное и красивое. Часто формальными преобразованиями можно сократить объем кода и сделать его более читаемым. А кроме того, некоторые логические трюки позволяют сделать само решение короче, быстрее и эффективнее.
Ресурсы:
- На Codeforces в разделе «Архив» стоит потренироваться на задачах как минимум класса С — многие из них содержат подводные камни;
- Проект The KeY to Software Correctness подойдет тем, кому интересно, как можно автоматически доказывать правильность работы кода. Он автоматизирует проверку кода на Java;
- Автоматический доказатель теорем z3, написанный Microsoft, для тех, кто пользуется другими языками. Краткая инструкция по его использованию находится на ресурсе rise4fun.
Комбинаторика
Комбинаторика изучает разные дискретные множества и отношения их элементов. Наиболее часто встречаемая программистами комбинаторная задача — вывести количество элементов, которые необходимо перебрать, чтобы получить решение в зависимости от некоторых параметров. Таким образом вы можете вывести асимптотическую сложность алгоритма.
Комбинаторные задачи формулируются в виде задачи подсчета количества элементов некоторого (в математике используют термин мощность) множества. Чтобы решать такие задачи, полезно иметь базовые знания в теории множеств из разряда свойств операций над множествами. Тогда задача сводится к выражению искомого множества через множества, мощности которых вычисляются по известным правилам. Для подсчета количества элементов применяются правила умножения или сложения, числа сочетаний или размещений. Хотя есть и более сложные задачи, лучше начинать с простого.
Ресурсы:
- С основами можно ознакомиться на сайте Mathprofi, который посвящен прозрачному и популярному описанию математики;
- Если вы владеете английским, можете посмотреть более продвинутую книгу An Introduction to Combinatorics and Graph Theory;
- Задачи по комбинаторике можно взять из задачника «Дискретная математика», в конце есть ответы.
Теория вероятностей
Иногда на собеседовании интервьюер, дабы проверить насколько крут кандидат, задает такую задачу: «Вот у нас есть отрезок, который начинается с числа А и заканчивается числом Б. Мы кидаем на него две точки случайным образом. Какая будет средняя длина наибольшего отрезка?» или же «Пусть у нас есть треугольник, на вершине которого сидит муха. Пусть она перелетает с вершины на вершину за 3 секунды и отдыхает на каждой вершине по секунде, каждый раз случайно выбирая себе путь. Через какое время она в среднем вернется в начальную точку?».
Это задачи по теории вероятностей. В программировании часто приходится применять вероятностный подход, для того чтобы оценить среднюю скорость работы алгоритма или же подогнать параметры вашего решения задачи под те запросы, которые чаще всего встречаются на практике.
Теория вероятности делится на две части: дискретную и непрерывную. Хотя в теории дискретная — это подкласс непрерывной, методы решения задач несколько различаются. Опять же лучше начинать с простого — дискретная теория вероятности часто сводится к комбинаторным задачам. И теоретическая часть у дискретной формулируется проще.
Непрерывная теория вероятности для полного понимания требует знания элементарных основ мат. анализа, в частности понятия интеграла, хотя многие задачи требуют лишь умения считать площади простых фигур. Именно непрерывная теория вероятности является фундаментом для математической статистики и машинного обучения. Поэтому, если хотите работать в этой области, стоит начать с изучения книги Ричарда Хэнсена Probability Theory and Statistics или Probability Theory with Simulations.
Ресурсы:
-
— сайт, на котором доступно и просто изложена высшая математика. На нём есть множество разделов с теорией, таблицами и задачами, в том числе и по теории вероятностей
- Книга Чарльза М. Гринстеда и Лори Снелла Introduction to Probability.
Теория графов
Слышали ли вы задачу о мостах Кенигсберга?
«Можно ли пройти по всем семи мостам города Кенигсберга, не проходя по каждому из них дважды?». Нам известно, что ответ на эту задачу — нет. Решить подобные задачи помогает теория графов.
Графы — это очень удобные формализованные представления нелинейных структур, которые довольно часто встречается в прикладных задачах. В отличие от простых линейных структур, таких как массивы или списки, работа с графами более сложна.
Изучите классические результаты и алгоритмы из теории графов, потому как некоторые задачи на графах являются NP-полными, и для них доказано существование более эффективного решения.
Ресурсы:
- Познакомиться с основными понятиями можно в краткой методичке «Введение в теорию графов»;
- По части алгоритмов можно заглянуть на сайт e-maxx и наш;
- Практиковаться можно на задачах с Codeforces, там есть задачи на графах.
Теория чисел и криптография
Задумывались ли вы, почему к простым числам такой большой интерес? Почему работает шифрование RSA? Чем отличается http от https и что такое сертификат безопасности?
Все эти вопросы изучает криптография. Сразу скажем, что эта наука достаточно сложная и не интуитивная — бывает непонтяно, как реализовать тот или иной алгоритм совершенно безошибочно. Тем не менее алгоритмы в криптографии не могут быть «чуть-чуть нерабочими». Малейшая ошибка может привести к компрометации всей криптографической системы.
Дискретная оптимизация
Чтобы найти экстремум (максимум либо минимум) функции, надо взять ее производную и приравнять к нулю. Решение уравнения дает локальный экстремум. Но если вам нужно искать максимум не на каком-то промежутке, а только по целым числам, то вам уже нужно будет задумываться о том, какое из соседних целых чисел нужно выбрать. Когда задача многомерная, вариантов с целыми числами становится все больше, и выбирать приходится из все увеличивающегося количества. Но бывают случаи еще хуже — когда вовсе нет никакой непрерывной функции, от которой можно было бы взять производную. Или же когда количество вариантов очень велико (в том случае, когда сами варианты нужно вычислять).
Бывает, что в таких задачах нельзя найти точное решение за приемлемое время — его можно получить только полным перебором. Такова, например, задача Коммивояжера, или задача линейного программирования. Иногда можно отказаться от точного решения, и использовать некоторые приближения. Обо всем этом можно узнать в курсе Discrete Optimization на Coursera.
Источники
Небезызвестная серия курсов Introduction to Discrete Mathematics for Computer Science на Coursera по дискретной математике. Она довольно обширна и дает общее представление о всех нужных областях дискретной математики — логике, комбинаторике, теории вероятностей, теории графов, теории чисел и криптографии. Последний курс затрагивает проблему дискретной оптимизации.
Кроме того, для тех, кому не очень нравится формат курсов, будет полезной книга Discrete Mathematics. An Open Introduction. Книга довольно большая и подробная, поэтому можно сделать упор на основных понятиях и определениях.
Напоследок для тех, кого заинтересовала дискретная математика, приведем одну из наиболее подробных практико-ориентированных книг по дискретной математике. Довольно известная книга Кнута, Грехема и Паташника «Конкретная математика». Она написана в неформальном стиле, изложение разбавлено комментариями на полях. Книга очень полезна для развития умения решать разные задачи. Однако в ней много частных вещей, которые могут пригодится только в олимпиадном программировании.
Что дальше?
В целом, для того чтобы иметь достаточный математический фундамент для изучения большинства областей, достаточно первых двух курсов, изучаемых на математических специальностях. К дискретной математике добавляются некоторые разделы непрерывной: линейная алгебра, общая алгебра, математический анализ, аналитическая геометрия, обыкновенные дифференциальные уравнения, методы оптимизации. В зависимости от специфики решаемых задач, к ним могут добавиться и дифференциальная геометрия, если вы собираетесь заниматься компьютерной графикой, или же теоретическая механика и мат. физика, если вы собираетесь заниматься физическими движками.
️ Нужны ли программисту математика, английский язык, теория алгоритмов и паттерны проектирования?
Если хочешь подтянуть свои знания, загляни на наш курс «Алгоритмы и структуры данных», на котором ты:
- углубишься в решение практических задач;
- узнаешь все про сложные алгоритмы, сортировки, сжатие данных и многое другое.
Ты также будешь на связи с преподавателем и другими студентами.
В итоге будешь браться за сложные проекты и повысишь чек за свою работу
Иногда возникают нюансы. Вам приходится не только писать код, но и формализовать задачу, поставленную заказчиком на манер знаменитого «копать от забора и до обеда». Или ваш код, написанный «на коленке» за пару минут, работает слишком медленно. Или ваша программа выдает исключение, давно описанное на stackoverflow.com, но вы не умеете читать по-английски. Или возникает необходимость сделать рефакторинг кода, а для вас это звучит как «харакири». Как правило, начальству наплевать на то, как вы справитесь с этими проблемами, но справиться вы обязаны – «вы же программист»! За что же вам платят эдакие деньжищи?
Математика
Давным-давно, когда компьютеры были большими, а зарплаты программистов – маленькими, каждый программист был математиком. Помните главного героя «Понедельника начинается в субботу» братьев Стругацких, программиста Сашу Привалова?
— Кристобаль Хозевич, – сказал я. – Я ее все-таки решил. Вы были совершенно правы. Пространство заклинаний действительно можно свернуть по любым четырем переменным…
…Я отдал ему листки, он сел рядом со мною, и мы вместе разобрали задачу с начала и до конца и с наслаждением просмаковали два изящнейших преобразования, одно из которых подсказал мне он, а другое нашел я сам…
– Это мы опубликуем. Это никому не стыдно опубликовать.
На минуточку: обычный программист (пусть даже заведующий вычислительным центром) знал математику настолько хорошо, чтобы придумать в ней что-то новое, достойное публикации! То есть, фактически был профессиональным математиком. А как же еще? В те времена считалось, что программист должен уметь самостоятельно формализовать задачу, и только потом написать программу для ее решения.
В моем дипломе, полученном на факультете прикладной математики, гордо значится специальность – математик. Хотя все понимали, что нас учат на программистов. Нас научили использовать численные методы для решения сложных задач, которые нельзя решить аналитически – например, уравнения теплопроводности. Пригодилось ли это на практике? Пока нет, но еще может пригодиться. Если говорить о нужности для программиста, всю высшую математику можно разделить на три части:
- То, что вы будете использовать каждый день: дискретная математика. О ней мы сейчас поговорим подробнее.
- То, что вам нужно для решения возникающих математических задач: дифференциальное и интегральное исчисление, линейная алгебра, статистика. Эти разделы математики используются не каждый день, а у многих программистов – даже не каждый год, но знать их все равно очень полезно, а во многих компаниях – даже необходимо. Почему? Если вам хоть изредка приходится ставить математическую постановку задачи – вы просто не сможете этого сделать без соответствующей математической базы. Имея базу, вы сможете найти информацию о своей задаче в Интернете и использовать ее, а без базы вы эту информацию просто не поймете!
- Специфические знания. Нужны только для решения задач из определенной прикладной области. Если вы пишете программы именно в этой области – знание необходимых разделов математики будет огромным плюсом, а если не пишете – эти разделы вам не нужны.
Дискретная математика
Этот раздел математики изучает конечные структуры и содержит множество различных подразделов. Самый важный подраздел для программистов – это математическая логика, в которую входит обработка привычных каждому программисту логических операций вроде and, or, и not. Например, следующий оператор срабатывает, если верно a, но не b, или, наоборот, верно b, но не a:
Если вы изучали дискретную математику, то сразу поймете, что то же самое можно написать гораздо проще, используя операцию «исключающее ИЛИ», причем код будет не только намного короче, но и станет работать быстрее:
Математическая логика также помогает «инвертировать» логические выражения или упрощать их, что может потребоваться программисту буквально каждый день:
Еще один важнейший раздел дискретной математики для программистов – это теория графов. Если слово «граф» для вас ассоциируется с графом Монте-Кристо, а не со структурой данных, состоящей из вершин и соединяющих их ребер, вы рискуете не только провалиться на любом собеседовании, но еще и опозориться:
С чем должно ассоциироваться слово «граф» для программиста
Теория графов имеет огромное значение для программистов, поскольку большинство реально встречающихся на практике сложных задач описываются в терминах графов, и именно в этих терминах описываются алгоритмы решения таких задач. Очень популярным подвидом графа является дерево – это связный граф без циклов. Полное незнание теории графов не позволит вам найти нужные алгоритмы и тем более понять их.
Алгоритмы на графах очень часто применяются в реальной жизни. Моей первой реальной задачей была визуализация проектных связей изделия, и схема этих проектных связей, разумеется, была графом. Эта задача до сих пор актуальна и популярна.
Английский язык
Если вы не умеете быстро и без напряжения читать книги по программированию на английском языке – это значит, что вы просто не сможете постоянно следить за передним краем даже в своей области, не говоря уже о смежных. Конечно, российские издательства выпускают переводы книг по программированию, но к моменту их выхода на Западе уже успевает выйти новое издание. К тому же качество этих переводов настолько плохое, что я предпочитаю читать книги в оригинале, то есть на английском. Зачастую электронные версии самых свежих книг на английском языке появляются задолго до их печати, то есть я могу осваивать новые технологии одновременно со всем миром. К тому же, книг по программированию выходит так много, что далеко не каждая из них вообще будет переведена на русский!
Иногда вам придется и написать абзац-другой – например, задать вопрос на форуме или в отдел технической поддержки. Поскольку писать сочинения вроде «London is the capital of Great Britain» вам едва ли понадобится, это не должно вызвать особых трудностей, если вы уже хорошо умеете читать. Даже если вы сделаете пару ошибок, не страшно – лишь бы вас правильно поняли.
Многие фирмы работают на Запад, и там регулярно проводятся совещания на английском языке. Вот там вам уже понадобятся и разговорные навыки, и аудирование, и даже хорошее произношение. Такие фирмы прямо пишут в своих вакансиях что-то вроде «English – Upper-Intermediate». Высококлассному специалисту с большим опытом должно быть очень обидно упускать такие вакансии только из-за незнания языка.
С другой стороны, многие фирмы устраивают своим сотрудникам бесплатные курсы английского языка. Не упускайте эту возможность! Знание языка – это не излишество, а необходимость. Несмотря на то, что сам код можно писать, не зная даже английского алфавита.
Теория алгоритмов
Теория алгоритмов оценивает не точное количество ресурсов (которое на практике редко можно рассчитать), а характер его зависимости от размеров исходных данных, называемой сложностью. Например, цикл по всем элементам массива имеет вычислительную сложность O(N), где N – количество элементов массива. Это значит, что количество вычислительных операций пропорционально N. Общеизвестно, что сложность быстрой сортировки (в среднем) равна O(N * logN), а сложность бинарного поиска – O(log N). Поиск в хеш-таблице происходит еще быстрее: при правильно подобранных ключах и достаточном размере таблицы его сложность O(1), то есть вообще не зависит от размера входных данных! Этим и обусловлена широкая популярность хеш-таблиц, а также базирующихся на них словарей (dictionary) и множеств (set).
Использование неоптимального алгоритма – это мина замедленного действия, заложенная под ваш продукт. Предположим, что вы взяли на работу новичка, не знающего, что такое бинарный поиск. Как, по-вашему, он будет искать нужный элемент в массиве? Правильно, перебором всего массива – то есть операция, которая даже для миллионов элементов должна занимать несколько секунд, у него займет несколько дней, недель или даже месяцев. Именно поэтому тимлид должен отлично разбираться в теории алгоритмов, чтобы не брать на работу тех, кто не знает даже ее основ, а также помогать своей команде искать и придумывать лучшие алгоритмы.
Теоретически, вы можете писать код, и не зная об алгоритмах, но это будет код школьника-троечника. Причем страшнее всего то, что вы этого даже не почувствуете! Если вам не хватает английского языка – это всегда очевидно: вы не можете понять текст, постоянно лезете в словарь, или, что еще хуже, пользуетесь автоматическим переводчиком. Если вам не хватает математической подготовки – вы, конечно, не сможете обнаружить нехватку самостоятельно, но хотя бы почувствуете, что чего-то не понимаете. А вот люди, ничего не знающие об алгоритмах, не получают никаких «звоночков». Они успешно пишут код, который прекрасно проходит все тесты (поскольку размер данных в тестах обычно невелик), но у пользователя работать не будет.
Поэтому теория алгоритмов программисту действительно необходима, она применяется практически каждый день. При этом не обязательно уметь придумывать новые алгоритмы самостоятельно, но оценивать сложность своих алгоритмов и искать более эффективные алгоритмы для ваших задач точно нужно уметь.
Паттерны проектирования
Пожалуй, впервые важность проектирования была продемонстрирована в книге Ф.Брукса «Мифический человеко-месяц», вышедшей в далеком 1975-м, но она была ориентирована на менеджеров, а не на программистов. В 1994-м Э. Гамма, Р. Хелм, Р. Джонсон и Д. Влиссидес (называемые «бандой четырех», «Gang of Four») опубликовали книгу «Паттерны объектно-ориентированного проектирования», заложившую основы современного подхода к проектированию ПО. В книге описано множество типовых приемов (которые авторы назвали «паттернами»), помогающих улучшить проекты проекты программ, и это стало основным фактором ее популярности. Стало модно задавать вопросы по паттернам проектирования на собеседованиях, и юные соискатели начали заучивать их наизусть. (Я не рассматриваю SOLID, появившийся позднее, поскольку он слабо помогает проектировать – но заучить его для собеседований вам тоже придется).
К сожалению, навыки кодирования и проектирования очень сильно отличаются друг от друга, так что гениальный проектировщик вряд ли окажется еще и супер-кодировщиком (а когда такое все же случается, в мире появляется очередной Линус Торвальдс). Типичный кодировщик – это юноша, очень быстро набирающий код, то есть обдумывающий его еще быстрее. Типичный проектировщик – это человек с большим опытом, который может не набрать ни одной строки кода на протяжении нескольких дней или даже недель. Его задача – придумать наилучшую архитектуру продукта, вот он и думает. Разумеется, кодировщики, ничего не знающие о проектировании, считают проектировщика просто бездельником! Именно поэтому крупные компании не ставят проектировщика в подчинение руководителю команды кодировщиков, а придумывают для него особую должность «архитектора» (software architect).
Отличный пример широко распространенного, но неправильного понимания паттерна проектирования дает паттерн Bridge (Мост). Начнем с правильного примера использования этого паттерна на C# (код взят из документации Microsoft):
Интерфейс IDBConnection реализует Мост между клиентским приложением и базой данных (БД). Цель паттерна – полная изоляция клиента от деталей реализации каждой конкретной БД. Конкретным объектом, реализующим этот интерфейс, может быть любой экземпляр класса, унаследованного от DBConnection (код создает экземпляры SqlConnection, OdbcConnection и OleDbConnection), но клиент никогда не должен узнать, какого именно. Это позволяет разработчикам клиентского приложения сделать его по-настоящему мультиплатформным, а разработчикам Моста – постепенно добавлять поддержку новых БД, которая не заставит разработчиков клиента ничего менять. Важная парадигма: для клиентского приложения оба «берега», соединяемых Мостом, всегда представляют одно и то же (в нашем примере – БД), только на «его» берегу находится абстрактное представление о БД, а на «чужом» – ее конкретная реализация.
Теперь взгляните, какой «пример Моста» приведен на сайте Метанит. Там объект программиста обращается к объекту языка программирования через «мост». Непонятно, зачем изолировать объекты языков программирования от объектов программистов, но проблема даже не в этом. Во-первых, вы не можете заранее предсказать все, что вам потребуется от языков программирования – а это значит, что ваш «мост», скорее всего, через некоторое время придется изменять, тогда как одна из целей настоящего Моста заключается именно в защите клиента от изменений (как вы думаете, будет ли когда-нибудь изменен IDBConnection?). Во-вторых, вы не можете раз и навсегда определить даже вид соотношения между языками и программистами! Кто сказал, что каждый программист знает только один язык? Такие «мосты» только напрасно усложняют проект и не приносят никакой реальной пользы.
А вот пример того же паттерна с сайта Refactoring Guru. Здесь строится «мост» между пультами и управляемыми с их помощью устройствами. Этот пример уже имеет смысл, поскольку управлять с пульта проще, чем кнопками на устройстве (клиенту всегда проще работать с абстрактным интерфейсом Моста, избавляющим его от ненужных деталей реализации), но все-таки он неудачен. Не все команды управления телевизором и радио будут совпадать. Но самое главное – пульт не скрывает от клиента конкретную реализацию: клиент по-прежнему смотрит телевизор, а не пульт, и слушает радио, а не пульт. А мы уже знаем, что основная цель Моста – именно изоляция клиента от конкретной реализации.
Удачи вам в определении того, чего вам не хватает, и успешного устранения этих пробелов!