Разделяй и властвуй. Разделите вашу проблему на множество мелких проблем и решите каждую из них.
Первая проблема: превратить абзац в список слов.
Вам повезло, потому что вам не нужно беспокоиться о том, чтобы быть совершенным. На самом деле синтаксический анализ естественных языков, чтобы точно определить, что такое «слово», может быть очень трудным, но, честно говоря, вам, вероятно, все равно, имеет ли «лампочка» ту же семантику, что и «лампочка». Так как вы, в частности, ищете общие слова (сейчас, об этом позже), именно те, которые легко идентифицировать, потому что они часто встречаются.
Итак, разбери эту проблему дальше. Вы хотите список слов. Начните с получения строки с текстом:
StreamReader streamReader = new StreamReader(@"c:\survey.txt");
string source = streamReader.ReadToEnd();
Отлично, у вас есть строка. Теперь превратите это в массив слов. Поскольку вы, вероятно, хотите считать слова «лягушка» и «лягушка» одним словом, сделайте все строчными. Как все это сделать? Разбить строчную строку на основе пробелов, переносов, знаков табуляции и пунктуации:
char[] punctuation = new char[] {' ', '\n', '\r', '\t', '(', ')', '"'};
string[] tokens = source.ToLower().Split(punctuation, true);
Теперь рассмотрим вывод. Это было ужасно Мы забываем обо всем. Точки и запятые и двоеточия и точки с запятой и так далее. Выясните, какая пунктуация вас интересует, и добавьте ее в список.
Является ли ToLower правильным решением? Что насчет ToLowerInvariant? Есть моменты, когда вы хотите подчеркнуть это; это не один из них. Тот факт, что ToLower не обязательно канонизирует турецкую строчную букву I таким образом, что последовательные обходы, вряд ли отбросит вашу сводную статистику. Мы не собираемся здесь заострять точность. Если кто-то говорит «роскошная яхта», а кто-то говорит «роскошная яхта», первое может быть одним словом, если вы забудете разбить на дефисы. Какая разница? В любом случае, слова с переносами слов вряд ли будут в вашей первой десятке.
Следующая задача: подсчитать все вхождения каждого слова:
var firstPass = new Dictionary<string, int>();
foreach(string token in tokens)
{
if (!firstPass.ContainsKey(token))
firstPass[token] = 1;
else
++firstPass[token];
}
Отлично. Теперь у нас есть словарь, который отображает слова в целые числа. Проблема в том, что это задом наперед. То, что вы хотите знать, это то, что все слова имеют одинаковое количество вхождений. Словарь представляет собой последовательность пар ключ / значение, поэтому сгруппируйте его:
var groups = from pair in firstPass
group pair.Key by pair.Value;
Хорошо, теперь у нас есть последовательность групп слов, каждая из которых связана со своим количеством появлений. Заказать. Помните, что ключом группы является значение словаря, количество:
var sorted = from group in groups
orderby group.Key
select group;
А вы хотите первую сотню, скажем:
foreach(var g in sorted.Take(100))
{
Console.WriteLine("Words with count {0}:", g.Key);
foreach(var w in g)
Console.WriteLine(w);
}
И все готово.
Теперь, это действительно то, что вас интересует? Я думаю, что было бы более интересно искать необычные слова или пары слов. Если слова «яхта» и «гонки» появляются вместе много, не удивительно. Если «помидор» и «кетчуп» часто появляются вместе, неудивительно. Если «помидоры» и «гонки» начинают появляться вместе, то, возможно, происходит что-то примечательное.
Это требует гораздо более глубокого анализа; прочитайте теорему Байеса, если это то, что вас интересует.
Также обратите внимание, что при этом отслеживается необработанный счетчик слов, а не их частота - количество раз, которое они появляются на тысячу слов . Это также может быть интересной метрикой для измерения: не только сколько раз это слово появлялось, точка, но сколько раз оно появлялось в процентах от текста.