25.10.2013

Гостиница Гильберта. Ответ

Эту задачу предложил немецкий математик Давид Гильберт где то в третьем десятилетии 20 века. Давид был одним из величайших умов своего времени. Среди большого числа его учеников, которые в последствии стали видными учеными, был Джон Фон Нейман. Он так же консультировал Эйнштейна при разработке тензорного анализа — фундамента теории относительности.

Один гость

Напомню вам условие задачи для одного гостя из предыдущей статьи:

Представьте себе гостиницу с бесконечным числом комнат. Комнаты пронумерованы натуральными числами, от 1 до ∞. В один прекрасный день в нашу гостиницу вошел человек и попросил снять комнату. К сожалению, для нашего гостя не нашлось комнаты, так как именно в этот день отель был полностью заполнен бесконечным числом гостей. Выгнать гостя? Или все же есть возможность предоставить ему свободную комнату не выселяя никого из постояльцев?

Несмотря на то, что задача явно говорит что все номера заняты, мы все же можем выделить сколько угодно свободных комнат. Давайте просто переселим человека из первой комнату во вторую, человека из второй комнаты в третью и так далее. То есть, каждого гостя из комнаты с номером n переселим в комнату с номером n+1, n→n+1. В результате этого у нас освобождается комната с номером один, и мы с радостью можем поселить нашего нового гостя.

Бесконечное число гостей

Напомню условие:

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

Задача стала интереснее. Может ли бесконечность вместить еще одну бесконечность? Для решения предыдущей задачи мы переселили каждого гостя на один номер вперед. Этот подход можно применить для любого конечного числа постояльцев. Если n номер комнаты постояльца, а m число прибывших гостей, тогда каждого постояльца надо переселить в номер n+m, чтобы освободить m номеров для m гостей. Но что если число гостей бесконечно, то есть m=∞?. Чему равно n+∞? Давайте попросим «Вольфрам Альфа» дать нам ответ: n+∞=∞. То есть, надо переселить каждого постояльца на номеров. Нет, это нам не подходит. Но задача имеет решение, давайте взглянем на определение четного числа:

Четное число — целое число, которое делится без остатка на 2.

Что если мы возьмем номер постояльца и умножим его на два? В результате мы получим четное число, так как оно будет делится на два. Следовательно если мы переселим каждого гостя из номера n в номер 2×n, n→2×n, мы получим бесконечное число нечетных свободных комнат, и мы сможем поселить бесконечное число гостей. Только не спрашивайте сколько времени займет переселения гостя из комнаты номер 8140406085191601 в комнату номер 16280812170383202.

***

Этот парадокс хорош тем, что он отлично показывает странные, но вполне логичные свойства бесконечности в простых и понятных сущностях. Посмотрите так же этот интересный ролик:

22.10.2013

Гостиница Гильберта

Представьте себе гостиницу с бесконечным числом комнат. Комнаты пронумерованы натуральными числами, от 1 до ∞. В один прекрасный день в нашу гостиницу вошел человек и попросил снять комнату. К сожалению, для нашего гостя не нашлось комнаты, так как именно в этот день отель был полностью заполнен бесконечным числом гостей. Выгнать гостя? Или все же есть возможность предоставить ему свободную комнату не выселяя никого из постояльцев?

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

Ответ.

09.10.2013

Учимся правильно

Итак, вам понравился мой пост о Курсере и вы решили попробовать. Замечательно, но я хочу еще больше улучшить вашу жизнь и написал несколько советов о том, как избежать проблемы с которыми я столкнулся занимаясь на первых курсах. На данный момент я закончил 5 курсов, и еще 3 в процессе. Так что я уже опытный в этом деле и ко мне можно прислушаться. Я хочу поставить этой статье амбициозную цель — помочь вам научится учится. И если ваша цель получить знания и навыки, а не пройти больше курсов, тогда эти советы помогут вам более эффективно тратить свое время и сделать так чтобы оно приносило максимальную пользу.

Массовые онлайн образовательные курсы (МООК) имеют очень много общего с обычными курсами, к которым мы привыкли со школы или университета, поэтому опыт образования в этих учебных заведениях хорошо ложится на реалии МООК. Но есть так же и существенное отличие — МООК предоставляет значительную свободу в организации времени занятий и процесса обучения. Писать или не писать лекции, выполнять или не выполнять дополнительные практические упражнения, заниматься утром или вечером, вся тягота решения этих вопросов ложится на ваши плечи, а не на плечи учебного заведения и учителя. Эта свобода требует от вас большой дисциплины и ответственности, и она может вас привести к тому, что вроде курс закончен, а знания растерялись по дороге.

Выбор курса

