Минимизировать счетчик строки LINQ - PullRequest
1 голос
/ 28 октября 2010

Продолжение ответа на предыдущий вопрос .

Есть ли способ еще больше уменьшить это, избегая внешнего вызова String.Split?Целью является ассоциативный контейнер {token, count}.

string src = "for each character in the string, take the rest of the " +
    "string starting from that character " +
    "as a substring; count it if it starts with the target string";

string[] target = src.Split(new char[] { ' ' });

var results = target.GroupBy(t => new
{
    str = t,
    count = target.Count(sub => sub.Equals(t))
});

Ответы [ 4 ]

4 голосов
/ 28 октября 2010

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

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

string src = "for each character in the string, take the rest of the " +
             "string starting from that character " +
             "as a substring; count it if it starts with the target string";

var results = src.Split()               // default split by whitespace
                 .GroupBy(str => str)   // group words by the value
                 .Select(g => new
                              {
                                  str = g.Key,      // the value
                                  count = g.Count() // the count of that value
                              });

// sort the results by the words that were counted
var sortedResults = results.OrderByDescending(p => p.str);
3 голосов
/ 28 октября 2010

Хотя метод Regex в 3-4 раза медленнее, он, возможно, более точен:

string src = "for each character in the string, take the rest of the " +
    "string starting from that character " +
    "as a substring; count it if it starts with the target string";

var regex=new Regex(@"\w+",RegexOptions.Compiled);
var sw=new Stopwatch();

for (int i = 0; i < 100000; i++)
{
    var dic=regex
        .Matches(src)
        .Cast<Match>()
        .Select(m=>m.Value)
        .GroupBy(s=>s)
        .ToDictionary(g=>g.Key,g=>g.Count());
    if(i==1000)sw.Start();
}
Console.WriteLine(sw.Elapsed);

sw.Reset();

for (int i = 0; i < 100000; i++)
{
    var dic=src
        .Split(' ')
        .GroupBy(s=>s)
        .ToDictionary(g=>g.Key,g=>g.Count());
    if(i==1000)sw.Start();
}
Console.WriteLine(sw.Elapsed);

Например, метод Regex не будет считать string и string, как две отдельные записи, ибудет корректно токенизировать substring вместо substring;.

EDIT

Прочитайте ваш предыдущий вопрос и поймите, что мой код не совсем соответствует вашей спецификации.Несмотря на это, он все еще демонстрирует преимущество / стоимость использования Regex.

1 голос
/ 28 октября 2010

Избавление от String.Split не оставляет много вариантов на столе.Один вариант - Regex.Matches, как показал Спендер , а другой - Regex.Split (что не дает нам ничего нового).

Вместо группировки вы можете использовать любой из этих подходов:

var target = src.Split(new[] { ' ', ',', ';' }, StringSplitOptions.RemoveEmptyEntries);
var result = target.Distinct()
                   .Select(s => new { Word = s, Count = target.Count(w => w == s) });

// or dictionary approach
var result = target.Distinct()
                   .ToDictionary(s => s, s => target.Count(w => w == s));

Вызов Distinct необходим, чтобы избежать дублирования элементов.Я пошел дальше и расширил символы, чтобы разделить их, чтобы получить настоящие слова без знаков препинания.Я обнаружил, что первый подход является самым быстрым с использованием кода тестирования Spender.

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

var result = target.Distinct()
                   .Select(s => new { Word = s, Count = target.Count(w => w == s) })
                   .OrderByDescending(o => o.Count);

// or in query form

var result = from s in target.Distinct()
             let count = target.Count(w => w == s)
             orderby count descending
             select new { Word = s, Count = count };

РЕДАКТИРОВАТЬ: избавился от кортежа, так как анонимный тип был под рукой.

1 голос
/ 28 октября 2010

Вот версия LINQ без ToDictionary(), которая может добавить ненужные накладные расходы в зависимости от ваших потребностей ...

var dic = src.Split(' ').GroupBy(s => s, (str, g) => new { str, count = g.Count() });

Или в синтаксисе запроса ...

var dic = from str in src.Split(' ')
          group str by str into g
          select new { str, count = g.Count() };
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...