C# 字符串到字典的集合

C# 字符串到字典的集合,c#,linq,c#-4.0,C#,Linq,C# 4.0,给定一个有序的字符串集合: var strings = new string[] { "abc", "def", "def", "ghi", "ghi", "ghi", "klm" }; 使用LINQ创建字符串字典,以确定该字符串在集合中的出现次数: IDictionary<string,int> stringToNumOccurrences = ...; IDictionary StringTonumOccessions=。。。; 最好在字符串集合上进行单次传递…标准LINQ

给定一个有序的字符串集合:

var strings = new string[] { "abc", "def", "def", "ghi", "ghi", "ghi", "klm" };
使用LINQ创建字符串字典,以确定该字符串在集合中的出现次数:

IDictionary<string,int> stringToNumOccurrences = ...;
IDictionary StringTonumOccessions=。。。;

最好在字符串集合上进行单次传递…

标准LINQ方式如下:

stringToNumOccurrences = strings.GroupBy(s => s)
                                .ToDictionary(g => g.Key, g => g.Count());

Timwi/Darin的建议将在对原始集合的一次传递中执行此操作,但它将为分组创建多个缓冲区。LINQ真的不太擅长做这种计数,像这样的问题是我写Push LINQ的最初动机。您可能想阅读我的文章,了解更多关于为什么LINQ在这里效率不高的细节

推动LINQ和同样想法的更令人印象深刻的实现——可以更有效地处理这个问题

当然,如果你真的不太在意额外的效率,可以使用
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;
}
公共静态IDictionary CountEntries(IEnumerable字符串)
{
var dictionary=newdictionary();
使用(var iterator=strings.GetEnumerator())
{
如果(!iterator.MoveNext())
{
//没有条目
返回字典;
}
字符串current=迭代器.current;
int currentCount=1;
while(iterator.MoveNext())
{
string next=iterator.Current;
如果(下一个==当前)
{
currentCount++;
}
其他的
{
字典[当前]=当前计数;
当前=下一个;
currentCount=1;
}
}
//写出尾随结果
字典[当前]=当前计数;
}
返回字典;
}
这是O(n),除了在写入值时,不涉及字典查找。另一种实现方法是使用
foreach
和一个
current
值,该值从null开始。。。但在其他几个方面,这最终是相当令人讨厌的。(我已经尝试过:)当我需要对第一个值进行特殊的案例处理时,我通常使用上述模式


实际上,您可以使用
Aggregate
对LINQ执行此操作,但这将非常糟糕。

如果这是实际的生产代码,我会同意

如果这确实是一个家庭作业,并且您需要编写自己的实现,那么它应该不会太难。以下是一些提示,可以为您指明正确的方向:

  • Dictionary
    有一个
    ContainsKey
    方法
  • IDictionary
    接口的
    此[TKey]
    属性是可设置的;i、 例如,您可以执行
    dictionary[key]=1
    (这意味着您也可以执行
    dictionary[key]+=1

  • 根据这些线索,我认为您应该能够找到如何“手工”完成此任务。

    如果您正在寻找一种特别有效(快速)的解决方案,那么
    GroupBy
    对您来说可能太慢了。您可以使用循环:

    var strings = new string[] { "abc", "def", "def", "ghi", "ghi", "ghi", "klm" };
    var stringToNumOccurrences = new Dictionary<string, int>();
    foreach (var str in strings)
    {
        if (stringToNumOccurrences.ContainsKey(str))
            stringToNumOccurrences[str]++;
        else
            stringToNumOccurrences[str] = 1;
    }
    return stringToNumOccurrences;
    
    var strings=新字符串[]{“abc”、“def”、“def”、“ghi”、“ghi”、“ghi”、“klm”};
    var stringtonumoccurrencess=新字典();
    foreach(字符串中的var str)
    {
    if(stringtonumoccurrencess.ContainsKey(str))
    StringTonumEvents[str]++;
    其他的
    StringTonumEvents[str]=1;
    }
    返回StringTonumEvents;
    
    这是一个foreach版本,就像Jon提到的,他在回答中发现“非常讨厌”。我把它放在这里,所以有一些具体的事情要谈

    我必须承认,我发现它比乔恩的版本更简单,我真的看不出它有什么恶心的地方。乔恩?有人吗

    static Dictionary<string, int> CountOrderedSequence(IEnumerable<string> source)
    {
        var result = new Dictionary<string, int>();
        string prev = null;
        int count = 0;
        foreach (var s in source)
        {
            if (prev != s && count > 0)
            {
                result.Add(prev, count);
                count = 0;
            }
            prev = s;
            ++count;
        }
        if (count > 0)
        { 
            result.Add(prev, count);
        }
        return result;
    }
    
    静态字典CountOrderedSequence(IEnumerable源代码)
    {
    var result=newdictionary();
    字符串prev=null;
    整数计数=0;
    foreach(源中的var s)
    {
    如果(上一个!=s&&count>0)
    {
    结果。添加(上一个,计数);
    计数=0;
    }
    prev=s;
    ++计数;
    }
    如果(计数>0)
    { 
    结果。添加(上一个,计数);
    }
    返回结果;
    }
    

    更新了以添加对空源代码的必要检查-我仍然认为它比Jon的更简单:-)

    不,这不是家庭作业,我得到了一个编译时错误,出现上述错误:错误CS1660:无法将lambda表达式转换为类型“System.Collections.Generic.IEqualityComparer”,因为它不是委托类型。错误CS1502:与“System.Collections.Generic.Dictionary.this[string]”匹配的最佳重载方法具有一些无效属性arguments@Michael:谢谢,修正了我的答案。我不知道谁会投票否决你。除了一个合理的输入错误之外,您确实有了第一个正确的解决方案(使用LINQ)。Jon,感谢您查看此内容。我自己也在想这件事。最理想的情况是,我希望一次通过col1就可以构建一个字典,并且出现次数可以在运行中增加。感谢您详细说明了涉及循环的解决方案。我确实意识到这是一种“冗长”的方式,但我希望能有一种快速、简单(且高效)的方式通过LINQ来实现,而我忽略了这种方式。@Michael:LINQ通常不会提供任何只在非常特定的情况下才起作用的内容(例如已经订购的收藏)。@Jon,事实上,我并不介意在LINQ解决方案中进行如此多的字典查找,但是其他临时集合,即使每个组只创建一次,看起来也不太受欢迎。我想我希望有某种自引用LINQ解决方案可以使用字典,因为它是内置的
    static Dictionary<string, int> CountOrderedSequence(IEnumerable<string> source)
    {
        var result = new Dictionary<string, int>();
        string prev = null;
        int count = 0;
        foreach (var s in source)
        {
            if (prev != s && count > 0)
            {
                result.Add(prev, count);
                count = 0;
            }
            prev = s;
            ++count;
        }
        if (count > 0)
        { 
            result.Add(prev, count);
        }
        return result;
    }