Курсера похожа на огромный магазин с очень качественными товарами по очень низким ценам. Какой курс выбрать: «Введение в систематический дизайн программ», или «Универсальная игровая программа», или «Введение в математическое мышление», или «Введение в историю человечества»? Очень часто потребитель внутри нас заменяет эти ИЛИ на И, и тут вы попадаете в ловушку. Курсы вроде бы бесплатные, но на самом деле вы платите одним из самых ценных ресурсов — временем. Не сомневайтесь, образование одна из самых лучших инвестиций в жизни, но если вы нагрузите вашу тележку огромным количеством курсов то у вас не хватит времени на все и, скорее всего, вы потратите кучу нервов в этой борьбе и ничего не приобретете.

Почему так? Потому что курсы на Курсере соответствуют во многом их университетским аналогам. Они не так просты, как может показаться, и требуют должного внимания к себе. Но не все курсы одинаково сложны. Давайте попробуем их разделить на три категории:

  • Cложные. Это курс в предметной области которого вы не разбираетесь. К примеру если вы не разбираетесь в математике и берете курс «Исчисления» тогда приготовьтесь потратить на него очень много времени.
  • Средние. Это курсы в предметной области которых вы разбираетесь. К примеру, я программист и мне довольно легко даются новые знания в этой области.
  • Простые. Не могу дать характеристику поэтому приведу пример. Курс «Введение в историю человечества» можно назвать таким. Он не предполагает выполнения каких-либо домашних заданий, тема проста и понятна каждому человеку. В процессе обучения просто слушаешь интересные рассказы. Такой курс очень приятно и легко слушать, в нем не будет неожиданных сложностей, в общем это как хорошее кино посмотреть.

А теперь давайте попробуем посчитать. В аннотации к каждому курсу обычно пишется сколько времени в неделю студент должен будет уделять этому курсу по мнению автора. Так же учитывайте то, что это время для носителя английского языка. Так что к нему еще прибавляется время которое вам понадобится на перевод и понимание сказанного. Для простых курсов это время обычно указано 2-4 часа в неделю и совпадает с тем, что в реальности отводится на них. Для средних курсов это время 6-10 часов в неделю, при этом пару часов можно добавить на непредвиденные сложности. Для сложных курсов это время такое же, 6-10 часов в неделю, но при этом к нему надо прибавить как минимум 50% этого времени, так как сложности у вас точно возникнут, особенно если курс не вводный.

Если вы работаете полный рабочий день то сможете безболезненно уделять около 2 часов в будни и около 6 часов по выходным. Получается что можно взять сложный и средний курс, или два средних и простой. Используя эту классификацию можете произвести расчет под ваши условия. Конечно, это все примерно, в моей практике было так что сложный курс в аннотации которого написано 6-10 часов занимал 22 часа в некоторые недели, так что учитывайте и такие непредсказуемые обстоятельства и оставляйте всегда свободное время.

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

Старайтесь задания и лекции по более сложным курсам делать в начале недели, а более простые курсы оставлять на конец недели. Это даст вам дополнительный запас времени на непредвиденные сложности.

Мотивация

Cамое главное при выборе курса и начале обучения — мотивировать себя закончить его. В начале курса вы рветесь в бой, готовы свернуть горы, смотрите лекции и выполняете задания за одну бессонную ночь. Но в середине курса, а курс может идти 3 месяца, все это пропадает. Хочется покорять новые вершины, а покорение текущих целей оставить позади. И это очень плохо. Поэтому мотивируйте себя для того чтобы дойти до конца. Курсера предлагает нам сертификат по окончанию курса, поставьте себе цель во чтобы это не стало заполучить его. Придумайте для себя ему ценность, распечатайте уже полученные сертификаты и повесьте на стену, гордитесь ими и старайтесь собрать как можно больше, как фантики от жвачки в детстве (если кто этим занимался).

Если этого мало — мотивируйте себя с помощью денег. У Курсеры есть механизм сертификатов которые подтверждают то, что именно вы прошли этот курс. Этот сертификат существует в материальном виде и придет вам по почте. За эту услугу берется плата, приблизительно 50$, но это может дать вам хорошую мотивацию довести дело до конца.

Еще попробуйте вступить в группу учеников. Лучше в физическую, а не в онлайн группу. Смотрите лекции с друзьями. Окутайте этот процесс социальными связями. Одному гораздо проще забросить любое дело чем группе.

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

Обучение

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

А теперь перейдем к лекциям. Одна из главных проблем с которой я столкнулся при прохождении своего первого курса заключалась в том что я смотрел лекции один раз и потом, когда приступал к практическим заданиям, постоянно оказывался в такой ситуации что я не понимал какой либо детали. Сначала я долго сокрушался на сложность курса, на плохо составленные материалы, на шум за окном, на неверное расположение звезд и так далее. В конечном счете я возвращался к лекциям, пересматривал их, находил ответ на вопрос, решал эту проблему и вскоре находилось еще одно непонимание и процесс начинался сначала. Конечно уровень английского был ниже, я относился к занятиям проще чем надо было бы, но это все пустяки. Решением вопроса стала изобретенная мной двух-проходная система просмотра лекций. В первый раз я смотрю лекцию на нормальной скорости, стараясь уловить общую картину и представить как одна часть относится к другой. Я просто слушаю что мне стараются объяснить и не отвлекаюсь на детали. Во второй раз я смотрю лекцию ускоренной от полутора до двух раз, со включенными субтитрами, и составляю конспект лекции. Да-да, делаю именно то что бесило в школе и колледже. Если в процессе просмотра я встречаю незнакомое слово, то я останавливаю лекцию и перевожу его в переводчике. Со временем количество незнакомых слов не будет так раздражать, еще и язык подучите. Используя двух-проходную систему просмотр одной десятиминутной лекции занимает от 15 до 17 минут, так что учитывайте эти затраты при подсчете трат времени на курс.

