По предложению Тимви / Дарина это будет выполнено за один проход над исходной коллекцией, но создаст несколько буферов для группировок.LINQ на самом деле не очень хорош в подсчете, и такая проблема была моей первоначальной мотивацией для написания Push LINQ.Возможно, вам захочется прочитать мое сообщение в блоге , чтобы узнать, почему LINQ здесь не очень эффективен.
Push LINQ и довольно впечатляющая реализация той же идеи - Reactive Extensions - может справиться с этим более эффективно.
Конечно, если вы не особо заботитесь о дополнительной эффективности, используйте ответ GroupBy
:)
РЕДАКТИРОВАТЬ: я не заметил, что ваши строки были заказаны.Это означает, что вы можете быть на намного более эффективным, потому что вы знаете, что как только вы увидите строку x, а затем строку y, если x и y различны, вы никогда больше не увидите x.В LINQ нет ничего, что могло бы сделать это особенно легко, но вы можете сделать это самостоятельно довольно просто:
public static IDictionary<string, int> CountEntries(IEnumerable<string> strings)
{
var dictionary = new Dictionary<string, int>();
using (var iterator = strings.GetEnumerator())
{
if (!iterator.MoveNext())
{
// No entries
return dictionary;
}
string current = iterator.Current;
int currentCount = 1;
while (iterator.MoveNext())
{
string next = iterator.Current;
if (next == current)
{
currentCount++;
}
else
{
dictionary[current] = currentCount;
current = next;
currentCount = 1;
}
}
// Write out the trailing result
dictionary[current] = currentCount;
}
return dictionary;
}
Это O (n), с включенными нет поисками в словаре, кроме как при написанииценности.Альтернативная реализация будет использовать foreach
и значение current
, начинающееся с нуля ... но это в конечном итоге будет довольно странным в нескольких других отношениях.(Я пробовал это :) Когда мне нужно обработать специальный случай для первого значения, я обычно использую вышеуказанный шаблон.
На самом деле вы могли бы сделать это с LINQ, используя Aggregate
, но это было бы довольно неприятно.