Практическая информатика

Модели и программирование

Разбить на страницы
Показывать лекцию целиком

Моделирование

В 1870 г. английское Адмиралтейство спустило на воду новый броненосец "Кэптен". Корабль вышел в море и перевернулся. Погиб корабль и все находящиеся на нем люди. Это было совершенно неожиданно для всех, кроме английского ученого-кораблестроителя В. Рида, который предварительно провел исследования на модели броненосца и установил, что корабль опрокинется даже при небольшом волнении. Но ученому, проделывающему, как казалось, несерьезные опыты с "игрушкой", не поверили лорды из Адмиралтейства. И случилось непоправимое...

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

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

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

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

Различают материальное и идеальное моделирование. Материальное моделирование, в свою очередь, делится на физическое и аналоговое моделирование.

Физическим принято называть моделирование, при котором реальному объекту противопоставляется его увеличенная или уменьшенная копия, допускающая исследование (как правило, в лабораторных условиях) с помощью последующего перенесения свойств изучаемых процессов и явлений с модели на объект на основе теории подобия. Примерами моделей такого рода служат: в астрономии - планетарий, в архитектуре - макеты зданий, в самолетостроении - модели летательных аппаратов и т. п.

Аналоговое моделирование основано на аналогии процессов и явлений, имеющих различную физическую природу, но одинаково описываемых формально (одними и теми же математическими уравнениями).

От предметного моделирования принципиально отличается идеальное моделирование, которое основано не на материальной аналогии объекта и модели, а на аналогии идеальной, мыслимой. Основным типом идеального моделирования является знаковое моделирование.

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

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

Пример

Посмотрите на следующую запись и попробуйте определить, что скрывается за этими знаками:

a1x1+b1x2=c1
a2x1+b2x2=c2

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

Математик: "Это система двух линейных алгебраических уравнений с двумя неизвестными, но что именно она выражает, сказать не могу".

Инженер-электрик: "Это уравнения электрического напряжения или токов с активными напряжениями".

Инженер-механик: "Это уравнения равновесия сил для системы рычагов или пружин".

Инженер-строитель: "Это уравнения, связывающие силы деформации в какой-то строительной конструкции".

Какой же из ответов правильный? Не удивляйтесь, но каждый из них в некотором смысле верен. Все зависит от того, что скрывается за постоянными коэффициентами a, b, c и символами неизвестных x1 и x2.

Схема процесса моделирования

Объект -> Модель -> Изучение модели -> Знания об объекте

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

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

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

Компьютерное моделирование

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

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

Примером имитационного моделирование может служить вычисление числа $$\pi$$ = 3,1415922653... методом Монте-Карло. Этот метод позволяет определять площади и объемы фигур (тел), которые сложно вычислить другими методами. Предположим, что требуется определить площадь круга. Опишем вокруг него квадрат (площадь которого, как известно, равна квадрату его стороны) и будем случайным образом бросать в квадрат точки, проверяя каждый раз, попала ли точка в круг или нет. При большом числе точек отношение площади круга к площади квадрата будет стремиться к отношению числа точек, попавших в круг, к общему числу брошенных точек.

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

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

Заметим, что располагая датчиком равномерно распределенных случайных чисел, генерирующим числа r из интервала [0; 1), легко получить равномерно распределенные случайные числа на произвольном интервале [a; b) по формуле

x=a+(b-a)*r.