А теперь про конспектирование. Его проблема в том что никто никогда не объяснял зачем оно нужно, а еще некоторые преподователи выдвигали совершенно непонятные требования в виде полного конспекта лекций в качестве пропуска к экзамену. Тут всплывает еще одна проблема классических лекций — необходимость слушать и писать одновременно, в итоге это все превращалось в кашу и теряло смысл. МООК не имеет данной проблемы потому что можно возвращаться к материалу сколько угодно. Ускорение просмотра видео убирает проблему того, что много раз одно и тоже скучно смотреть. Вообще ускорение это замечательный инструмент, в большинстве случаев при втором просмотре можно смотреть на скорости 2x и все понимать. Это зависит от лектора, но большинство лекторов специально говорят медленней чтобы люди, у которых проблема с английским, имели шанс понять о чем речь.

Конспект лучше составлять в цифровом виде, потому что по нему можно осуществлять поиск. Еще круче делать его в виде ментальных карт. Они представляют собой диаграмму связей начальным узлом которой является тема, а отростки от него являются какими-то понятиями которые относятся к этой теме. Такие карты позволяют нам делать меньше записей и лучше структурировать их. Еще в них можно использовать ссылки, изображения, прикреплять файлы. На многих курсах видео сопровождение к лекции в формате слайдов, и если мысль которую вы хотите законспектировать находится на экране то лучше сделать скриншот и прикрепить его к узлу диаграмы. Для дополнительного усвоения материала я перевожу данный слайд своими словами. Я использую программу «ИксМайнд», она бесплатная и полностью меня устраивает.

Конспект в виде ментальной карты

Так в чем же смысл конспекта? У него есть две цели. Первая и самый важная — конспектирование как процесс позволяет повысить запоминаемость материала во много раз, при условии что оно выполняется в спокойных условиях и конспектируется материал который уже был воспринят устно, то есть знакомый. Этот результат не научный, это мой опыт. Именно этот смысл теряется в традиционных лекциях-диктовках, когда пишешь и слушаешь то ни одно из этих дел не получается на должном уровне. Второй смысл в том, что удобней найти ответ на вопрос в своих лекциях, особенно если им придана структура с помощью ментальных карт, чем найти и посмотреть нужную лекцию. Самое интересное в том, что при двух-проходной системе просмотра лекций у меня эта потребность возникала всего пару раз. То есть материал запоминается отлично.

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

В большинстве случаев лекции разбиты на короткие кусочки по 5-10 минут. Такие лекции проще усваивать чем более длинные лекции. Но, если вы встречаете лекцию длинной 30 минут то можете разбить ее на отрезки по 10 минут и применять двух-проходную систему на каждом отрезке. То есть 10 минут посмотрели, отмотали и посмотрели ускоренно. Я вначале прибегал к этому приему, но потом как то 30 минутные лекции научился за один присест смотреть.

Очень часто прямо посреди лекции появляется тест, если ответ на него не очевиден и вообще вопрос не понятен значит вы что то упускаете, и лучше отмотать обратно и пересмотреть фрагмент до теста. Вообще тесты довольно хороший индикатор понимания материала, проходите их, думайте над неверными ответами, и находите понимание того, почему верный ответ действительно верный. Если вы не получаете 100% балов за тест есть смысл попробовать его еще раз до тех пор пока вы не достигнете мастерства.

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

  1. Понимание
    1. Теория
    2. Практика
  2. Мастерство

Понимание это основная часть обучения. Мы изучаем теоретический материал и применяем его на практике. Советы по этому шагу я озвучил выше. На шаге мастерства вы детализируете и структурируете свои знания путем объяснения их другому человеку. Этот шаг является ключем к полному пониманию вопроса. Вы можете заниматься каким-либо делом 5 лет и думать что вы эксперт в нем, но как только вы пытаетесь объяснить суть этого дела кому-то другому у вас получается такой рассказ, что вы ловите себя на мысли о том, что вы не понимаете то о чем говорите. Ваш собеседник будет задавать каверзные вопросы на которые у вас может не быть ответа. Старайтесь искать ответы, заполняя пробелы в своем знании, и этот процесс позволит вам отшлифовать ваши знания до совершенства. Форум и группы студентов это идеальное место для этого шага. Помогайте другим и ваше понимание будет намного глубже.

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

