От тетрадката към програмата
Миналия час броихте думи на ръка. Днес това го прави програмата — и то за текст с хиляди думи за части от секундата. Това е третата и последна програма на C# за годината (след съветника по правила и перцептрона) и влиза в оценката като тях: работи върху дадените примери, издържа граничните случаи, обяснявате всеки ред.
Програмата има две части: честотен речник — кои думи колко пъти, — и класификатор по списък от думи, който казва дали съобщението е с положителен, отрицателен или неутрален тон.
Стъпка 1: от текст към думи
Както на хартия: малки букви, махаме препинателните знаци, режем по интервалите. В C# трите неща са един ред:
char[] razdeliteli = { ' ', '.', ',', '!', '?', ';', ':', '-', '"' };
string[] dumi = tekst.ToLower().Split(razdeliteli, StringSplitOptions.RemoveEmptyEntries);ToLower()прави всички букви малки — работи и с кирилица.Split(razdeliteli, …)реже низа при всеки от изброените знаци. Така препинателните знаци изчезват заедно с интервалите.RemoveEmptyEntriesизхвърля празните парчета — иначе „лае.“ с интервал след точката би дало празна „дума“.
Стъпка 2: честотен речник
Dictionary<string, int>
Колекция „ключ → стойност“: за всеки ключ (дума) пази една стойност (брой). Търсенето по ключ е почти мигновено, независимо колко думи има вътре.
Dictionary<string, int> chestoti = new Dictionary<string, int>();
foreach (string duma in dumi)
{
if (chestoti.ContainsKey(duma)) chestoti[duma]++; // виждали сме я → +1
else chestoti[duma] = 1; // нова дума → 1
}След цикъла chestoti.Count е броят различни думи, а chestoti["котката"] — колко пъти се среща „котката“.
Подреждане по честота
Речникът няма ред. За „топ 5“ го подреждаме: първо по честота (намаляващо), а при равна честота — по азбучен ред, за да е изходът винаги един и същ:
var podredeni = chestoti
.OrderByDescending(p => p.Value) // по брой, най-честите отпред
.ThenBy(p => p.Key, StringComparer.Ordinal) // при равенство — по азбука
.Take(5); // само първите пет
foreach (var p in podredeni)
Console.WriteLine($"{p.Key,-10} {p.Value}");pе двойка ключ–стойност;p.Keyе думата,p.Value— броят.StringComparer.Ordinalсравнява по кода на буквите — за малки кирилски букви това съвпада с азбучния ред и не зависи от настройките на компютъра.{p.Key,-10}подравнява думата в поле от 10 знака — за да се четат колоните. Трябваusing System.Linq;.
Стъпка 3: класификатор по списък от думи
Идеята е от урок 5 — правила, написани от човек. Два списъка: положителни и отрицателни думи. За всяка дума в съобщението: ако е в първия списък, +1; ако е във втория, −1. Сборът е оценката на тона: над нула — положителен, под нула — отрицателен, нула — неутрален.
static string[] polozhitelni = { "страхотен", "харесах", "обичам", "супер", "благодаря" };
static string[] otricatelni = { "ужасен", "мразя", "скучен", "проблем", "зле" };
static int Tochki(string izrechenie)
{
int tochki = 0;
foreach (string duma in Dumi(izrechenie))
{
if (Array.IndexOf(polozhitelni, duma) >= 0) tochki++;
if (Array.IndexOf(otricatelni, duma) >= 0) tochki--;
}
return tochki;
}Array.IndexOf връща позицията на думата в списъка или −1, ако я няма. Това е цялото „разпознаване“ — сравнение на низове.
Списък от думи или обучен модел
Нашият класификатор е торба с думи плюс ръчно написани тегла: +1 за „супер“, −1 за „ужасен“, 0 за всичко останало. Спам филтърът в пощата ви прави абсолютно същата сметка — но теглата на думите не са писани от човек, а научени от милиони писма с етикет „спам“ / „не спам“.
Какво трябва да запомниш
ToLower()+Split(разделители, RemoveEmptyEntries)превръщат текста в масив от думи.Dictionary<string, int>брои думите:ContainsKey, после++или= 1.- Подреждане по честота, а при равенство по азбука (
Ordinal) дава еднозначен изход. - Класификаторът по списък събира +1 и −1 за думите от два списъка; знакът на сбора е тонът.
- Списъкът греши при отрицание, при непредвидени форми на думите и при ирония — това са граничните случаи.
- Обученият спам филтър е същата сметка с тегла, научени от примери — перцептрон върху торба с думи.