Задания

  • Разработайте модель случайного одномерного блуждания (модель "пьяницы"). Блуждание задается по правилу: если случайное число из отрезка [0;1) меньше 0,5, то делается шаг влево, в противном случае - вправо.

    Для реализации модели используйте электронную таблицу. Предположим, что в начальный момент объект наблюдения находится в точке с y -координатой равной y0. Если случайное число больше 0,5, то y -координата увеличивается на 1, в противном случае - уменьшается на 1.

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

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

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

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

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

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

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

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

    Познакомимся подробнее с игрой "Жизнь", относящейся к категории так называемых моделирующих игр - игр, которые в той или иной степени имитируют процессы, происходящие в реальной жизни. Жизнь, как естественный процесс - явление настолько сложное и увлекательное, что тысячи ученых пытались раскрыть ее тайны. Свой вклад в решение этой проблемы внес и человек, не имевший к биологии никакого отношения, английский математик Джон Хортон Конвей.

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

    Действие игры происходит на плоскости, разделенной на клетки. Каждая клетка окружена 8 такими же клетками (так называемая окрестность Мура ) и может находиться в двух состояниях - живом или мертвом (быть пустой). Гибель и рождение всех организмов происходит одновременно. На состояние любой клетки оказывают влияние только состояния соседних с ней клеток. Во времени эти состояния дискретно изменяются в соответствии со следующими правилами ( генетическими законами Конвея ).

  • Выживание или гибель. Если живая клетка имеет менее 2 или более 3 соседей в окрестности из 8 клеток, то в следующем поколении она умирает (моделирование реальных условий - недостатка питания или перенаселенности), в противном случае она выживает.
  • Рождение. В пустой клетке появляется новая живая клетка, если у нее ровно 3 соседа.
  • Основная идея игры состоит в том, чтобы, начав с некоторого расположения живых клеток, проследить за ее эволюцией. Популяция клеток постоянно претерпевает необычные изменения. Иногда колония клеток постепенно вымирает, однако произойти это может не сразу, а лишь после того, как сменится очень много поколений.

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

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

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

    Планер (glider) - это самый ранний из обнаруженных и наиболее примечательный объект, одной из особенностей которого является его способность спонтанно возникать в совершенно произвольных ситуациях и перемещаться за пределы видимого пространства (в случае, когда движение направлено в противоположную сторону от сосредоточения основных объектов популяции). Через определенное количество шагов планеры покидают пределы освоенной области и устремляются в "космические дали".

    Более подробную информацию о игре "Жизнь" и программы для наблюдения за эволюцией объектов можно найти по следующему URL: elvisti.kiev.ua/skl/conwey1/w_life_n.htm.

    Как уже было сказано, игра "Жизнь" описывается с помощью теории автоматов. На основе этого примера можно сформулировать общие правила построения клеточных автоматов.

  • Состояние клеток дискретно (обычно 0 и 1, хотя могут быть автоматы и с большим числом состояний).
  • Соседями является ограниченное число клеток, часто это ближайшие клетки.
  • Правила, задающие динамику развития клеточного автомата, обычно имеют простую функциональную форму и зависят от решаемой проблемы.
  • Клеточный автомат является тактируемой системой, т. е. смена состояний клеток происходит одновременно.
  • Клеточные автоматы предоставляют большую свободу в выборе структуры и правил развития системы. Это позволяет моделировать на их основе и решать с их помощью самые разнообразные задачи.
  • Представим себе некую исходную фигуру на плоскости, к которой начинают применяться правила "Жизни". В результате эволюции возникнут сотни, а в отдельных случаях и тысячи разнообразнейших (на первый взгляд не связанных друг с другом) фигур. Но достаточно лишь знать начальное расположение элементов и номер шага, чтобы восстановить нужную фигуру. По-видимому, эта идея может быть использована для построения новых алгоритмов сжатия информации.

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

    Задания

  • Постройте модель процесса распространения инфекции стригущего лишая по участку кожи размером n x n (n-нечетное) клеток. Заражение начинается с центральной клетки. В каждый интервал времени пораженная инфекцией клетка может с вероятностью 1/2 заразить любую из соседних здоровых клеток. Через шесть единиц времени зараженная клетка становится невосприимчивой к инфекции. Возникший иммунитет действует в течение последующих четырех единиц времени, а затем клетка выздоравливает.

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

  • Разработайте имитационную модель системы "хищник-жертва" по следующей схеме. Остров размером 20x20 заселен дикими кроликами, волками и волчицами. Имеется несколько представителей каждого вида. Кролики довольно глупы: в каждый момент времени они с одинаковой вероятностью 1/9 передвигаются в один из восьми соседних квадратов (за исключением участков, ограниченных береговой линией) или просто сидят неподвижно. Каждый кролик с вероятностью 0,2 превращается в 2 кроликов. Волчицы передвигаются случайным образом до тех пор, пока в одном из соседних восьми квадратов не окажется кролик. Если волчица и кролик оказываются в одном квадрате, волчица съедает кролика и получает одно очко, в противном случае она теряет 0,1 очка за каждую единицу времени. Волки и волчицы с нулевым количеством очков умирают. В начальный момент времени все волки и волчицы имеют 1 очко. Волк ведет себя подобно волчице до тех пор, пока в соседних квадратах не исчезнут все кролики; в этом случае, если волчица находится в одном из восьми ближайших квадратов, волк гонится за ней. Если волк и волчица окажутся в одном квадрате, они производят потомство случайного пола. Проследите, как сказываются на эволюции популяции изменение различных параметров модели.
  • Парадигмы программирования

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

  • директивные (directive), называемые также процедурными (procedural) или императивными (imperative),
  • декларативные (declarative) языки,
  • объектно-ориентированные (object-oriented).
  • К директивным языкам относятся такие классические языки программирования, как Algol, Fortran, Basic, Pascal, C. Наиболее существенными классами декларативных языков являются функциональные (functional) или аппликативные, и логические (logic) языки. К категории функциональных языков относятся, например, Lisp и Haskell. Самым известным языком логического программирования является Prolog (Пролог). Среди объектно-ориентированных языков программирования (языков ООП) отметим C++, Java, Python и Ruby.

    Отложим пока обсуждение концепции ООП и поговорим о различии между первыми двумя парадигмами. Главное заключается в следующем: декларативная программа заявляет (декларирует), что должно быть достигнуто в качестве цели, а директивная предписывает, как ее достичь.

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

    Директивная программа - это список команд примерно такого рода: от пункта А по ул. Садовой на север до площади Славы, оттуда по ул. Пушкина два квартала, потом повернуть направо и идти до Театрального переулка, по этому переулку налево по правой стороне до дома 20, который и есть пункт Б.

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

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

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

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

    Директивное программирование

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

    Директивное программирование стали называть процедурным, когда в процессе увеличения сложности моделируемых систем и размера получаемых программ возникла концепция подпрограмм, называемых также процедурами (procedure), функциями (function) или методами (method). Подпрограмма позволяет локализовать в ней процесс выполнения определенного действия, которое может быть повторено многократно с помощью механизма вызова.

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

    Ниже приводится пример программы на языке C, в которой кроме главной функции main используются еще две подпрограммы - print_array, печатающая элементы переданного ей массива целых чисел, и selection, сортирующая массив, переданный ей в качестве аргумента.

    #include <stdio.h>
    
    void print_array(int c[], int n, char* t) {
      int i;
      printf("%s",t);
      for (i = 0; i < n; i += 1)
        printf("ta[%d]=%d", i, c[i]);
      printf("\n");
    }
    
    void selection(int c[], int n) {
        int i, j, k, x;
        
        for (i = 0; i < n; i += 1) {
    	for (x = c[k=i], j = i + 1; j < n; j++)
    	    if (c[j] < x) x = c[k=j];
    	c[k] = c[i]; c[i] = x;
        }
    }
    
    int main(void) { 
      int a[] = {8, 3, 2, 7, 9, 5};
    
      int n = sizeof(a)/sizeof(int);
      print_array(a, n, "Исходный массив\n");
      selection(a, n);
      print_array(a, n, "Отсортированный массив\n");
      return 0;
    }

    Разместите текст этой программы в файле с именем sort.c и выполните следующие команды, компилирующие и запускающие ее:

    cc sort.c
    ./a.out

    Функция main дважды вызывает процедуру print_array: сначала для печати исходного массива, а затем, после вызова функции selection, для печати его же в уже отсортированном виде. Однажды реализованные функции print_array и selection могут быть использованы при написании относительно большой программы многократно.

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

    Со временем при проектировании программ акцент сместился с организации процедур на организацию структур данных. Современные директивные языки программирования предлагают еще один метод структурирования программ: инкапсуляция (от слова capsule - капсула, контейнер) данных и подпрограмм в более крупные объекты, называемые модулями. Большую часть данных модуля и выполняемые операторы можно скрыть таким образом, что их нельзя будет изменить или использовать способами, отличными от заранее предопределенных. Эта парадигма известна, как принцип сокрытия данных. Если в языке нет возможности сгруппировать процедуры вместе с данными, то он плохо поддерживает модульный стиль программирования.

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

    Стек функционирует точно также, только в нем хранится совокупность произвольных элементов. Новый элемент помещается на вершину стека с помощью операции втолкнуть (push). Виден в стеке только самый верхний элемент, который может быть извлечен из него командой вытолкнуть (pop). Иногда говорят, что стек задает дисциплину обслуживания LIFO (Last In First Out - последним пришел, первым выйдешь). Организация данных в виде стека широко распространена в программировании. Например, управление автоматически распределяемой памятью в процессе выполнения программы производится по принципу стека.

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

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

    Декларативное программирование

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

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

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

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

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

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

    Функциональное программирование весьма красиво и иногда в качестве первого языка программирования, изучаемого студентами, выбирается Haskell или Lisp. Для успешного овладения данным стилем программирования, впрочем, необходимо весьма глубокое понимание многих разделов математики.

    Пример

    Хорошей иллюстрацией функционального стиля программирования является программа на языке Haskell для получения всех пифагоровых троек чисел, не превосходящих заданного числа (пифагоровой тройкой называют три целых числа, являющихся сторонами некоторого прямоугольного треугольника).

    Создайте файл с именем triads.hs в который поместите следующий текст: triads n = [(x,y,z)|let ns=[1..n], x<-ns, y<-ns, z<-ns, x*x+y*y==z*z] (скачать файл triads.hs )

    triads n = [(x,y,z) | let ns = [1 .. n], 
                 x <- ns, y <- ns, z <- ns, x*x+y*y == z*z]

    Такую программу легко понять: получить все тройки целых чисел x, y и z, не превышающих заданного числа n и удовлетворяющих условию x2+y2=z2.

    Для запуска интерпретатора языка Haskell в командной строке наберите hugs. После появления приглашения > введите команду :load triads.hs для загрузки содержимого файла в память. Теперь можно находить пифагоровы триады, например, при помощи следующего вызова функции triads 50. Для завершения работы интерпретатора наберите :quit и нажмите на клавишу Enter.

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

    Пример

    Создайте файл с именем primes.hs и поместите в него следующие строки:

    -- primes      :: Integral a => [a]
    primes       = map head (iterate sieve [2..])
    sieve (p:xs) = [ x | x<-xs, x `rem` p /= 0 ]

    (скачать файл primes.hs )

    primes  = map head (iterate sieve [2 ..])
    sieve (p:xs) = [ x | x <- xs, x `rem` p /= 0 ]

    После старта интерпретатора hugs и загрузки в него этой программы достаточно вызвать функцию primes (без аргументов) и программа начнет печатать простые числа до тех пор, пока вы не прервете ее выполнение, нажав комбинацию клавиш Ctrl+C.

    Еще одной реализацией декларативного стиля является логическое программирование, основанное на логике предикатов, которое подробно рассматривается в следующей главе.

    Логика предикатов - это ветвь формальной логики, получившая развитие в XX веке. В логическом программировании основное внимание уделяется описанию структуры прикладной задачи, а не выработке предписаний компьютеру, что ему следует делать. Prolog (от французского PROgrammation LOGique, далее Пролог) - это наиболее известный язык логического программирования. Этот язык (наряду с функциональным языком Lisp) часто называют языком искусственного интеллекта - с его помощью решаются задачи создания экспертных систем и систем обработки естественных языков.

    Пример

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

    "Где-то в непроходимых джунглях, недалеко от города Ханоя, есть монастырь бога Брамы. В начале времен, когда Брама создавал Мир, он воздвиг в этом монастыре три высоких алмазных стержня и на один из них возложил 64 диска, сделанных из чистого золота. Он приказал монахам перенести эту башню на другой стержень (в соответствии с правилами, разумеется). С этого времени монахи работают день и ночь. Когда они закончат свой труд, наступит конец света."

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

    Поместите в файл с именем hanoi.pl следующий текст (символ % начинает комментарий, который не обязательно помещать в файл).

    % move(число_дисков, откуда, куда, через)
    move(1,X,Y,_) :-  
             write('Move top disk from '), %передвиньте верхний диск с
             write(X), write(' to '), 
             write(Y), nl. 
    move(N,X,Y,Z) :- 
             N>1, 
             M is N-1, 
             move(M,X,Z,Y), 
             move(1,X,Y,_), 
             move(M,Z,Y,X).

    (скачать файл hanoi.pl )

    % move(число_дисков, откуда, куда, через)
    move(1,X,Y,_) :- write('Move top disk from '),
                     write(X), write(' to '),  
                     write(Y), nl. 
    move(N,X,Y,Z) :- N>1, M is N-1, 
                     move(M,X,Z,Y),
                     move(1,X,Y,_), 
                     move(M,Z,Y,X).

    Запустите интерпретатор языка Пролог при помощи команды pl. После появления приглашения к работе (?- ) загрузите содержимое файла командой [hanoi]. (расширение файла указывать не нужно, а вот точка после закрывающей квадратной скобки необходима). Теперь, чтобы заставить Пролог решить задачу о перемещении трех дисков, введите следующий запрос:

    move(3,left,right,center).

    (не забудьте о точке в конце ввода). Ниже приводится порядок перемещения дисков, найденный этой программой.

    Для завершения работы с интерпретатором наберите команду halt. и нажмите Enter.

    Задания

  • Измените программу triads.hs так, чтобы не выводились одинаковые тройки чисел, такие как (3,4,5) и (4,3,5). Для этого введите дополнительное условие, например, x<y.
  • Получите решение головоломки "Ханойская башня" для четырех дисков.
  • Объектно-ориентированное программирование

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

    Практически все современные языки программирования, независимо от принадлежности к тому или иному стилю (директивному или декларативному), поддерживают концепцию ООП. Среди них C++, Java, Ruby и Haskell. Существуют и версии объектно-ориентированного Пролога.

    Можно сказать, что ООП - это моделирование объектов посредством иерархически связанных классов. При этом малозначащие детали объекта скрыты от нас, и если мы даем команду какому-то объекту, то он "знает", как ее выполнить. Фундаментальной концепцией в ООП является понятие обязанности или ответственности за выполнение действия.

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

    Классы представляются в виде иерархической древовидной структуры, в которой классы с более общими чертами располагаются в корне дерева, а специализированные классы и в конечном итоге индивидуумы располагаются в ветвях. На рисунке показана одна из возможных иерархий классов, включающая в себя собак Белку и Стрелку, кошку Мурку и утконоса Фросю.

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

    Итак, в основе ООП лежат три основных понятия:

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

    Наследование - это процесс, в результате которого один тип наследует свойства другого типа.

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

    Пример

    Для иллюстрации некоторых принципов ООП приведем небольшую программу на языке Ruby, который тоже поддерживает объектно-ориентированный стиль программирования.

    Поместите в файл с именем life.rb фрагмент кода, расположенный ниже.

    #!/usr/bin/ruby
    
    class Animal
       def breath #Дыхание
         print "все животные дышат: вдохнули и выдохнули\n" 
       end
    end
    
    class Cat<Animal
       def bark # Подать голос
          print "Mew Mew, я кошка. \n"
       end
    end
    
    class Dog
        def bark # Подать голос
          print "Bow Wow, я собака. \n"
        end
    end
    
    class Bird
       def lay_egg
         print "Яйцо снесено\n"
       end
       def fly
         print "Я птица, я лечу!!!\n"
       end
    end
    
    class Penguin<Bird
       def fly
          print "Пингвины не летают!!!\n"
       end
     end
    
    # Создаем объекты разных классов
    pochi = Dog.new
    pochi.bark
    
    tama = Cat.new
    tama.breath
    tama.bark
    
    macaw  = Bird.new
    macaw.lay_egg
    macaw.fly
    
    penguin  = Penguin.new
    penguin.lay_egg
    penguin.fly

    (скачать файл life.rb )

    Для запуска этой программы выполните в окне shell команду

    ruby life.rb

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

    Задания

  • Создайте еще одну кошку.
  • Объясните, кем является pochi и сможет ли он выполнить команду pochi.breath (дышать)? Если нет, то внесите соответствующие изменения в текст программы.
  • Измените код программы так, чтобы и птицы в ней тоже умели дышать.
  • Страницы:

    Моделирование

    В 1870 г. английское Адмиралтейство спустило на воду новый броненосец "Кэптен". Корабль вышел в море и перевернулся. Погиб корабль и все находящиеся на нем люди. Это было совершенно неожиданно для всех, кроме английского ученого-кораблестроителя В. Рида, который предварительно провел исследования на модели броненосца и установил, что корабль опрокинется даже при небольшом волнении. Но ученому, проделывающему, как казалось, несерьезные опыты с "игрушкой", не поверили лорды из Адмиралтейства. И случилось непоправимое...

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

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

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

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

    Различают материальное и идеальное моделирование. Материальное моделирование, в свою очередь, делится на физическое и аналоговое моделирование.

    Физическим принято называть моделирование, при котором реальному объекту противопоставляется его увеличенная или уменьшенная копия, допускающая исследование (как правило, в лабораторных условиях) с помощью последующего перенесения свойств изучаемых процессов и явлений с модели на объект на основе теории подобия. Примерами моделей такого рода служат: в астрономии - планетарий, в архитектуре - макеты зданий, в самолетостроении - модели летательных аппаратов и т. п.

    Аналоговое моделирование основано на аналогии процессов и явлений, имеющих различную физическую природу, но одинаково описываемых формально (одними и теми же математическими уравнениями).

    От предметного моделирования принципиально отличается идеальное моделирование, которое основано не на материальной аналогии объекта и модели, а на аналогии идеальной, мыслимой. Основным типом идеального моделирования является знаковое моделирование.

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

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

    Пример

    Посмотрите на следующую запись и попробуйте определить, что скрывается за этими знаками:

    a1x1+b1x2=c1
    a2x1+b2x2=c2

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

    Математик: "Это система двух линейных алгебраических уравнений с двумя неизвестными, но что именно она выражает, сказать не могу".

    Инженер-электрик: "Это уравнения электрического напряжения или токов с активными напряжениями".

    Инженер-механик: "Это уравнения равновесия сил для системы рычагов или пружин".

    Инженер-строитель: "Это уравнения, связывающие силы деформации в какой-то строительной конструкции".

    Какой же из ответов правильный? Не удивляйтесь, но каждый из них в некотором смысле верен. Все зависит от того, что скрывается за постоянными коэффициентами a, b, c и символами неизвестных x1 и x2.

    Схема процесса моделирования

    Объект -> Модель -> Изучение модели -> Знания об объекте

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

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

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

    Компьютерное моделирование

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

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

    Примером имитационного моделирование может служить вычисление числа $$\pi$$ = 3,1415922653... методом Монте-Карло. Этот метод позволяет определять площади и объемы фигур (тел), которые сложно вычислить другими методами. Предположим, что требуется определить площадь круга. Опишем вокруг него квадрат (площадь которого, как известно, равна квадрату его стороны) и будем случайным образом бросать в квадрат точки, проверяя каждый раз, попала ли точка в круг или нет. При большом числе точек отношение площади круга к площади квадрата будет стремиться к отношению числа точек, попавших в круг, к общему числу брошенных точек.

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

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

    Заметим, что располагая датчиком равномерно распределенных случайных чисел, генерирующим числа r из интервала [0; 1), легко получить равномерно распределенные случайные числа на произвольном интервале [a; b) по формуле

    x=a+(b-a)*r.

    Задания

  • Разработайте модель случайного одномерного блуждания (модель "пьяницы"). Блуждание задается по правилу: если случайное число из отрезка [0;1) меньше 0,5, то делается шаг влево, в противном случае - вправо.

    Для реализации модели используйте электронную таблицу. Предположим, что в начальный момент объект наблюдения находится в точке с y -координатой равной y0. Если случайное число больше 0,5, то y -координата увеличивается на 1, в противном случае - уменьшается на 1.

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

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

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

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

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

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

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

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

    Познакомимся подробнее с игрой "Жизнь", относящейся к категории так называемых моделирующих игр - игр, которые в той или иной степени имитируют процессы, происходящие в реальной жизни. Жизнь, как естественный процесс - явление настолько сложное и увлекательное, что тысячи ученых пытались раскрыть ее тайны. Свой вклад в решение этой проблемы внес и человек, не имевший к биологии никакого отношения, английский математик Джон Хортон Конвей.

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

    Действие игры происходит на плоскости, разделенной на клетки. Каждая клетка окружена 8 такими же клетками (так называемая окрестность Мура ) и может находиться в двух состояниях - живом или мертвом (быть пустой). Гибель и рождение всех организмов происходит одновременно. На состояние любой клетки оказывают влияние только состояния соседних с ней клеток. Во времени эти состояния дискретно изменяются в соответствии со следующими правилами ( генетическими законами Конвея ).

  • Выживание или гибель. Если живая клетка имеет менее 2 или более 3 соседей в окрестности из 8 клеток, то в следующем поколении она умирает (моделирование реальных условий - недостатка питания или перенаселенности), в противном случае она выживает.
  • Рождение. В пустой клетке появляется новая живая клетка, если у нее ровно 3 соседа.
  • Основная идея игры состоит в том, чтобы, начав с некоторого расположения живых клеток, проследить за ее эволюцией. Популяция клеток постоянно претерпевает необычные изменения. Иногда колония клеток постепенно вымирает, однако произойти это может не сразу, а лишь после того, как сменится очень много поколений.

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

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

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

    Планер (glider) - это самый ранний из обнаруженных и наиболее примечательный объект, одной из особенностей которого является его способность спонтанно возникать в совершенно произвольных ситуациях и перемещаться за пределы видимого пространства (в случае, когда движение направлено в противоположную сторону от сосредоточения основных объектов популяции). Через определенное количество шагов планеры покидают пределы освоенной области и устремляются в "космические дали".

    Более подробную информацию о игре "Жизнь" и программы для наблюдения за эволюцией объектов можно найти по следующему URL: elvisti.kiev.ua/skl/conwey1/w_life_n.htm.

    Как уже было сказано, игра "Жизнь" описывается с помощью теории автоматов. На основе этого примера можно сформулировать общие правила построения клеточных автоматов.

  • Состояние клеток дискретно (обычно 0 и 1, хотя могут быть автоматы и с большим числом состояний).
  • Соседями является ограниченное число клеток, часто это ближайшие клетки.
  • Правила, задающие динамику развития клеточного автомата, обычно имеют простую функциональную форму и зависят от решаемой проблемы.
  • Клеточный автомат является тактируемой системой, т. е. смена состояний клеток происходит одновременно.
  • Клеточные автоматы предоставляют большую свободу в выборе структуры и правил развития системы. Это позволяет моделировать на их основе и решать с их помощью самые разнообразные задачи.
  • Представим себе некую исходную фигуру на плоскости, к которой начинают применяться правила "Жизни". В результате эволюции возникнут сотни, а в отдельных случаях и тысячи разнообразнейших (на первый взгляд не связанных друг с другом) фигур. Но достаточно лишь знать начальное расположение элементов и номер шага, чтобы восстановить нужную фигуру. По-видимому, эта идея может быть использована для построения новых алгоритмов сжатия информации.

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

    Задания

  • Постройте модель процесса распространения инфекции стригущего лишая по участку кожи размером n x n (n-нечетное) клеток. Заражение начинается с центральной клетки. В каждый интервал времени пораженная инфекцией клетка может с вероятностью 1/2 заразить любую из соседних здоровых клеток. Через шесть единиц времени зараженная клетка становится невосприимчивой к инфекции. Возникший иммунитет действует в течение последующих четырех единиц времени, а затем клетка выздоравливает.

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

  • Разработайте имитационную модель системы "хищник-жертва" по следующей схеме. Остров размером 20x20 заселен дикими кроликами, волками и волчицами. Имеется несколько представителей каждого вида. Кролики довольно глупы: в каждый момент времени они с одинаковой вероятностью 1/9 передвигаются в один из восьми соседних квадратов (за исключением участков, ограниченных береговой линией) или просто сидят неподвижно. Каждый кролик с вероятностью 0,2 превращается в 2 кроликов. Волчицы передвигаются случайным образом до тех пор, пока в одном из соседних восьми квадратов не окажется кролик. Если волчица и кролик оказываются в одном квадрате, волчица съедает кролика и получает одно очко, в противном случае она теряет 0,1 очка за каждую единицу времени. Волки и волчицы с нулевым количеством очков умирают. В начальный момент времени все волки и волчицы имеют 1 очко. Волк ведет себя подобно волчице до тех пор, пока в соседних квадратах не исчезнут все кролики; в этом случае, если волчица находится в одном из восьми ближайших квадратов, волк гонится за ней. Если волк и волчица окажутся в одном квадрате, они производят потомство случайного пола. Проследите, как сказываются на эволюции популяции изменение различных параметров модели.
  • Парадигмы программирования

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

  • директивные (directive), называемые также процедурными (procedural) или императивными (imperative),
  • декларативные (declarative) языки,
  • объектно-ориентированные (object-oriented).
  • К директивным языкам относятся такие классические языки программирования, как Algol, Fortran, Basic, Pascal, C. Наиболее существенными классами декларативных языков являются функциональные (functional) или аппликативные, и логические (logic) языки. К категории функциональных языков относятся, например, Lisp и Haskell. Самым известным языком логического программирования является Prolog (Пролог). Среди объектно-ориентированных языков программирования (языков ООП) отметим C++, Java, Python и Ruby.

    Отложим пока обсуждение концепции ООП и поговорим о различии между первыми двумя парадигмами. Главное заключается в следующем: декларативная программа заявляет (декларирует), что должно быть достигнуто в качестве цели, а директивная предписывает, как ее достичь.

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

    Директивная программа - это список команд примерно такого рода: от пункта А по ул. Садовой на север до площади Славы, оттуда по ул. Пушкина два квартала, потом повернуть направо и идти до Театрального переулка, по этому переулку налево по правой стороне до дома 20, который и есть пункт Б.

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

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

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

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

    Директивное программирование

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

    Директивное программирование стали называть процедурным, когда в процессе увеличения сложности моделируемых систем и размера получаемых программ возникла концепция подпрограмм, называемых также процедурами (procedure), функциями (function) или методами (method). Подпрограмма позволяет локализовать в ней процесс выполнения определенного действия, которое может быть повторено многократно с помощью механизма вызова.

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

    Ниже приводится пример программы на языке C, в которой кроме главной функции main используются еще две подпрограммы - print_array, печатающая элементы переданного ей массива целых чисел, и selection, сортирующая массив, переданный ей в качестве аргумента.

    #include <stdio.h>
    
    void print_array(int c[], int n, char* t) {
      int i;
      printf("%s",t);
      for (i = 0; i < n; i += 1)
        printf("ta[%d]=%d", i, c[i]);
      printf("\n");
    }
    
    void selection(int c[], int n) {
        int i, j, k, x;
        
        for (i = 0; i < n; i += 1) {
    	for (x = c[k=i], j = i + 1; j < n; j++)
    	    if (c[j] < x) x = c[k=j];
    	c[k] = c[i]; c[i] = x;
        }
    }
    
    int main(void) { 
      int a[] = {8, 3, 2, 7, 9, 5};
    
      int n = sizeof(a)/sizeof(int);
      print_array(a, n, "Исходный массив\n");
      selection(a, n);
      print_array(a, n, "Отсортированный массив\n");
      return 0;
    }

    Разместите текст этой программы в файле с именем sort.c и выполните следующие команды, компилирующие и запускающие ее:

    cc sort.c
    ./a.out

    Функция main дважды вызывает процедуру print_array: сначала для печати исходного массива, а затем, после вызова функции selection, для печати его же в уже отсортированном виде. Однажды реализованные функции print_array и selection могут быть использованы при написании относительно большой программы многократно.

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

    Со временем при проектировании программ акцент сместился с организации процедур на организацию структур данных. Современные директивные языки программирования предлагают еще один метод структурирования программ: инкапсуляция (от слова capsule - капсула, контейнер) данных и подпрограмм в более крупные объекты, называемые модулями. Большую часть данных модуля и выполняемые операторы можно скрыть таким образом, что их нельзя будет изменить или использовать способами, отличными от заранее предопределенных. Эта парадигма известна, как принцип сокрытия данных. Если в языке нет возможности сгруппировать процедуры вместе с данными, то он плохо поддерживает модульный стиль программирования.

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

    Стек функционирует точно также, только в нем хранится совокупность произвольных элементов. Новый элемент помещается на вершину стека с помощью операции втолкнуть (push). Виден в стеке только самый верхний элемент, который может быть извлечен из него командой вытолкнуть (pop). Иногда говорят, что стек задает дисциплину обслуживания LIFO (Last In First Out - последним пришел, первым выйдешь). Организация данных в виде стека широко распространена в программировании. Например, управление автоматически распределяемой памятью в процессе выполнения программы производится по принципу стека.

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

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

    Декларативное программирование

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

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

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

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

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

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

    Функциональное программирование весьма красиво и иногда в качестве первого языка программирования, изучаемого студентами, выбирается Haskell или Lisp. Для успешного овладения данным стилем программирования, впрочем, необходимо весьма глубокое понимание многих разделов математики.

    Пример

    Хорошей иллюстрацией функционального стиля программирования является программа на языке Haskell для получения всех пифагоровых троек чисел, не превосходящих заданного числа (пифагоровой тройкой называют три целых числа, являющихся сторонами некоторого прямоугольного треугольника).

    Создайте файл с именем triads.hs в который поместите следующий текст: triads n = [(x,y,z)|let ns=[1..n], x<-ns, y<-ns, z<-ns, x*x+y*y==z*z] (скачать файл triads.hs )

    triads n = [(x,y,z) | let ns = [1 .. n], 
                 x <- ns, y <- ns, z <- ns, x*x+y*y == z*z]

    Такую программу легко понять: получить все тройки целых чисел x, y и z, не превышающих заданного числа n и удовлетворяющих условию x2+y2=z2.

    Для запуска интерпретатора языка Haskell в командной строке наберите hugs. После появления приглашения > введите команду :load triads.hs для загрузки содержимого файла в память. Теперь можно находить пифагоровы триады, например, при помощи следующего вызова функции triads 50. Для завершения работы интерпретатора наберите :quit и нажмите на клавишу Enter.

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

    Пример

    Создайте файл с именем primes.hs и поместите в него следующие строки:

    -- primes      :: Integral a => [a]
    primes       = map head (iterate sieve [2..])
    sieve (p:xs) = [ x | x<-xs, x `rem` p /= 0 ]

    (скачать файл primes.hs )

    primes  = map head (iterate sieve [2 ..])
    sieve (p:xs) = [ x | x <- xs, x `rem` p /= 0 ]

    После старта интерпретатора hugs и загрузки в него этой программы достаточно вызвать функцию primes (без аргументов) и программа начнет печатать простые числа до тех пор, пока вы не прервете ее выполнение, нажав комбинацию клавиш Ctrl+C.

    Еще одной реализацией декларативного стиля является логическое программирование, основанное на логике предикатов, которое подробно рассматривается в следующей главе.

    Логика предикатов - это ветвь формальной логики, получившая развитие в XX веке. В логическом программировании основное внимание уделяется описанию структуры прикладной задачи, а не выработке предписаний компьютеру, что ему следует делать. Prolog (от французского PROgrammation LOGique, далее Пролог) - это наиболее известный язык логического программирования. Этот язык (наряду с функциональным языком Lisp) часто называют языком искусственного интеллекта - с его помощью решаются задачи создания экспертных систем и систем обработки естественных языков.

    Пример

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

    "Где-то в непроходимых джунглях, недалеко от города Ханоя, есть монастырь бога Брамы. В начале времен, когда Брама создавал Мир, он воздвиг в этом монастыре три высоких алмазных стержня и на один из них возложил 64 диска, сделанных из чистого золота. Он приказал монахам перенести эту башню на другой стержень (в соответствии с правилами, разумеется). С этого времени монахи работают день и ночь. Когда они закончат свой труд, наступит конец света."

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

    Поместите в файл с именем hanoi.pl следующий текст (символ % начинает комментарий, который не обязательно помещать в файл).

    % move(число_дисков, откуда, куда, через)
    move(1,X,Y,_) :-  
             write('Move top disk from '), %передвиньте верхний диск с
             write(X), write(' to '), 
             write(Y), nl. 
    move(N,X,Y,Z) :- 
             N>1, 
             M is N-1, 
             move(M,X,Z,Y), 
             move(1,X,Y,_), 
             move(M,Z,Y,X).

    (скачать файл hanoi.pl )

    % move(число_дисков, откуда, куда, через)
    move(1,X,Y,_) :- write('Move top disk from '),
                     write(X), write(' to '),  
                     write(Y), nl. 
    move(N,X,Y,Z) :- N>1, M is N-1, 
                     move(M,X,Z,Y),
                     move(1,X,Y,_), 
                     move(M,Z,Y,X).

    Запустите интерпретатор языка Пролог при помощи команды pl. После появления приглашения к работе (?- ) загрузите содержимое файла командой [hanoi]. (расширение файла указывать не нужно, а вот точка после закрывающей квадратной скобки необходима). Теперь, чтобы заставить Пролог решить задачу о перемещении трех дисков, введите следующий запрос:

    move(3,left,right,center).

    (не забудьте о точке в конце ввода). Ниже приводится порядок перемещения дисков, найденный этой программой.

    Для завершения работы с интерпретатором наберите команду halt. и нажмите Enter.

    Задания

  • Измените программу triads.hs так, чтобы не выводились одинаковые тройки чисел, такие как (3,4,5) и (4,3,5). Для этого введите дополнительное условие, например, x<y.
  • Получите решение головоломки "Ханойская башня" для четырех дисков.
  • Объектно-ориентированное программирование

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

    Практически все современные языки программирования, независимо от принадлежности к тому или иному стилю (директивному или декларативному), поддерживают концепцию ООП. Среди них C++, Java, Ruby и Haskell. Существуют и версии объектно-ориентированного Пролога.

    Можно сказать, что ООП - это моделирование объектов посредством иерархически связанных классов. При этом малозначащие детали объекта скрыты от нас, и если мы даем команду какому-то объекту, то он "знает", как ее выполнить. Фундаментальной концепцией в ООП является понятие обязанности или ответственности за выполнение действия.

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

    Классы представляются в виде иерархической древовидной структуры, в которой классы с более общими чертами располагаются в корне дерева, а специализированные классы и в конечном итоге индивидуумы располагаются в ветвях. На рисунке показана одна из возможных иерархий классов, включающая в себя собак Белку и Стрелку, кошку Мурку и утконоса Фросю.

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

    Итак, в основе ООП лежат три основных понятия:

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

    Наследование - это процесс, в результате которого один тип наследует свойства другого типа.

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

    Пример

    Для иллюстрации некоторых принципов ООП приведем небольшую программу на языке Ruby, который тоже поддерживает объектно-ориентированный стиль программирования.

    Поместите в файл с именем life.rb фрагмент кода, расположенный ниже.

    #!/usr/bin/ruby
    
    class Animal
       def breath #Дыхание
         print "все животные дышат: вдохнули и выдохнули\n" 
       end
    end
    
    class Cat<Animal
       def bark # Подать голос
          print "Mew Mew, я кошка. \n"
       end
    end
    
    class Dog
        def bark # Подать голос
          print "Bow Wow, я собака. \n"
        end
    end
    
    class Bird
       def lay_egg
         print "Яйцо снесено\n"
       end
       def fly
         print "Я птица, я лечу!!!\n"
       end
    end
    
    class Penguin<Bird
       def fly
          print "Пингвины не летают!!!\n"
       end
     end
    
    # Создаем объекты разных классов
    pochi = Dog.new
    pochi.bark
    
    tama = Cat.new
    tama.breath
    tama.bark
    
    macaw  = Bird.new
    macaw.lay_egg
    macaw.fly
    
    penguin  = Penguin.new
    penguin.lay_egg
    penguin.fly

    (скачать файл life.rb )

    Для запуска этой программы выполните в окне shell команду

    ruby life.rb

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

    Задания

  • Создайте еще одну кошку.
  • Объясните, кем является pochi и сможет ли он выполнить команду pochi.breath (дышать)? Если нет, то внесите соответствующие изменения в текст программы.
  • Измените код программы так, чтобы и птицы в ней тоже умели дышать.
  • Вернуться к учебному плану