И напоследок поговорим о жуликах. О тех людях которые создают ощущение несправедливости в мире образования и которые разрушают его. За что я люблю МООК это то что в нем есть только одна награда — знания. То, ради чего и создавалось образование. Оно не обросло степенями и титулами которые в нашей стране, к сожалению, можно купить и наличие такого титула может приносить прибыль. Так что если вы что то не понимаете, а крайняя дата сдачи скоро, есть только один выход — понять.

02.10.2013
архивирован

Курс «Универсальная игровая программа» начался

Забыл вас предупредить о еще одном замечательном событии в области онлайн-образования. 30 сентября началась сессия курса «Универсальная игровая программа» от Стенфордского университета. В данный момент я уже занимаюсь по двум курсам: «Введение в математическое мышление» и «Введение в историю человечества». Но я просто не могу пропустить этот курс и пожертвую своим временем чтобы приобрести знания, которые возможно помогут построить интеллект основанный исключительно на числах. Я думаю этот курс доставит массу удовольствия любому программисту.

Перевод описания со страницы курса:

Универсальные игровые программы (УИП) это компьютерные системы, которые могут играть в стратегические игры основываясь только на формальном описании игровых правил, которое они получают в процессе работы. Другими словами они не знают правил игры до момента ее начала. В отличии от специализированных компьютерных игроков, таких как «Дип блю», УИП не могут полагаться на алгоритмы которые разработаны специально для отдельных игр, они должны разработать их сами. Успешность игровой программы зависит не только от интеллекта ее разработчиков, но и от нее самой.

13.09.2013

10000100110 10000011000 10000100010 10000011001 10000100001 10000100100


10000100110 10000011001 10000010111 10000100011 10000011000 10000100010
110101      10000010100 10000101000 10000010100 10000100010 10000010101
10000100110 10000010100 110111      110000      101100      10000010100
10000011000 10000011001 10000100010 110010      10000010100 10000010110
10000010100 10000010111 10000100011 10000011000 10000101000 10000010010
10000010100 10000011101 10000010100 110011      10000100111 10000100011
10000010100 10000011100 10000100010 10000010101 10000101100 10000011101
10000100111 10000010100 10000101100 10000100111 10000100011 10000010100
10000100110 10000011001 10000010111 10000100011 10000011000 10000100010
110101      10000010100 10000011000 10000011001 10000100010 110010
10000010100 10000100100 10000100101 10000100011 10000010111 10000100101
10000010101 10000100001 10000100001 10000011101 10000100110 10000100111
10000010101 10000010011 10000010100 10000100100 10000100011 10000011100
10000011000 10000100101 10000010101 10000010110 10000100000 110101
110100      10000010100 10000010110 10000010101 10000100110 10000010010
10000010100 10000011011 10000011001 10000100000 10000010101 110100
10000010100 10000101010 10000100011 10000100101 10000100011 10000101101
10000011101 10000101010 10000010100 10000100100 10000100101 10000100011
10000011001 10000011111 10000100111 10000100011 10000010110 10000010010
10000010100 10000000001 10000100011 10000100000 110010      10000101101
10000100011 10000011110 10000010100 10000100100 10000100101 10000100011
10000011000 10000101000 10000011111 10000100111 10000011101 10000010110
10000100010 10000100011 10000100110 10000100111 10000011101 10000010010
10000010100 10000011111 10000100101 10000010101 10000100110 10000011101
10000010110 10000100011 10000010111 10000100011 10000010100 10000011111
10000100011 10000011000 10000010101 10000010100 10000011101 10000010100
10000100100 10000100011 10000100001 10000011001 10000100010 110010
10000101101 10000011001 10000010100 10000100100 10000100101 10000100011
10000000001 10000100000 10000011001 10000100001 10000010011 10000010100
10000010110 10000010100 10000011111 10000010101 10000101100 10000011001
10000100110 10000100111 10000010110 10000011001 10000010100 10000100100
10000100011 10000011000 10000010101 10000100101 10000011111 10000010101
10000010100 10000100100 10000100101 10000011101 10000100001 10000011101
10000100111 10000011001 10000010100 110011      10000100111 10000101000
10000010100 10000100010 10000011001 10000000001 10000100011 10000100000
110010      10000101101 10000101000 110100      10000010100 10000011100 
10000010101 10000011000 10000010101 10000101100 10000101000 10000010010 
10000010100 10000011111 10000100000 110100      10000101100 10000010100 
10000100011 10000100111 10000010100 10000100010 10000011001 10000011001
10000010100 10000100100 10000100101 10000010101 10000010110 10000011000 
10000010101 10000010011
// raw binary here: http://pastebin.com/zG8vwpCb
// {{{{BIN -> UTF8_CHAR_CODE} -> UTF8_CHAR} -> CAESAR (KEY="ПРАВДА")} -> ILLUMINATION}
alphabet = ["А","Б","В","Г","Д","Е","Ё","Ж","З","И","Й","К","Л","М","Н","О","П","Р","С","Т","У","Ф","Х","Ц","Ч","Ш","Щ","Ъ","Ы","Ь","Э","Ю","Я","1","2","3","4","5","6","7","8","9","0",",",".", " "];
// glhf
10.09.2013

