Чтобы научиться мыслить как программист, надо научиться мыслить как не программист
Это как бы ответ на статью lxsmkv «Задача о переправе». Наиболее запоминающаяся часть той статьи — это огромная (в сопоставлении с сложностью задачи) таблица, в которой выражена модель задачи.
Попробуем придумать что-то попроще.
1. Условие
У нас есть волк, коза и капуста. Их надо переправить на другой берег. Переправляет человек (перевозчик). Сложность в том, что волк съедает козу, а коза — капусту, если рядом нет человека. Кроме того, переправлять можно только по одному (в лодке только одно место для груза).
2. Идея решения
Теперь оцифруем задачу, то есть представим её в виде чисел и операций с ними. Мы должны описать распределения волка, козы и капусты по берегам (конфигурации) и действия с ними. Итак, у нас есть словесная формулировка условия задачи. Я хочу в модели совместить наглядность картинки с абстрактностью числа. То есть модель должна быть наглядна, чтобы быть понятной человеку, и абстрактной, чтобы быть понятной машине.
Я хочу её представить в таком виде, чтобы она была легко понятна мне и чтобы она была легко программируема.
Легче всего воспринимаются изображения (с одного взгляда). Легче всего программируются действия с числами. Таким образом, я хочу, чтобы моя модель совмещала в себе свойства как изображения, так и числа. Причём первично число, так как в перспективе предполагается программирование. То есть нам нужно не просто какое-то число, а такое, визуальный образ которого будет отражать задачу (и ход её решения). Изобразим условие задачи в виде картинки. Действие «съедает» изобразим стрелкой.
Кто-то увидит в этой картинке пищевую цепочку, кто-то ориентированный граф. Но всё это не имеет значения. Для нас важно, что некоторые пары запрещены. И какие именно пары, определяется их положением на картинке.
А раз так, то становится совершенно безразличным, кто из них кто и кто кого ест. Важно, как легко заметить, что запрещены соседние пары. Следовательно, рисунок можно сделать схематичным, изобразив и волка, и козу, и капусту одинаковыми значками.
Теперь нам надо показать распределение участников по берегам.
Нетрудно заметить, что информация, содержащаяся в правой части схемы, дублируется в левой части. Действительно, участники не исчезают из задачи и не появляются вновь. Каждый из них находится или на левом, или на правом берегу. Следовательно, достаточно одной правой части схемы.
В такой схеме содержится вся информация о любом распределении объектов, возникающем в ходе решения задачи. Что это за объект определяется местом (позицией), занимаемым им на схеме, а на каком он берегу определяется тем, занято место или пусто.
Теперь возьмёмся за числа. Все современные системы счисления позиционные, то есть функция цифры в числе определяется её положением в этом числе. То есть разрядом. И каждый разряд может иметь несколько значений.
Теперь с каждым разрядом свяжем объект задачи. А значений каждого разряда нам понадобится два. Пусть первый разряд – это капуста, второй – коза, а третий – волк. В каждом разряде нам понадобятся два значения: 1 – стартовый берег, 0 – финишный берег.
Получается, что для описания любой конфигуации нам нужно трёхзначное двоичное число. Например, 111 – стартовая конфигуация, 000 – финишная.
То же самое можно сказать и о кодировании хода числом. Тоже необходимо трёхзначное двоичное число, в котором разряд – это участник, а значение разряда кодирует действие (1 – перевезти, 0 – нет).
Остаётся описать переход от одной конфигурации к другой. Для этого надо найти математическую операцию о. Тогда этот переход можно описать так:
К1 о Д = К2
Что же это за операция? Рассмотрим один из разрядов конфигурации и тот же разряд действия. Если в этом разряде действия единица, то в разряде конфигурации единица должна смениться на нуль и наоборот. Если в разряде действия нуль, то разряд конфигурации остаётся без изменения.
Можно составить такую таблицу:
К | Д | К о Д |
---|---|---|
1 | 1 | 0 |
1 | 0 | 1 |
0 | 1 | 1 |
0 | 0 | 0 |
Очевидно, что это xor. То есть разряды конфигурации К инвертируются по маске Д.
В конечном итоге мы должны конфигурацию 111 инвертировать по маске 111:
111 xor 111 = 000
Ну вот, собственно темообразующая часть статьи на этом закончена – идея найдена. Остальное – следствия.
3. Правила
В предыдущей части сформулирована идея, на которой может быть построена цифровая модель задачи. Однако в задаче есть ещё и правила ходов. Сформулировав их с использованнием найденной идеи, мы и получим модель задачи и её решения.
Правила – это запреты, то есть они определяют, что нельзя делать. В нашем случае запреты можно преобразовать в разрешения. Действительно, для кодирования позиций (конфигуаций) и ходов мы можем использовать трёхзначные двоичные числа:
1. Исходный список конфигураций (ходов): 000, 100, 010, 001, 110, 101, 011, 111.
2. Исходный список масок (действий) такой же: 000, 100, 010, 001, 110, 101, 011, 111.
Запреты означают, что часть из этих чисел использовать нельзя. Следовательно, можно использовать только какие-то подмножества чисел из этих списков. Выясним, какие.
Для этого сначала надо понять роль перевозчика в нашей модели.
Пока он на берегу, всё спокойно, никто никого не ест. Но как только он уплывает на другой берег, волк съедает козу или коза капусту. Следовательно, допусимость пары проверяется в конце хода (или после хода) на том берегу, с которого делается ход.
Следовательно, нам в числах, кодирующих конфигуации и ходы, надо закодировать ещё и перевозчика. Для этого добавим ещё один разряд. Пусть единица в этом разряде кодирует перевозчика на стартовом берегу, а нуль – на финишном. Причём очевидно, что у числа, кодирующего ход, в старшем (четвёртом) разряде всегда 1. То есть не бывает ходов без участия перевозчика. Тогда исходые списки чисел станут такими:
1. Исходный список конфигураций: 1000, 1100, 1010, 1001, 1110, 1101, 1011, 1111, 0000, 0100, 0010, 0001, 0110, 0101, 0011, 0111.
2. Исходный список масок: 1000, 1100, 1010, 1001, 1110, 1101, 1011, 1111.
Теперь используем этот же принцип для кодирования разрешённости/запрещённости хода или конфигурации (позиции). Добавим ещё один разряд, пятый – 1 ход или конфигуация разрешены, 0 – запрещены.
Сформируем теперь списки разрешённых ходов.
Инвертировать можно только или один разряд, или ни одного (перевозить можно только одного пассажира или плыть порожняком). Следовательно, маски могут быть только 1000, 1100, 1010, 1001 (это список разрешённых ходов).
Сформируем теперь списки разрешённых позиций.
Запрещена исходная конфигурация 1111 (первый ход не может быть порожняком).
Запрещаются единицы или нули в соседних разрядах (на берегу не могут быть пары волк–коза и коза–капуста).
То есть запрещаются конфигурации 1111, 1110, 1011, 1001, 1100. Следовательно, разрешаются 1000, 1101 и 1010.
После нечётного хода проверяется конфигурация единиц, а после чётного — нулей (единицы – это объекты на этом берегу, нули – на том; нечётный ход – переезд с этого берега на тот, чётный – с того на этот).
Если мы хотим, чтобы игра заканчивалась и быстро, то надо ограничить количество ходов. Поэтому запретим повторение позиций.
Это правило требует, чтобы позиция, из которой делается очередной ход, становилась запрещённой. То есть одна и та же позиция может быть разрешена, а может быть и запрещена. Следовательно, надо кодировать запрещённость/разрешённость позиции. Снова вводим дополнительный разряд – пятый: позиция разрешена – 1, позиция запрещена – 0.
Очевидно, что код хода в этом разряде должен всегда содержать 0, потому что разрешённая позиция не запрещается автоматически, а только если в результате хода получается тоже допустимая позиция.
Код исходной позиции сначала, естественно, содержит в пятом разряде единицу – она, раз мы в ней находимся, разрешена.
Окончательно получается список допустимых позиций 11000, 11101, 11010 и список допустимых ходов 01000, 01100, 01010, 01001.
4. Вычисление ходов.
Теперь попробуем представить себе, что должна делать функция, вычисляющая ход (вычислХод).
Аргументы этой функции:
исхПоз – исходная позиция,
списЗапрещПоз – список запрещённых позиций,
списДопустХод – список допустимых ходов.
Вернуть функция должна резПоз – рузультирующую позицию.
Итак, функция вычислХод должна:
для каждого элемента Х списка допустимых ходов списДопустХод вычислить резПоз = исхПоз xor Х для каждой позиции П из списЗапрещПоз если резПоз and П ≠ 0 добавить резПоз xor 10000 к списЗапрещПоз вернуть резПоз
5. Конец
Ну вот, осталось спроектировать главную функцию, которая будет вызывать функцию вычислХод с нужными аргументами и складывать полученные результаты в какую-нибудь удобную структуру данных, из которой в конце концов можно будет вывести результат на экран в удобном для восприятия человеком виде. Ну и собственно написать код.
На этом месте можно начинать мыслить, как программист. Желающие могут заняться этим в качестве домашнего задания.
- модель решения задачи
- занимательные задачи
- математическое моделирование
- мышление
- Проектирование и рефакторинг
- Разработка игр
- Учебный процесс в IT
Как научиться мыслить как программист — отвечают эксперты
Наш подписчик прислал вопрос в редакцию Tproger: «Как научиться мыслить как программист?» Предоставляем вам ответы экспертов.
Нам в редакцию Tproger пришел вопрос от подписчика, которым мы хотим поделиться с вами:
«Как научиться мыслить как программист?»
Мы обратились за разъяснениями к нашим экспертам, а полученные ответы предоставляем вашему вниманию.
Как научиться мыслить как программист?
Михаил Субботин
преподаватель израильской высшей школы IT и безопасности HackerU
Главная задача программиста — понимать, как работает мир, разбивать сложные явления на простые инструкции, а потом собирать их воедино. Программист должен уметь правильно задавать вопросы и находить на них ответы. Поэтому я не вижу разницы между фразами «мыслить как программист» и просто «мыслить». Но если вы все же хотите мыслить «как программист», то начните читать, рассуждать, спорить и находить ответы на вопросы, которые интересуют именно вас. На мой взгляд, программист — одна из немногих профессий, где приходится часто и много размышлять. Так что дерзайте.
Рейтинг полезности ответа:
Ильназ Гильязов
эксперт курса «Профессия веб-разработчик» университета digital-профессий Нетология
На самом деле, вопрос не такой тривиальный, как кажется. И правильно бы его было сформулировать немного по-другому, как научиться понимать и решать поставленные задачи, т.к. языки программирования, подходы к проектированию и все остальное меняется достаточно быстрыми темпами, но несколько умений остаются вне времени.
Исходя из опыта, могу выделить среди них следующие:
- Умение сформулировать/понять задачу.
- Умение упрощать.
- Умение декомпозировать, выделять главное, и выстраивать логическую цепочку.
- Умение выделять граничные значения и исключительные ситуации.
Спойлер: любой навык и любое умение нарабатывается только практикой.
Умение сформулировать/понять задачу
Фактически нас этому учат с детского сада – сначала выражать и формулировать свои желания, затем ставят все более и более абстрактные задачи, проверяя нашу способность понять их. И все здорово ровно до тех пор, пока вы общаетесь в одном информационном поле (школа, студенты одной специализации, специалисты одной предметной области). Проблемы начинаются тогда, когда вы взаимодействуете со специалистами из другой предметной области – медицина, страхование и т.д. Они говорят на своём языке, к которому привыкли, и вам придётся либо научится понимать, либо искать переводчика.
Как с этим работать: учитесь общаться, учитесь излагать свои мысли, учитесь понимать других людей. В современном мире даже программистам-интровертам этим приходится заниматься. Начните с того, что хотя бы раз в неделю посещайте мероприятия по тематикам, с которым вы никак не связаны, но не просто ходите, а активно задавайте вопросы и пытайтесь понять ключевые идеи и взаимосвязи (это перекликается со следующими двумя пунктами). Можете просто общаться с новыми людьми из других сфер жизни.
Учитесь простыми словами объяснять то, чем вы занимаетесь и над чем работаете, а также понимать, что вам объясняют другие. Всегда проверяйте то, насколько поняли вас, и насколько поняли вы: уточняйте у собеседников, правильно ли вы их поняли, и правильно ли поняли они вас.
Умение упрощать
Из всех компонентов этот, наверное, самый близкий к коду. Упрощайте – используйте техники и подходы, которые позволят вам больше думать о бизнес-задаче, чем о том коде, который вы пишете:
- Early Exit – проверяйте условия в начале метода/функции и выходите, если вас что-то не устраивает.
- Разделяйте объекты на те, что содержат только данные и не содержат логики, и объекты, содержащие логику.
- Разделяйте логику по слоям (неважно, какой паттерн вы выберите).
- Механизм исключений – выбрасывайте исключения, если текущий слой не имеет возможности разобраться с исключительной ситуаций.
- Декларативное программирование (аннотации, декораторы и т.д., в зависимости от языка программирования, но тут важно знать меру).
- Любые другие, которые приведут к сокращению сложности восприятия кода (включая использование готовых библиотек, фреймворков и решений).
Умение декомпозировать, выделять главное и выстраивать логическую цепочку
Одно из ключевых умений, позволяет правильно расставлять приоритеты и проектировать системы. А также, что немаловажно, общаться с заказчиками и руководителями, которым важно все. Суть заключается в следующем – вы должны научиться из любой системы выделять ключевые компоненты, без которых эта система не имеет смысла, и определять их свойства и поведение.
Как с этим работать: опять же, только практика – выбирайте любую бизнес-систему и пытайтесь оставить в ней минимальное количество объектов и их взаимосвязей (если говорить терминами стартап-сообщества – MVP), позволяющих сохранить эту систему в работающем состоянии. Опишите весь процесс по шагам, посмотрите, какие объекты и как взаимодействуют.
Попробуйте понять, почему это взаимодействие устроено именно так. Например, почему процесс заказа или покупки устроен именно так, а не иначе. Можно ли организовать его по-другому? И почему в новых проектах,это взаимодействие иногда строится по-другому (они об этом любят писать в различных источниках).
Найдите changelog’и либо описание истории проекта, посмотрите как менялся проект со временем, с чего начинался, что в него добавлялось и почему.
Умение выделять граничные значения и исключительные ситуации
Говорят, что когда-то очень давно программисты не должны были тестировать свой код. Сейчас ситуация в корне изменилась и в большинстве вакансий присутствует как минимум требование уметь писать авто-тесты.
На самом деле, программисту очень важно изучить теорию тестирования. Потому что именно из этого у вас сформируется понимание того, как обрабатывать различные входные данные, какие условия строить, где вас могут подстерегать ошибки и умеете ли вы вообще с этими ошибками работать.
Вместо заключения
Как уже было сказано в спойлере, ключевое – это практика. Не бойтесь ходить на встречи с реальными заказчиками, не бойтесь задавать вопросы, не бойтесь браться за проекты из другой предметной области (но при этом трезво оценивайте свои силы – если вы ничего не смыслите в медицине, не стоит браться за написание ПО для какого-то аппарата жизнеобеспечения), не бойтесь пробовать новые подходы и методы (когда-то многие вещи из тех, что используются сейчас, высмеивались и считались в корне неверными).
Только через практику вы получите реальный опыт и на нём поймёте, что правильно, а что нет. Яркий тому пример: нас долгие годы учили тому, что нужно выполнить гигантский объём проектирования, нарисовать кучу схем и диаграмм, прежде чем написать хотя бы одну строчку кода. Сейчас всех учат тому, что нужно быстрее планировать и быстрее делать.
Из книжек я бы не рекомендовал те книжки, которые воспитывают вас в духе, мол нужно делать именно так и никак иначе, потому что в первую очередь важно работающее решение, покрытое тестами, а рефакторинг можно сделать потом.
Научиться мыслить как программист
Вокруг профессии программиста ходит очень много мифов и заблуждений, не имеющих ничего общего с реальностью. В понимании большинства программист — это обязательно «технарь», математик и вообще компьютерный гений. А людям с гуманитарным складом ума в программировании якобы делать нечего. Разумеется, это в корне неверно. Математический склад ума вовсе не гарантирует успеха в изучении программирования, а гуманитарное образование — никакой не приговор, если вы решили стать программистом. Куда важнее — развить в себе мышление программиста и осознать, что все проще, чем кажется.
Сейчас вы узнаете, как работает мозг программиста и почему ваш склад ума и образование на самом деле не так важны.
Образ мышления программиста и его особенности
Программист не должен уметь решать в уме сложные математические уравнения, держать в памяти «многоэтажные» формулы и тому подобное. Это в принципе не относится к его сфере деятельности. Мозг программиста настроен на совершенно другие задачи:
1. Умение правильно и точно формулировать/понимать задачу
Так или иначе программист будет коммуницировать с коллегами, клиентами или подконтрольными ему исполнителями, если, к примеру, станет тимлидом. И от того, насколько точно он будет формулировать и понимать текущие задачи, напрямую зависит эффективность его работы.
2. Умение упрощать сложное и не усложнять то, что не нужно
Хороший программист — ленивый программист. Грубо, но честно. Сложный путь в программировании — далеко не всегда лучший. Обычно наоборот. И умение упростить решение той или иной задачи без ущерба качеству — это круто.
3. Умение расставлять приоритеты и выстраивать логические цепочки
Развитие мышления программиста невозможно без совершенствования его логики и умения вычленять наиболее приоритетные задачи. Да и в целом стоит сказать, что в программировании логика куда важнее математики.
Советы, как развить в себе мышление программиста
Когда вы начнете изучать программирование, важно держать в голове несколько простых правил. Они помогут вам повысить эффективность обучения, а в последствии и работы.
- Всегда помните о целях. Желание выйти за рамки обычно только распыляет внимание и ухудшает итоговый результат.
- Выбирайте правильные технологии. Не нужно экспериментировать и использовать решения, не подходящие для вашей задачи.
- Не изобретайте велосипед. Поначалу лучше воздержаться от экспериментов и двигаться по проторенной дорожке. Когда наберетесь достаточно опыта, можно будет искать собственные пути реализации поставленных задач, но в начале карьеры это лишнее.
- Будьте последовательны. Мыслите упорядоченно и не «прыгайте» между разными задачами и проектами.
- Автоматизируйте то, что можно автоматизировать. Современные языки программирования позволяют автоматизировать многие рутинные процессы и сконцентрироваться на более важных этапах разработки. Не тратьте время на то, что за вас могут выполнить алгоритмы.
Также не стоит стремиться к совершенству, потому как «совершенное — враг хорошего». В таком стремлении вы будете усложнять систему необязательным кодом, наверняка столкнетесь с непредвиденными багами и, вероятно, не уложитесь в сроки разработки.
Можно ли стать программистом, если вы пока вообще ничего в этом не смыслите?
Никто не рождается программистом. И врожденные математические способности и склонность к точным наукам здесь тоже ни при чем. Главное — желание изучить что-то новое, готовность корректировать свой образ мышления и пробовать собственные силы в новой профессии.
Курс «Python»
Хочешь получить обучение от Дмитрия Жарикова?
На курсе вы научитесь:
- Основам основ: «Алгоритмы + структуры данных = программы»
- Алгоритмам работы с языком программирования Python
- Разработке приложений с графическим интерфейсом
- Добавлению их к себе в портфолио!
Как мыслить как программист
Решение проблем – это метанавык. Как мы с ними справляемся? Довольно случайно. Если у вас нет системы, вы, скорее всего, «решаете» проблемы таким образом:
- Пробуете решение.
- Если не получается, пробуете другое.
- Повторяете шаг второй, пока не повезет.
Так делать не стоит. Это огромная потеря времени. Вам нужно а) найти закономерность и б) практиковаться.
«Почти все работодатели в первую очередь смотрят на то, как сотрудники решают проблемы. Именно этот навык для них важнее, чем знание языков программирования, отладка и проектирование системы. Вычислительное мышление и возможность справляться с большими и сложными проблемами ценятся так же, как и базовые технические навыки, необходимые для работы», – HackerRank (Отчет о навыках разработчиков 2018).
Найдите закономерность
«Новички-программисты часто фокусируются на изучении синтаксиса, а не на решении проблем. И это самая большая ошибка», – В. Антон Спрол.
Что же нужно делать, когда вы сталкиваетесь с новой проблемой?
1. Поймите ее
Вы должны знать, что от вас требуется. Задачи кажутся сложными, потому что вы не понимаете их. Как узнать, поняли вы проблему или нет? Попробуйте объяснить ее простым языком.
Так происходит довольно часто: вы застреваете на задаче, начинаете объяснять ее, и внезапно замечаете логические недочеты? Большинству программистов знакомо это чувство. Запишите на бумагу вашу проблему или расскажите о ней кому-то.
2. Планируйте
Не приступайте сразу к решению проблемы без плана. Вам ничего не поможет, если вы не распишете точные шаги. Дайте мозгу время проанализировать проблему и обработать информацию. Чтобы составить хороший план, ответьте на этот вопрос:
«Допустим, на входе есть X. Какие шаги нужны для того, чтоб на выходе получить Y?»
У программистов есть отличный инструмент для этого… Комментарии!
3. Разбивайте проблему на маленькие подпроблемы
Это самый важный шаг. Не пытайтесь сразу решить одну большую задачу. Вместо этого разбейте ее на небольшие задачки и постепенно решайте их. Начните с самой простой – то есть с той, ответ на которую вы уже знаете. Как только вы справитесь со всеми, соедините точки. Так вы сможете решить первоначальную задачу.
«Я всегда советую программистам-новичкам упрощать проблему. Давайте представим, что вам нужно написать программу, которая читает десять чисел и определяет, какое будет из них будет третьим по величине. Новичкам это задание может показаться очень сложным, несмотря на то, что для его решения необходимо знание базового синтаксиса. Если вы застряли, попробуйте упростить проблему. Вместо третьего по величине числа попробуйте найти просто самое большое из всех. Все еще сложно? Найдите самое большое число из трех заданных. Или из двух. Упрощайте проблему до тех пор, пока не поймете, как ее решить. Затем постепенно усложняйте задачу и переписывайте решение», – В. Антон Спрол.
4. Застряли?
Вы сейчас, наверное, думаете: «Ну да, это, конечно, здорово, но что если я застрял и не могу решить даже маленькую проблему?»
Не переживайте. Это происходит со всеми. Разница заключается в том, что программисты не злятся, когда сталкиваются с этим. Им наоборот интересно найти решение. Вот три способа, как с это сделать:
- Отлаживайте. Пересмотрите свое решение. Попробуйте определить, где вы ошиблись.
- Оцените заново. Посмотрите на проблему с другой стороны. Есть ли что-то, что можно представить более простым способом?
«Иногда мы теряемся в деталях и упускаем общие принципы, которые помогли бы решить задачу быстрее. Классический пример – это сумма последовательных чисел – 1 + 2 + 3 + … + n. Карл Фридрих Гаусс представил это в виде выражения n(n+1)/2, и ему не пришлось выполнять ненужные вычисления».
Есть еще один вариант – полностью переписать код. Удалить все и начать заново.
- Исследуйте. Неважно, какую задачу вы пытаетесь решить сейчас. Кто-то уже сделал это до вас. Вам нужно просто поискать. Делайте это, даже если решили проблему.
Практикуйтесь
Не думайте, что через неделю вы станете мастером в этой области. Если вы хотите хорошо решать задачи, делайте это регулярно. Практикуйтесь. Шахматы, математические задачи, судоку, го, монополия, видеоигры, криптокотики – все это поможет вам оттачивать навык решения проблем. Известные люди тоже так делают. Например, Питер Тиль играет в шахматы, а Илон Маск – в видеоигры.
«Илон Маск, Рид Хоффман, Марк Цукерберг и другие утверждают, что игры заложили основу для их успеха», – Мэри Микер.
Неужели это значит, что вы должны просто играть в видеоигры? Не совсем. Конечно, нужно еще учиться. Найдите что-то, где вы могли бы практиковать свои умения.
Материалы по теме: