пятница, 5 июня 2009 г.

ИСКУССТВО ОПИСАНИЯ ПРОБЛЕМНОГО ПРОСТРАНСТВА В ИНФОРМАТИКЕ

ИСКУССТВО ОПИСАНИЯ ПРОБЛЕМНОГО ПРОСТРАНСТВА В ИНФОРМАТИКЕ

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

По тому, как человек умеет решать проблемы, когнитивные психологи оценивают его интеллектуальные возможности. Для описания вопросов, возникающих при решении проблем, в когнитивной психологии используются некоторые методы из раздела информатики под названием «искусственный интеллект». Суть этого раздела информатики заключается в том, чтобы попытаться заставить вести более или менее разумно вычислительные машины. Специалистами по искусственному интеллекту Алленом Ньюллом (психологом) и Гербертом Саймоном (программистом) разработана модель решения задач. В рамках модели решение описывается в терминах поиска в пространстве состояний (проблемном пространстве). Операторы решения задачи рассматриваются как замена одного состояния в этом пространстве на другое.
Проблемное пространство и поиск обычно поясняют, например, на задачах типа «игры в 15» (то, как она решается в программировании, рассмотрено в работе ). Рисуется дерево поиска и на его примере поясняется суть пространства состояний и операторов решения. С точки зрения специалиста по информатике, это обычная задача на поиск с возвратом. Однако с точки зрения психологии в этой ситуации не все очевидно. Так, например, исследуются схемы приобретения операторов решения проблемы, и выясняется то, что они могут быть приобретены с помощью открытий, воспроизведения примера решения проблемы или через прямую инструкцию. В результате выясняется, что обучение эффективнее всего строить через «показ на примере». Для формального представления операторов решения проблем психологи разработали ряд способов, одним из которых является система продукций. Она есть не что иное, как совокупность правил принятия решений при получении результата задачи. Структура правила: если <выполняются условия> то <выполнить действия>. Не вдаваясь в детали, отметим, что при разработке экспертных систем в системе программирования Prolog описание задач с использованием систем продукций является общепринятым приемом. Анализируя правила выбора продукций (операторов) человеком, психологи видят, что он склонен избегать при этом повторов и максимально уменьшать различия между текущим состоянием проблемы и целью. Наибольшая сложность возникает при анализе проблем, в которых правильное решение включает в себя шаги, увеличивающие различия между текущим состоянием и целью. Это положение обычно иллюстрируется (по результатам тестирования определенных групп людей) задачей о переправе трех хоббитов и трех орков на другой берег реки с помощью лодки для двух существ. При этом ни на одном берегу в любой момент времени орков не может быть больше хоббитов. Затем результаты (установлено, что больше чем в 50% случаев шло отклонение от правильных решений) обобщаются на другие виды деятельности человека, проводятся аналогии и вырабатываются соответствующие рекомендации.
В информатике. Поиск эффективных алгоритмов (цель – за минимальное время с использованием ограниченных ресурсов получить результат из исходных данных) отличительная особенность деятельности программиста. Занятие программированием, точнее, решением задач с использованием средств среды программирования, требует, постоянно требует конструирования проблемного пространства, в частности, путем абстрагирования. Приведем пример задачи по информатике .
Племя из M миссионеров и L людоедов находится по одну сторону реки, через которую необходимо переправиться. В распоряжении имеется одна лодка, которая может выдержать вес только K представителей этого племени (все имеют одинаковый вес). Кроме того, если в какой-то момент времени число людоедов станет больше числа миссионеров, миссионеры будут съедены независимо от того, на каком берегу или в лодке это случится. Написать программу поиска способа переправы этого племени, если он существует, состоящего из минимального количества перегонов лодки через реку.
Мы видим усиление (модификацию) задачи о трех хоббитах и трех орков, она сложнее, требуется не только выявить то, о чем пишут психологи, но и рассмотреть частные случаи, а также разработать решение для общего случая. Думается, что после исследования этой задачи, а это действительно мини-исследование, возможные отклонения текущего состояния от цели будут очевидны, и специалист по информатике не окажется в группе не прошедших тестирование.
Другой метод выбора операторов перехода из состояния в состояние в проблемном пространстве называют анализом средств и целей (более сложный вариант принципа уменьшения различия). Он заимствован из программы А. Ньюэлла и Г. Саймона (универсального решателя задач). Его суть не в отказе от отдельных операторов, а в их блокировке (временной), с последующим возвратом к ним. При этом генерируются новые подцели, устраняющие отдельные различия и приводящие, возможно, к снятию ранее заблокированных операторов.
Анализ задач в терминах поиска операторов перехода в проблемном пространстве не единственный аспект оценки интеллекта когнитивными психологами в умении решать проблемы. Важность правильной репрезентации проблемы или искусство описания проблемного пространства проблемы имеет большое значение в её последующем решении. Суть этого вопроса с точки зрения психологии в том, что испытуемые, получившие соответствующие знания, не умеют находить решение аналогичных задач в другой предметной области, другими словами, перевести задачу, сохраняя идентичность, в другое предметное поле. В информатике этот вопрос обсуждается несколько в другой терминологии, называемой этапом формализации задачи и выбором структур данных, соответствующих задаче. Этапу формализации при изучении информатики и, в частности, программирования, уделяется очень большое внимание, ибо от него, во многом, зависит успешность решения задачи. Программное решение – это модель, динамическая модель задачи, синтезирующая в единое целое структуру данных, а с их помощью описывается проблемное пространство, и операторы (действия) по переходу от одного состояния в другое (управление вычислительным процессом). Без формализации, под которой в информатике понимается строгое описание проблемы на каком-то языке (синтаксис и семантика которого автоматически проверяется компьютером) это невозможно сделать.
Искусство описания проблемного пространства основано на таком свойстве (качестве) развитого (и развивающего) интеллекта как абстрагирование. Приведем классическую трактовку этого понятия: «Теоретическое обобщение, позволяющее отразить основные закономерности исследуемого явления, изучать и прогнозировать новые, неизвестные закономерности. В качестве абстрактных объектов выступают целостные образования, содержащие непосредственное содержание человеческого мышления (понятия, суждения, умозаключения, законы, математические структуры и др.)» .
Уникальность информатики как области человеческой деятельности и, соответственно, как учебного предмета заключается в том, что в ней создан (и постоянно разрабатывается) инструментарий абстрагирования, причем в нем синтезирована работа со всеми её типами (в соответствии с философской классификацией): абстракции отождествления (или обобщения); абстракция идеализирующая (или идеализация); абстракция аналитическая (или изолирующая); абстракция актуальной бесконечности (отвлечение от принципиальной невозможности зафиксировать каждый элемент бесконечного множества, то есть бесконечные множества рассматриваются как конечные); абстракция потенциальной осуществимости (отвлечение от реальных границ наших возможностей, нашей ограниченности собственной конечности, то есть предполагается, что может быть осуществлено любое, но конечное число операций в процессе деятельности) . Уникальность информатики и в том, что тот инструментарий, о котором мы говорим, позволяет строить иерархические системы абстракций, проверять правильность работы абстракций (в динамике), итерационно возвращаться к первоначальным этапам построения абстракций и так до тех пор, пока исследуемое явление (проблема) не становится понятным (проблема будет решена). Этим инструментарием в информатике являются системы программирования.
Необходимо отметить, что системы программирования возникали не сами по себе, не просто как результат некого хотения специалистов индустрии информационных технологий, а как ответ на запрос, на требование общества к этой индустрии по решению проблем с использованием компьютера. В развитии систем программирования можно выделить ряд ключевых идей.
Одной из таких идей является идея типов данных (типизации). Оформившись примерно 1959-1961 годы прошлого века она последовательно развивалась до создания в системах программирования аппарата конструирования сложных типов, отвечающих (соответствующих) решаемой проблеме. Новый виток развития идеи наступил после осознания того, что программа есть некая цельность, получаемая в результате синтеза в единое целое данных и управления обработкой этих данных (управления вычислительным процессом). В результате появился «объект», как конструкция, объединяющая в единое целое данное и действие с этими данными, как «кирпичик» системы обработки данных (программы), решающей поставленную задачу. В объектно ориентированных системах программирования, как неком отрезке спирали развития систем программирования, а именно о них идет речь, идея получила новое «звучание». Однако индустрия информационных технологий, решающая все более сложные проблемы практики, не остановилась в развитии, процесс продолжается.
Главным для нас в данной работе является понимание того, что типы данных (типизация), объекты и так далее являются, выражаясь философским языком, инструментом абстрагирования и специалист по информатике, решая проблему, в частности, путем абстрагирования, конструирует некое проблемное пространство задачи, в котором она имеет изящное, простое, эффективное и надежное решение.
Для доказательности изложения по утверждению тезиса о том, что искусство описания проблемного пространства задачи является ключевой компетенцией специалиста по информатике, приведем фрагменты методического разбора двух конкретных задач с международной олимпиады школьников по информатике 2003 года, заостряя внимание при этом (методические отступления) на те элементы профессионализма, которые приводят к их решению. Тем самым мы покажем, что даже на школьном уровне, требования к навыкам и умениям по абстрагированию, не к отдельным его типам, а в целом, при конструировании проблемного пространства задачи достаточно высокие.
Задача. Компания Racine Business Networks (RBN) подала в суд на компанию Heuristic Algorithm Languages (HAL), утверждая, что HAL использовала исходный код из RBN UNIX и внесла его в открытый код операционной системы HALinux.
Как RBN, так и HAL используют язык программирования, в котором каждый оператор располагается на отдельной строке и имеет вид:
STOREA = STOREB + STOREC
(STOREA, STOREB и STOREC – имена переменных). Каждый оператор записывается следующим образом: имя первой переменной начинается с первой позиции строки, затем – пробел, знак равенства, пробел, имя второй переменной, пробел, знак сложения, пробел и имя третьей переменной. Одно и то же имя переменной может встречаться в строке более одного раза. Имена переменных имеют длину от 1 до 8 символов и состоят из заглавных латинских ASCII букв («A»… «Z»).
Утверждается, что HAL скопировала последовательность строк программы прямо из исходного кода RBN с минимальными изменениями.
• Чтобы скрыть свое преступление, HAL изменила имена некоторых переменных. Точнее, HAL взяла последовательность строк из программы RBN и для каждой переменной в ней изменила ее имя. При этом новое имя переменной может совпасть со старым. После переименования никакие две различные переменные не могут называться одинаково.
• HAL могла изменить в некоторых строках порядок имен переменных в правой части оператора присваивания. Например, оператор вида
STOREA = STOREB + STOREC
мог быть изменен так:
STOREA = STOREC + STOREB
• Утверждается также, что HAL не изменила порядок, в котором строки кода следуют в исходном тексте программы.
Необходимо по заданным исходным кодам программ RBN и HAL найти самую длинную последовательность подряд идущих строк из программы HAL, которую можно получить из последовательности подряд идущих строк программы RBN с помощью указанных выше преобразований. Заметьте, что найденная последовательность в коде HAL и соответствующая ей последовательность в коде RBN не обязательно начинаются в строках с одинаковыми номерами.
Входные данные: code.in
• Первая строка входного файла содержит два разделенных пробелами целых числа r и h (1  r  1000; 1  h  1000). r задает число строк в коде программы RBN, а h – число строк в коде программы HAL.
• Последующие r строк содержат программу RBN.
• Последующие h строк содержат программу HAL.
Пример входных данных:
4 3
RA = RB + RC
RC = D + RE
RF = RF + RJ
RE = RF + RF
HD = HE + HF
HM = HN + D
HN = HA + HB
Выходные данные: code.out. Выходной файл должен содержать одну строку с единственным целым числом – длиной самой длинной последовательности идущих подряд строк, которую HAL могла скопировать с изменениями у RBN.
Пример выходных данных:
2
Строки с 1-й по 2-ю из программы RBN совпадают со строками со 2-й по 3-ю из программы HAL при условии, что в программе RBN выполнены следующие замены имен переменных: RA  HM, RB  D, RC  HN, D  HA, RE  HB. Решения с тремя и более соответствующими строками не существует.
Фрагмент методического разбора.
Пусть есть не две программы, как в формулировке задачи, а две строки (s1 и s2), и требуется найти в них общую часть (подстроку) наибольшей длины. Подстрока определяется из подряд идущих символов. Мы не будем рассматривать специальных достаточно эффективных алгоритмов Нудельмана-Вунша , Ханта-Зиманского , а ограничимся самым простым решением. Начиная с позиции li строки s1 и позиции lj строки s2 (относительный сдвиг строк), осуществляем посимвольной сравнение (текущий номера символов: ri в строке s1 и rj – в s2). Если s1[ri] равно s2[rj], то увеличиваем на единицу значения ri и rj, а также изменяем количество совпадений, иначе увеличиваем на единицу значения li и lj. Последовательно перебирая пары (li,lj): (1,1), (1,2), …, (1, Length(s2)), а затем (2,1), (3,1), …, (Length(s1),1), находим результат (Length(s) – количество символов в строке s).
Пример. Пусть даны две строки: s1=abxxxxc и s2=dxxxxe. При (li,lj)=(1,1) результат равен трем, при (li,lj)=(1,2) – двум, при (li,lj)=(2,1) – четырем. Приведем для наглядности изложения фрагмент логики, выполняющей сравнение строк, с заданным относительным сдвигом.
Function Com(li,lj:Integer):Integer;
Var ri,rj,res,t1,t2:Integer;
Begin
res:=0;ri:=0;rj:=0;
t1:=Length(s1);t2:=Length(s2);
While (ri<=t1) And (rj<=t2) And (li<=t1) And (lj<=t2) Do Begin
ri:=Max(ri,li);rj:=Max(rj,lj);{Max – функция вычисления максимального из двух чисел.}
If s1[ri]=s2[rj] Then Begin ri:=ri+1;rj:=rj+1;res:=Max(res,ri-li);End
Else Begin li:=li+1;lj:=lj+1;End;
End;
Com:=res;
End;
Пришло время сформулировать основной вопрос – а можно ли преобразовать тексты программ так, чтобы логика поиска общей подстроки двух строк стала работоспособна и в нашей задаче? Конечно, она претерпит некие изменения, но суть – основная идея останется неизменной.
Методическое отступление. Мы упростили задачу, чтобы «вычленить» суть, а затем абстрагируемся, строим проблемное пространство задачи, отвлекаясь от не существенных деталей!
Вчитаемся в формулировку задачи и попытаемся осознать, насколько важны, имеют принципиальное значение, сами имена переменных. Какая нам разница имя «Петя» или «Ваня». Главное – в какой части оператора, левой или правой, оно встречается, и было ли оно ранее в тексте программы. И если да (было), то опять же где – в левой или правой части оператора. Заметим, что если имя встречается в правой части, то, следуя формулировке задачи, не имеет значения первым оно записано, или вторым. Поэтому каждое имя мы будем характеризовать двухкомпонентным вектором (num_left,num_right), где num_left – номер последнего предшествующего оператора, в левой части которого записано данное имя, а num_right – номер последнего предшествующего оператора, в правой части которого записано данное имя. Если имя встречается первый раз, то вектор равен (0,0). Сформируем для первой программы матрицу p[1..r.1..3], элементами которой являются значения векторов, а для второй программы – q[1..h,1..3]. Они, для примера из формулировки задачи, имеют вид:
и . Мы формируем эти матрицы только по тексту самих программ (при построчном вводе каждого оператора, если говорить о технической реализации) – обращаться к тексту другой программы нет необходимости. А после формирования матриц и сами тексты программ больше не требуются! Смысл не улавливается? Добавим в текст второй программы из формулировки задачи еще две строки. Он имеет вид:
HD = HE + HF
HM = HN + D
HN = HA + HB
A = A + B
HB = A + A
а матрица . Вас не удивляет некая похожесть матрицы p и нового варианта матрицы q (точнее её части со второй по пятую строки)? Ответом задачи в этом случае является значение четыре – «самая длинная последовательность подряд идущих строк из программы HAL, которую можно получить из последовательности подряд идущих строк программы RBN с помощью указанных преобразований» равна четырем.
Методическое отступление. Момент «эврика» наступил. Мы представили исходные данные как некие объекты (вектора, матрицы) в проблемном пространстве. Его надо закрепить. Предлагается написать школьнику произвольные тексты программ. Составить для них матрицы p и q. Программу разрабатывать не следует – это отдельное задание (чисто техническое для нашего уровня обсуждения). Найти ответ задачи.
Вернемся к обсуждению задачи. Пусть матрицы p и q сформированы. Как изменится функция Com для данного случая – нам необходимо осуществлять не посимвольное сравнение строк, а построковое сравнение матриц с учетом их относительного сдвига.
Во-первых, требуется функция сравнения двухкомпонентных векторов a и b с учетом их местоположения в матрицах p и q. Один из простых способов её реализации имеет вид:
Function Eq(a,b:TVar):Boolean; { Тип данных – TVar=Array [1..2] Of Integer; }
Var i:Integer;
pp:Boolean;
Begin
pp:=True;
For i:=1 To 2 Do
pp:=pp And (((a[i] Eq:=pp;
End;
Во-вторых, в функции Com требуется реализовать те сравнения, которые указаны в формулировке задачи. С этой целью введем две логические переменные f1 и f2. Значение f1 – это результат сравнения левых частей операторов программ или f1:=Eq(p[ri,1],q[rj,1]), а значение f2 – правых, f2:=(Eq(p[ri,2],q[rj,2]) And Eq(p[ri,3],q[rj,3])) Or (Eq(p[ri,2],q[rj,3]) And Eq(p[ri,3],q[rj,2])). В результате сравнение символов «If s1[ri]=s2[rj]» в тексте Com заменяется проверкой условия истинности значений переменных f1 и f2 – «If f1 And f2».
Методическое отступление. Что мы делаем? Мы обобщаем (один из видов абстрагирования) простые факты (поиск общей части двух строк) до работы с нашими абстрактными объектами в проблемном пространстве задачи. Но если бы проблемное пространство было не построено, то и обобщать было нечего.
Задача. В стаде фермера Джона находятся n пронумерованных от 1 до n (1  n  50) и похожих коров. Когда фермер Джон ставит корову в стойло, он должен знать, какую корову он туда ставит.
Коровы различаются по p признакам, пронумерованным от 1 до p (1  p  8), каждый из которых имеет по три возможных значения. Например, цвет метки на ухе коровы может быть желтым, зеленым или красным. Значения каждого признака определяются одной из букв «X», «Y» или «Z». Любая пара коров фермера Джона отличается по крайней мере одним признаком.
Напишите программу, которая по заданным признакам коров поможет фермеру Джону определить номер коровы, которую он ставит в стойло. Ваша программа может задать Джону не более 100 вопросов вида: “Принадлежит ли значение некоторого признака T у коровы некоторому множеству значений S?”.
Задайте как можно меньше вопросов для определения номера коровы.
Входной файл признаков: guess.in
• Первая строка входного файла содержит два целых числа n и p, разделенных пробелом.
• Каждая из последующих n строк описывает признаки коровы, используя p букв, разделенных пробелами. Первая буква каждой строки – значение признака 1 и так далее. Вторая строка во входном файле описывает корову с номером 1, третья строка – корову с номером 2 и т.д.
Пример входного файла признаков:
4 2
X Z
X Y
Y X
Y Y
Интерактивность: стандартный ввод и стандартный вывод. Шаг «вопрос/ответ» осуществляется через стандартный ввод и стандартный вывод. Ваша программа задает вопрос о корове посредством записи в стандартный вывод строки следующего вида: буква «Q», номер признака, значения признака (одно или более), разделенные пробелами. Например, строка «Q 1 Z Y» соответствует вопросу: «Имеет ли первый признак коровы значение «Z» или «Y»?». Признак может быть целым числом в пределах от 1 до p. Значения признака должны быть только «X», «Y» или «Z» и не должны повторяться в одной строке.
После задания вопроса ваша программа должна читать одну строку, содержащую одно из целых чисел – 0 или 1. Число 1 обозначает, что корова обладает одним из указанных значений признака. Число 0 означает, что ни одним из указанных значений признака корова не обладает.
Последняя строка вывода программы должна содержать букву «C», пробел и одно число (номер коровы).
Пример диалога: (для вышеуказанного примера)
Ввод Вывод Комментарий
Q 1 X Z
0 Может быть корова 3 или корова 4
Q 2 Y
1 Должна быть корова 4!
C 4
Завершение работы программы
Фрагмент методического разбора.
Самый простой вариант решения последовательно задавать вопросы типа: равен ли признак с номером i значению X, если ответ «да», то признак определен, иначе следует вопрос – равен ли признак с номером i значению Y. Очевидно как то, что потребуется в худшем случае 2p вопрос, так и то, что это решение не наберет полный балл. Требуется задавать «как можно меньше вопросов для определения номера коровы».
Второй очевидный факт заключается в том, что вопрос типа «Q 1 Z Y» равносилен вопросу «Q 1 X». Действительно, ответ «нет» на первый вопрос означает равенство признака значению X, ответ «да» – равенство Z или Y. Для второго вопроса имеем: ответ «да» – признак равен X, ответ «нет» – признак равен Z или Y. Таким образом, можно ограничиться вопросами второго типа.
Мы не стронемся с места до тех пор, пока не определим проблемное пространство задачи и логику перехода из одного состояния в другое в этом пространстве! Первым шагом в этой работе является кодировка признаков и то, как вопросы и ответы на них отражаются в изменении кодировки. Признак имеет три значения. Следовательно, трех бит достаточно для представления их в памяти. Считаем, что «0» (в бите записан ноль) является допустимым для значения признака, а «1» говорит о невозможности равенства признака этому значению. Последовательность «000» означает, что признак может принимать значения X, Y и Z, а «001» – только Y и Z. Последовательность из 3*p бит служит для описания всех признаков и определяет конкретное состояние в пространстве решения задачи. Если задано t признаков, то последовательность имеет вид PtPt-1…P1, другими словами – первый признак, описывается тремя младшими битами последовательности, затем второй признак и так далее. В этом случае конструкция
For i:=0 To p-1 Do
For j:=0 To 2 Do … обеспечивает побитовый просмотр (начиная с младших разрядов) последовательности.
Методическое отступление. Идея конструирования проблемного пространства есть, осталось её развить и продумать детали перехода из одного состояния в другое.
Обратимся к примеру в формулировке задачи. Описание конечных состояний для всех коров (первая строка таблицы), следующих из формулировки задачи, представлено в табл. 1.
Таблица 1
1 2 3 4
Z Y X Z Y X Z Y X Z Y X Z Y X Z Y X Z Y X Z Y X
0 1 1 1 1 0 1 0 1 1 1 0 1 1 0 1 0 1 1 0 1 1 0 1
P2 P1 P2 P1 P2 P1 P2 P1
Начальным состоянием является последовательность «000000». Пусть задан вопрос «Q 1 X» – равен ли первый признак коровы значению X. Если ответ «нет», то из начального состояния осуществляется переход в состояние «000001», а если «да», то – «000110». Другими словами при ответе «нет» это значение признака запрещается, а остальные не изменяются, ответ «да» запрещает другие значения признаков. Формализованная запись логики перехода из одного состояния в другое имеет вид:
Function ChangeY(Const s,i,j:Integer):Integer;{s – номер состояния, i – номер признака, j – номер значения признака.}
Var t,c:Integer;
Begin
c:=s;
For t:=0 To 2 Do {Добавляем «1» в оставшиеся нулевые значения признака.}
If (t<>j) And ((c And (1 ShL (i*3+t)))=0) Then c:=c+ (1 ShL (i*3+t)); {Командой ShL осуществляется сдвиг единицы на заданное количество разрядов.}
ChangeY:=c;
End;
Function ChangeN(Const s,i,j:Integer):Integer;
Begin
ChangeN:=s+1 ShL (i*3+j); {Запрещаем это значение признака.}
End;
Методическое отступление. Целесообразно, на наш взгляд, сделать паузу в обсуждении и выполнить небольшой тренинг, по «ручному» просчету перехода из одного состояния в другое при разных вариантах ответов на вопросы. При этом следует состояния описывать как в двоичном представлении, так и в десятичном. Тем самым мы закрепляем понимание того, что есть «проблемное пространство».
Вторым шагом работы является описание всего проблемного пространства (количество состояний 23*p). Достаточно ли для описания одного одномерного массива? Для того, чтобы ответить на этот вопрос, требуется ответить на другой вопрос – а какие бывают состояний? Например, недопустимые. Так состояние 000011 (310) является недопустимым (недостижимым) для примера из формулировки задачи – первый признак коров не может быть равен не X и не Y. Состояние 010100 (2010) является допустимым для первой и третьей коров и недопустимым для второй и четвертой коров (второй признак у коров не может быть не равен единице). Наконец, есть состояния допустимые только для одной коровы. Так в примере состояние 011100 (2810) допустимо только для первой коровы.
Введем два массива w[0.. 23*p - 1] и d[0.. 23*p - 1]. При инициализации элементам d присваиваем значение 0, а элементам w – -1. На начальной стадии «разметим» проблемное пространство:
,
.
Например, w[9]=4, d[9]=0 – состояние 001001 (910) допустимо только для коровы с номером 4; w[12]=1, d[12]=255 – состояние 001100 (1210) допустимо для нескольких коров (больше одной), в частности для коровы с номером 1; w[15]=255, d[15]=0 – состояние 001101 (1510) недопустимо ни для одной коровы.
Как разметить проблемное пространство? Возьмем признаки первой коровы XZ и зафиксируем нули в соответствующих позициях – 0****0. В позициях, отмеченных символом «*» допустима запись как 0, так и 1. Другими словами, нам требуется сгенерировать все последовательности, у которых в позициях, отмеченных «*», записаны 0 или 1. И это необходимо сделать для каждой коровы. В табл. 2 приведены допустимые последовательности для всех коров.
Таблица 2
1 2 3 4
s 011110 s 101110 s 110101 S 101101
2 000010 2 000010 1 000001 1 000001
4 000100 4 000100 4 000100 4 000100
6 000110 6 000110 5 000101 5 000101
8 001000 8 001000 16 010000 8 001000
10 001010 10 001010 17 010001 9 001001
12 001100 12 001100 20 010100 12 001100
14 001110 14 001110 21 010101 13 001101
16 010000 32 100000 32 100000 32 100000
18 010010 34 100010 33 100001 33 100001
20 010100 36 100100 36 100100 36 100100
22 010110 38 100110 37 100101 37 100101
24 011000 40 101000 48 110000 40 101000
26 011010 42 101010 49 110001 41 101001
28 011100 44 101100 52 110100 44 101100
30 011110 46 101110 53 110101 45 101101
Анализируя данные табл. 2, видим, что некоторые состояния допустимы только для одной из коров. Назовем их конечными. В табл. 3 приводятся конечные состояния для каждой коровы.
Таблица 3
S 1 s 2 s 3 S 4
18 010010 34 100010 17 010001 9 001001
22 010110 38 100110 21 010101 13 001101
24 011000 42 101010 48 110000 41 101001
26 011010 46 101110 49 110001 45 101101
28 011100 52 110100
30 011110 53 110101
Примечание. Для удобства работы опишем признаки коров с помощью числовой матрицы (r[1..n,1..p]), формируемой при вводе данных. Для примера из формулировки задачи она имеет вид: . Значение 0 соответствует X, 1 – Y и 2 – Z.
Методическое отступление. Для работы в нашем проблемном пространстве мы вводим и определяем абстрактный объект – матрица.
Одним из вариантов выполнения действий по начальной разметке пространства состояний задачи может быть следующим. В процедуре решения задачи организуем цикл по i (коровам) с вызовом рекурсивной процедуру разметки For i:=1 To n Do GenEq(0,0,0). При s=0 (в начальный момент времени) может быть задан любой из 3*p вопросов. Вопрос опишем парой (a, b), где a – номер признака, а b – номер вопроса. Тогда пары (0, 1), (0,2), (0,3), …, (p-1,1), (p-1,2), (p-1,3) соответствуют состоянию s=0 и мы их назовем начальными. Для нашего примера они равны (0,1), (0,2), (0,3), (1,1), (1,2), (1,3).
Procedure GenEq(a,b,s:Integer); {a – номер признака, b – номер значения признака, s – номер состояния. Начинаем для каждой коровы с нулевого состояния.}
Begin
If b=3 Then Begin a:=a+1; b:=0; End; {Если значение признака равно трем, то переходим к следующему признаку.}
If (a GenEq(a,b+1,s); {Переход к следующему значению признака.}
If r[i,a+1]<>b then GenEq(a,b+1,s+(1 ShL (a*3+b))); {Если у коровы с номером i, признак а не равен значению b, то можно выполнить переход в другое состояние. Его номер вычисляется путем сдвига единицы на число разрядов, соответствующих значениям a и b. Плюс единица выравнивает нумерацию признаков (с нуля или с единицы) в матрице r и в процедуре GenEq.}
End
Else
If w[s]=255 Then w[s]:=i Else d[s]:=255;{Если в состоянии s не были (w[s]=255), то присваиваем значение номера коровы, а иначе помечаем, что состояние s допустимо для нескольких коров.}
End;
Методическое отступление. Появилась замечательная работа, способствующая пониманию задачи, – просчитать разметку проблемного пространства как с использованием процедуры GenEq, так и без неё. Конечно, выходить за значение p>2 (количество признаков) неразумно, но значения признаков можно изменять.
После разметки пространства мы имеем начальное, конечные, промежуточные и недопустимые (они отмечены в w значением 255) состояния. Для рассматриваемого примера в табл. 3 даны конечные состояния (в w записан номер коровы, а в d значение 0). Если в табл. 2 убрать конечные состояния, то в ней останутся только промежуточные состояния. Их для нашего примера 17 (различных). Промежуточные состояния отмечены в массиве d значением 255 (после разметки). Осталось просчитать для каждого промежуточного состояния в зависимости от заданных вопросов возможные переходы в другие состояния и зафиксировать в соответствующем элементе массива d минимальное количество вопросов в худшем случае. Другими словами, требуется составить таблицу переходов для всех промежуточных состояний. Элементом массива w для состояния s является код признака и его значение, они определяют новое состояние. Эта работа выполняется с помощью следующей рекурсивной функции.
Function Calc(s:Integer):Integer; {Находим минимальное количество вопросов, необходимых для достижения одного из конечных состояний из состояния s.}
Var q,i,j,s1,s2:Integer;
Begin
If d[s]=255 Then Begin {Конечные и недопустимые состояния не рассматриваем. Нас интересуют только промежуточные.}
For i:=0 To p-1 Do {Цикл по признакам.}
For j:=0 To 2 Do {Цикл по значениям признака.}
If s And (1 ShL (i*3+j))=0 Then Begin {Если значение j признака i в состоянии s допустимо, то моделируем переходы из него при ответах «да» и «нет».}
s1:=ChangeY(s,i,j); {Переход в состояние s1 при ответе «да».}
s2:=ChangeN(s,i,j); {Переход в состояние s2 при ответе «нет».}
If (s1<>s) And (s2<>s) Then Begin
q:=max(Calc(s1),Calc(s2)); {Находим максимальное количество вопросов (худший вариант задания вопросов).}
If (q+1) d[s]:=q+1; {Количество вопросов.}
w[s]:=(i+1)*10+j; {Код – из номера признака и его значения (в десятичном виде), при котором из состояния s реализуется вариант игры с наименьшим количеством вопросов.}
End;
End;
End;
End;
Calc:=d[s]
End;
Практически мы закончили обсуждение решения задачи, обеспечивающего сформулированные в условии требования.
Методическое отступление. Промоделировать работу проверяющей программы путем организации игры компьютера с человеком можно с помощью следующей процедуры.
Procedure Solve;
Var s,c,i,j:Integer;
Begin
<элементам массива d присвоить значение 0>;
< элементам массива w присвоить значение 255>;
For i:=1 To n Do GenEq(0,0,0); {Начальная разметка.}
Calc(0); {Формирование таблицы переходов.}
s:=0;
While d[s]<>0 Do Begin
WriteLn('Q ',w[s] Div 10,' ',CnvI[w[s] Mod 10]); {Задаем вопрос. Массив констант CnvI имеет вид: CnvI:Array[0..2] Of Char=(’X’,’Y’,’Z’);}
ReadLn(c); {Человек отвечает.}
If c=0 Then s:=ChangeN(s,w[s] Div 10-1,w[s] Mod 10)
Else s:=ChangeY(s,w[s] Div 10-1,w[s] Mod 10); {Переход в другое состояние. Значение «-1» требуется для выравнивания нумерации.}
End;
WriteLn('C ',w[s]);
End;
Если в d[s] хранится наименьшее количество вопросов для перехода из состояния s в одно из конечных состояний, то структура значения элемента массива w, хоть и просматривается, но возможно не до конца ясна. Её можно пояснить с помощью рис. 1, на котором приводятся три из шести возможных вопросов для начального состояния. Рядом с вершинами в угловых скобках приводится номер признака и номер его значения, представленные в вопросе, или, если это конечное состояние, номер коровы.
Резюмируя, можно сказать, что в целом, искусство описания проблемного пространства является ключевой компетенцией специалиста по информатике любого уровня, начиная со школьного. С точки зрения когнитивной психологии и философии эта компетенция характеризует развитые интеллектуальные возможности человека.