Рекурсия

Если вы не знакомы с рекурсивными функциями то вот определение из шести слов: это функция которая вызывает саму себя. Звучит просто, а на деле это мощно, сложно и опасно.

Когда я учился нас предостерегали использовать рекурсивные функции, и правильно: если не контролировать рекурсивную функцию она быстро взорвет ваш стек вызовов и приведет к падению программы. Чтобы сделать правильную рекурсивную функцию надо решить три вопроса:

  1. Выделить базовый случай.
  2. Выделить рекурсивный случай и изменение входных данных.
  3. Доказать что изменение данных в рекурсивных случаях в конечном счете приведет к базовому случаю.

Базовый случай это такое состояние входных данных при котором функция завершает свою работу.

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

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

Рассмотрим интересный пример — реализацию функции map. Это классическая функция, которая принимает массив данных d и применяет к каждому элементу функцию fn. В результате получается массив данных которые возвращает fn:

// @param {arrayof: x} d  Данные
// @param {x -> y}     fn Функция 
var map = function(d, fn){
  if(d.length==0){
    return [];
  }else{
    var first = d[0];
    var rest  = d.slice(1,d.length);
    return [fn(d[0])].concat(map(rest, fn));
  } 
};
// Проверим
var data = ["one", "two", "three"];
map(data, function(e){return "contains: " + e});

Разберем эту функцию в соответствии со структурой которую мы описали выше.

Базовый случай у нас это d.length==0, возвращается пустой массив.

В рекурсивной части происходят две интересные вещи:

  • Создается массив с первым элементов входного массива и к нему конкатенируется результат рекурсивного вызова функции map.
  • При рекурсивном вызове берется оставшаяся часть массива, без первого элемента, то есть количество данных уменьшается.

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

В этой функции рекурсивный вызов образует цикл натуральной рекурсии. Вот он показан наглядно:

Практика

Теперь вы. Попробуйте построить функцию filter которая принимает массив строк и строку поиска и возвращает все элементы массива которые равны этой строке поиска.

Взаимная рекурсия

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

Разобраться в этом трюке попробуем на примере. Представим себе кибер-дерево в узле которого может содержится яблоко в виде 1, 0 обозначает отсутствие яблока. Представим наше дерево визуально:

И в виде кода:

var n1 = {apple: 1, childs: []};
var n2 = {apple: 1, childs: []};
var n3 = {apple: 0, childs: []};
var n4 = {apple: 0, childs: []};

var n5 = {apple: 0, childs: [n1,n2,n3]};
var n6 = {apple: 1, childs: []};
var n7 = {apple: 1, childs: [n4]};

var root = {apple: 1, childs: [n7, n6, n5]};

Наша задача — пройтись по всему дереву и собрать все яблоки (единицы). Результат должен быть в виде массива яблок.

Алгоритм и решение

Давайте назовем нашу функцию harvest. На вход ей будет поступать дерево (узел с потомками), на выходе будет массив. Поскольку узел может содержать или не содержать яблоко нам надо обработать оба этих случая. Для простоты пока отбросим потомков. В случае если яблоко присутствует нам надо положить его в результирующий массив, который будет возвращен в результате работы функции. Поскольку мы пока отбросили потомков в случае отсутствия яблока у узла нам делать ничего не надо:

// @param  {object: tree} Дерево              
// @return {arrayof: 1}   Массив яблок
var harvest = function(tree){
  if(tree.apple==1){
    return [1];
  }else{
    // яблока нет
  }
};

А теперь подумаем о потомках нашего дерева. Потомки содержатся в поле childs нашего узла и представлены в виде массива таких же узлов. Давайте пожелаем чтобы у нас была функция harvestChilds которая обрабатывает этих потомков. Сделаем для нее заглушку, так мы сможем закончить нашу функцию harvest, а к новой функции вернемся позже:

// !!! - оставим для себя отметку что эта функция не реализована
var harvestChilds = function(){}

Вернемся к функции harvest. Если у нас есть яблоко сейчас мы возвращаем [1], то есть только яблоко текущего узла, при этом мы никак не учитываем потомков. Давайте представим что наша функция harvestChilds возвращает массив яблок потомков, если это так тогда нам надо к нашей единице прибавить этот массив. Мы это уже делали в функции map используя метод concat, в этом случае поступим так же.

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

// @param  {object: tree} Дерево              
// @return {arrayof: 1}   Массив яблок
var harvest = function(tree){
  if(tree.apple==1){
    return [1].concat(harvestChilds(tree.childs));
  }else{
    return harvestChilds(tree.childs);
  }
};
 
