От хартия към програма
Два часа бяхте машината за извод. Днес я пишете. Всичко, което ви трябва, го знаете от Модул 1: условия, методи, масиви и един прост клас. Няма нищо „изкуствено интелигентно“ в кода — и точно това е поуката: първите AI системи са били обикновени програми с добре подредено знание.
Това е първата от трите програми на C#, които се оценяват през годината. Критериите са три: работи върху дадените примери, издържа граничните случаи от урока и можете да обясните всеки ред.
Правилата като условни конструкции
Най-прекият превод на дърво в код: всеки въпрос е if, всеки отговор „да“ влиза в блока, „не“ — в else. Първо ни трябва метод, който задава въпрос и връща отговора като bool:
static bool Pitay(string vapros)
{
Console.WriteLine(vapros + " (да/не)");
string otgovor = Console.ReadLine();
return otgovor == "да";
}После дървото се преписва възел по възел. Забележете, че въпросът „Вали ли?“ се задава само ако разстоянието е под 2 км — програмата пита само за това, което ѝ трябва, точно както извеждането назад от урок 5:
if (Pitay("Разстоянието е под 2 км?"))
{
if (Pitay("Вали ли?")) savet = "автобус";
else savet = "пеша";
}
else
{
if (Pitay("Има ли пряк автобус или метро?")) savet = "градски транспорт";
else if (Pitay("Бързате ли?")) savet = "такси";
else savet = "колело";
}- Всяко листо на дървото е едно присвояване на
savet. - Дълбочината на дървото е дълбочината на влагането — три нива тук.
- Нов въпрос = нов
ifвътре в правилния клон. Лесно за пет правила, мъчително за петдесет.
Обосновката „защото…“
Съветник, който казва само „такси“, не е по-добър от монета. Заедно със съвета пазим и обосновка — низ, който описва защо. В пример 1 тя се записва в същото листо, в което се записва и съветът:
savet = "такси";
zashto = "разстоянието е голямо, няма пряка линия и бързате";Добрата обосновка:
- съдържа само фактите, от които наистина зависи изводът — не всички зададени въпроси;
- изброява ги в реда, в който са проверени — това е пътят в дървото;
- може да се прочете от потребителя без да гледа кода.
Правилата като данни
Урок 5 обеща, че базата от знания може да е отделена от програмата. Ето как: едно правило е обект с масив от условия и извод.
class Pravilo
{
public string[] Uslovia; // всички трябва да са сред фактите
public string Izvod;
public Pravilo(string[] uslovia, string izvod)
{
Uslovia = uslovia;
Izvod = izvod;
}
}Фактите са низове в масив: „иглички“, „жълъд“… Машината за извод е един цикъл — минава правилата по ред и спира на първото, чиито условия всички са налице:
foreach (Pravilo p in pravila)
{
bool srabotva = true;
foreach (string u in p.Uslovia)
if (!Ima(u)) srabotva = false;
if (srabotva)
{
Console.WriteLine("Извод: " + p.Izvod);
Console.WriteLine("Защото: " + string.Join(" и ", p.Uslovia));
return;
}
}
Console.WriteLine("Не мога да преценя — нямам правило за тези факти.");Обосновката тук идва без усилие — това са условията на сработилото правило. А Ima(u) е просто Array.IndexOf(fakti, u) >= 0.
| Правила като if | Правила като данни | |
|---|---|---|
| Ново правило | нов if на правилното място, ново компилиране | нов ред в масива |
| Кой може да го добави | само програмист | и специалист, ако правилата се четат от файл |
| Ред на въпросите | вграден в дървото — пита само нужното | събира всички факти, после разсъждава |
| Обосновка | пише се ръчно във всяко листо | условията на сработилото правило |
Гранични случаи
Програмата от пример 1 приема за „не“ всичко, което не е точно „да“: „Да“, „ да“, „yes“, „може би“. Потребителят няма как да разбере защо съветът е грешен. Граничните случаи, които съветникът трябва да издържа:
- главни букви и интервали — „ДА“, „Да “ — нормализирайте с
Trim()иToLower(); - отговор извън очакваното — „може би“ — кажете, че не разбирате, и питайте пак;
- свършил вход —
ReadLine()връщаnull— приемете „не“ вместо да гръмнете; - нито едно сработило правило — кажете го честно, не връщайте последното правило;
- две сработили правила — първото печели; проверете дали редът е този, който искате.
static bool Pitay(string vapros)
{
while (true)
{
Console.WriteLine(vapros + " (да/не)");
string otgovor = Console.ReadLine();
if (otgovor == null) return false;
otgovor = otgovor.Trim().ToLower();
if (otgovor == "да") return true;
if (otgovor == "не") return false;
Console.WriteLine("Не разбрах „" + otgovor + "“ — отговорете с да или не.");
}
}Какво трябва да запомниш
- Дърво → вложени
if/else: всеки въпрос е условие, всяко листо — присвояване на съвет и обосновка. - Правила като данни: клас с масив от условия и извод; масив от правила; цикъл, който спира на първото сработило.
- Обосновката съдържа само фактите, от които зависи изводът — при правилата като данни идва наготово.
- По-конкретните правила стоят преди по-общите — цикълът спира на първото.
- Гранични случаи: главни букви и интервали, непознат отговор, свършил вход, нито едно сработило правило.