// !!! - оставим для себя отметку что эта функция не реализована
// @param  {arrayof: tree} childs Потомки дерева
// @return {arrayof: 1}           Яблоки, собранные с потомков
var harvestChilds = function(childs){
  return [];
};

С harvest вроде разобрались, перейдем к harvestChilds. Как мы уже знаем она должна обработать каждый элемент массива потомков, который ей был передан, и вернуть массив яблок которые содержат эти потомки. Все это, опять же, похоже на нашу функцию map, поэтому давайте и эту функцию реализуем в рекурсивном стиле.

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

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

Что будем возвращать в базовом случае? Поскольку в процессе разработки функции harvest мы пожелали чтобы наша функция harvestChilds возвращала массив, тогда наш базовый случай будет возвращать пустой массив. Представим наши изменения в виде кода:

// !!! - оставим для себя отметку что эта функция не реализована
// @param  {arrayof: tree} childs Потомки дерева
// @return {arrayof: 1}           Яблоки, собранные с потомков
var harvestChilds = function(childs){
  if(childs.length==0){
    return []; // базовый случай
  }else{
    //  рекурсивный случай
  }
};

Осталось самое интересное — рекурсивный случай. На входе нас ждет массив поддеревьев-потомков, каждый элемент которого надо обработать следующим образом: если у элемента есть яблоко тогда надо прибавить к результирующему массиву один элемент, если яблока нет... Так, стоп. Догадались? У нас уже есть функция которая это делает — harvest.

Мы подошли к ключевому моменту — созданию цикла взаимной рекурсии. Давайте взглянем на функцию harvest. Для своих потомков она вызывает функцию harvestChilds, а harvestChilds для каждого потомка будет вызывать функцию harvest и так далее. Таким образом мы обработаем все наше дерево слево-направо. Представим работу алгоритма визуально, зеленым подсвечиваются обработанные узлы на данном шаге:

Заметим так же очень интересный факт — поскольку наши данные в виде деревьев имеют взаимную ссылку (потомок является тем же типом данных что и родитель) и функция у нас получилась со взаимной ссылкой. Подумайте над этим.

Но вернемся к нашему коду. Каждый элемент массива потомков мы будем обрабатывать функцией harvest, которая возвращает массив. Для того чтобы обработать не только первого потомка, но и всех остальных нам надо рекурсивно вызвать функцию harvestChilds и передать ей массив потомков из которого исключен первый потомок, потомок который мы уже обработали. Результат этой функции надо прицепить к результату обработки первого потомка, как мы это делали в функции map. Открываем редактор, тянем руки к клавиатуре, поехали:

// @param  {arrayof: tree} childs Потомки дерева
// @return {arrayof: 1}           Яблоки, собранные с потомков
var harvestChilds = function(childs){
  if(childs.length==0){
    return []; // базовый случай
  }else{
    //  рекурсивный случай
    var first = childs[0];
    var rest  = childs.slice(1, childs.length);
    return harvest(first).concat(harvestChilds(rest));
  }
};

Посмотрим на программу целиком:

var n1 = {apple: 1, childs: []}
var n2 = {apple: 1, childs: []}
var n3 = {apple: 0, childs: []}
var n4 = {apple: 0, childs: []}
 
var n5 = {apple: 0, childs: [n1,n2,n3]}
var n6 = {apple: 1, childs: []}
var n7 = {apple: 1, childs: [n4]}
 
var root = {apple: 1, childs: [n7, n6, n5]}
 
// @param  {object: tree} Дерево              
// @return {arrayof: 1}   Массив яблок
var harvest = function(tree){
  if(tree.apple==1){
    return [1].concat(harvestChilds(tree.childs));
  }else{
    return harvestChilds(tree.childs);
  }
};
 
// @param  {arrayof: tree} childs Потомки дерева
// @return {arrayof: 1}           Яблоки, собранные с потомков
var harvestChilds = function(childs){
  if(childs.length==0){
    return []; // базовый случай
  }else{
    //  рекурсивный случай
    var first = childs[0];
    var rest  = childs.slice(1, childs.length);
    return harvest(first).concat(harvestChilds(rest));
  }
};

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

harvest(root);

У меня вернулся массив из 5 яблок, если у вас что то не так сверьте ваш код с моим. Поздравляю, мы отлично поработали.

Выше мы представили визуально цикл натуральной рекурсии, давайте так же представим цикл взаимной рекурсии:

Практика

Небольшое задание для вас. Представим что у нас есть хтмл-дерево со структурой:

Представим эту структуру в виде объектов яваскрипта:

var p    = {element: "p", id: "some-text", childs: []};
var li1  = {element: "li", id: "list-el-1", childs: []};
var li2  = {element: "li", id: "list-el-2", childs: []};
var ol   = {element: "ol", id: "list", childs: [li1, li2]};
var img  = {element: "img", id: "pic-of-me", childs: []};
var body = {element: "body", id: "", childs: [p, ol, img]};
var html = {element: "html", id: "", childs: [body]};

Ваша задача будет вывести в консоль все id элементов в этой структуре. Если у элемента нету id тогда ничего выводить в консоль не надо.

Заключение

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

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

04.09.2013
архивирован

Сегодня начинается курс «Введение в систематический программный дизайн»

Я вас уже в какой-то мере наверное замучил своими приглашениями на курсы, но сегодня произошло то, о чем я просто не могу молчать. Сегодня началась очередная сессия курса «Введение в систематический программный дизайн» от замечательного инструктора Грегора Кинцалеса, профессора Университета Британской Колумбии.

Почему я вас не приглашаю на этот раз, а просто оповещаю? Я уже закончил предыдущую сессию этого курса недели 2 назад, и скажу вам что это стоило каждой секунды времени которые я потратил. Для тех из вас кто никогда не программировал и хочет получать 150 тысяч рублей сидя на жопе (в Москве), это замечательный шанс попробовать, курс не требует никакого предыдущего опыта написания программ. Но для тех кто уже схватил карпальный туннельный синдром это тоже будет интересным опытом, особенно если вы до этого не программировали в функциональном стиле или ваши познания в методах дизайна программ не особо большие.

Попозже я напишу обзор на этот курс, думаю что он будет довольно большим потому что я в восторге от этого курса. А сейчас краткий ввод в курс дела. В процессе обучения вы изучите интересный метод дизайна программ, который состоит из трех частей: анализ проблемы, разработка структур данных для представления информации, разработка функций через написание тестов. Главная мантра этого метода — информация в проблеме влияет на представление данных в программе, которые в свою очередь влияют на структуру функций и всей программы. Этот метод позволяет бороться с главной проблемой при написании программного обеспечения — сложностью. Лично я о многих проблемах в процессе работы стал думать по другому, в более правильном ключе. Вы так же познакомитесь с языком программирования Ракет, оседлаете рекурсивные функции, научитесь работать с деревьями. Так же будет куча практических заданий в которых вы реализуете кучу интересных программ, самые интересные задания в моей сессии были связаны с отображением фракталов и написанием программы которая автоматически решает Судоку.

Перевод части описания курса:

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

Чтобы создавать свои собственные программы вам нужно знать две вещи: как использовать язык программирования и его библиотеки, а так же более общий навык проектирования программ.

Этот курс представит вам подход проектирования который позволит вам осилить создание сложных программ систематически. Этот метод будет работать для программ которые вы создадите в процессе обучения так же как и для тяжелых программных проектов которые вы создадите в будущем.

02.09.2013

Деревья

Давайте сблизимся с природой, посадим дерево и соберем с него урожай. Чувствую ваше сердцебиение участилось и рука тянется к Ctrlw. Спокойно, от клавиатуры мы отходить не будем, нашей окружающей средой будет Яваскрипт, память компьютера будет почвой, а урожаем будет информация.

Деревья — одна из наиболее распространенных структур данных в информатике. И это не просто так, деревья позволяют представлять большое количество информации и при этом они очень просты и понятны для человека.

Перед тем как дать формальное описание давайте посмотрим как они выглядят:

Это дерево? В реальном мире деревья растут вверх, вниз у них растут только корни. Все у этих компьютерщиков не так.

ожидание и реальность

Структура

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

  • Корневой узел
  • Узел
  • Терминальный узел
  • Связь

Корневой узел это начало нашего дерева, то что у биологического дерева вырастает из земли. Корневой узел не может быть потомком другого узла.

Узел это потомок корневого узла. В отличии от корневого узла он может иметь узел-родитель. Если провести аналогию с настоящим деревом то узел и его потомки это ветка.

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

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

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

Представление данных

Деревья позволяют представлять данные имеющие иерархию. Приведем пример структуры каталогов, представленной в виде дерева:

Как мы видим в корне у нас находится каталог с именем / который содержит каталоги дир1 и дир2, дир1 в свою очередь содержит дир3.

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

Давайте приведем еще более интересный пример. Знаете ли вы что любую конечную и последовательную игру (конечное число ходов, игроки ходят последовательно) можно представить в виде дерева? Шахматы, шашки, крестики-нолики: все они имеют конечное число ходов и комбинаций, вопрос только какое это число. Клод Шеннон в 1950 году вычислил количество возможных не повторяющихся шахматных партий — 10120. Этой особенностью пользуются компьютерные программы которые успешно могут противостоять лучшим игрокам — людям.

У нас нет таких вычислительных мощностей чтобы посчитать все дерево шахмат, поэтому давайте построим игровое дерево для вот этой незаконченной партии в крестики-нолики, ходит крестик:

Данная партия складывается не в пользу нолика, только в одном узле данного дерева он выигрывает. Построить такое дерево очень просто:

  1. Берем данную доску и поочередно ставим в каждую пустую клетку знак текущего игрока. Получившийся массив досок это потомки исходной доски.
  2. Если при установке знака произошел выигрыш текущего игрока, или ничья, тогда у этой доски не может быть потомков.
  3. Для каждого потомка данной доски, в котором нету выигрыша или ничьи, делаем то что описано в пункте 1.

Реализация на Яваскрипте

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

Давайте посмотрим на наше дерево каталогов, которое мы представили ранее. Для простоты откинем родительскую связь, тогда у нас остаются узлы, которые содержат данные и могут иметь потомков.

Данные это имя каталога, у нас это: /, дир1, дир2, дир3. Это можно представить в виде строки:

"/"
"дир1"
"дир2"
"дир3"

Еще у нас должно быть место для связи, но сначала давайте рассмотрим эту связь отдельно. Каталог дир3 не содержит потомков, следовательно это должно быть представлено в виде каких то пустых данных. Каталог дир1 содержит каталог дир3, а каталог / содержит каталоги дир1 и дир2. Это тип данных с неопределенной длинной, ага, это же массив.

И все это, данные и связи, должны быть представлены в виде одного элемента. Комбинировать данные нам поможет массив, представим каталог дир3:

["дир3",[]]

Первый элемент массива это наши данные, второй это собственно связи. Поскольку дир3 не имеет связей вторым элементом массива идет пустой массив.

Непонятно и сложно? Яваскрипт спешит на помощь, в нем есть тип данных объект который представляет из себя ассоциативный массив. Давайте представим дир3 используя объект:

{name: "дир3", childs: []}

Стало намного понятнее, а теперь давайте представим всю структуру каталогов:

var dir3 = {name: "дир3", childs: []}
var dir2 = {name: "дир2", childs: []}
var dir1 = {name: "дир1", childs: [dir3]}
var root = {name: "/",    childs: [dir1, dir2]}

Тадамс, дерево посадили. Попробуем вывести имя каталога дир3, внука корневого каталога:

console.log(root.childs[0].childs[0].name)

Практика

Небольшая задача для вас. В Студии Лебедева продается набор матрешек «Матрешкус бланко», он состоит из шести матрешек: бит, байт, килобайт, мегабайт, гигабайт, терабайт. Представьте эти матрешки в виде древа, в корневом узле должен находится терабайт, далее его потомок гигабайт и так далее. Решение в комменты.

Дальше — больше

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

27.08.2013
архивирован

Скидки ко дню знаний на Кодскул

Скоро первое сентября, первый день школы, а если вы гражданин России, Казахстана, Киргизии, Белоруссии, Молдавии, Туркмении, Украины или Приднестровской Молдавской Республики то это еще и хороший праздник — день знаний. К этому замечательному событию один из моих любимейших порталов онлайн-образования Кодскул объявил скидку в 20% на оплату обучения, 20 баксов вместо 25 за месяц. Скидка действует до 2 сентября, при покупке ввести промокод:

Backtoschool

Позже я напишу пост о своем опыте учебы на этом портале и детально разберу его по косточкам. А сейчас небольшой обзор, чтобы вы понимали о чем речь.

Кодскул фокусируется на обучении программированию и дизайну. Процесс обучения состоит из просмотра лекций и выполнения заданий. Вся крутость состоит в том, что вам не надо устанавливать какие-то программы на свою машину, все задания выполняются прямо в браузере. Даже при обучении программированию под ай-о-эсс. Если вы столкнулись с какими-то проблемами в процессе задания можно воспользоваться подсказками. Ничего лучше и проще чем Кодскул для получения практических навыков в короткие сроки, а курс можно закончить за пару дней, я пока не встречал, там даже ребенок научится кодить. В лучших традициях геймификации в конце выдаются награды и очень щедрые бонусы, вот значки которые я заработал на сегодняшний день. Если хотите попробовать на вкус, но не платить месяц, вот замечательный бесплатный курс «Рельсы для зомби».

23.08.2013
архивирован

Приглашаю на курс «Введение в математическое мышление»

Через 10 дней стартует замечательный курс «Введение в математическое мышление» от Стэнфордского университета.

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

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

Перевод описания курса с его страницы:

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

Математическое мышление это не то же самое что математика, по крайне как нам это представляют в школе. Школьная математика обычно сфокусирована на изучении процедур для того чтобы решать стереотипные проблемы. Профессиональные математики думают в особом ключе чтобы решать реальные проблемы, проблемы которые могут возникнуть в нашем повседневном мире, или из науки, или из самой математики. Ключ к успеху в школьной математике — мыслить внутри коробки. В контрасте, ключевая особенность математического мышления это думать вне коробки — очень ценное умение в современном мире. Этот курс поможет вам развить этот замечательный тип мышления.

Постскриптум

Еще напомню вам что сейчас проходит сессия курса «Введение в историю человечества», куда я вас уже приглашал. Я уже закончил первую неделю, очень рекомендую зарегистрироваться пока еще можно.