优化循环

时间:2017-09-21 11:34:37

标签: c# algorithm loops optimization

我试图实现一个工具,根据单词的词条对某些字符串进行分组。在初始化期间,我为每个可能的组创建一个字典,其中包含将分组到此键中的单词列表。这就是我到目前为止所做的:

public Dictionary<string, HashSet<string>> Sets { get; set; }

private void Initialize(IStemmer stemmer)
{
    // Stemming of keywords and groups
    var keywordStems = new Dictionary<string, List<string>>();
    var groupStems = new Dictionary<string, List<string>>();

    foreach (string keyword in Keywords)
    {
        keywordStems.Add(keyword, CreateLemmas(keyword, stemmer));
        foreach (string subset in CreateSubsets(keyword))
        {
            if (subset.Length > 1 && !groupStems.ContainsKey(subset))
            {
                groupStems.Add(subset, CreateLemmas(subset, stemmer));
            }
        }
    }

    // Initialize all viable sets
    // This is the slow part
    foreach (string gr in groupStems.Keys)
    {
        var grStems = groupStems[gr];
        var grKeywords = new HashSet<string>((from kw in Keywords
                                                where grStems.All(keywordStems[kw].Contains)
                                                select kw));
        if (grKeywords.Count >= Settings.MinCount)
        {
            Sets.Add(gr, grKeywords);
        }
    }
}

有什么方法可以加速这种方法的瓶颈吗?

3 个答案:

答案 0 :(得分:2)

@mjwills的答案是个好主意。这似乎是最昂贵的操作:

var grKeywords = new HashSet<string>((
  from kw in Keywords
  where grStems.All(keywordStems[kw].Contains)
  select kw));

建议通过利用茎是一组的事实来优化Contains。但如果它们是一套,那么为什么我们一再要求遏制呢? 他们是一套;设置操作。问题是“关键字是什么,grStem集合的每个成员都包含在关键字的词干集”中。 “该集合中包含的每个成员都是子集操作。

var grKeywords = new HashSet<string>((
  from kw in Keywords
  where grStems.IsSubsetOf(keywordStems[kw])
  select kw));

IsSubsetOf的实现针对常见场景进行了优化,例如“两个操作数都是集合”。它需要早期出局;如果您的组词干大于大于,那么您不需要检查每个元素;其中一个将会失踪。但是你的原始算法无论如何都要检查每个元素,即使你可以提前保释并节省所有时间。

答案 1 :(得分:2)

@ mjwills再次提出了一个好主意,我建议对其进行一些改进。这里的想法是执行查询,将结果缓存在一个数组中,只有在必要时才将其实现为哈希集:

foreach (var entry in groupStems)
{
    var grStems = entry.Value;
    var grKeywords = (WHATEVER).ToArray();
    if (grKeywords.Length >= Settings.MinCount)
        Sets.Add(entry.Key, new HashSet<string>(grKeywords));
}

首先:我实际上怀疑通过用不必要的数组结构替换它来避免不必要的哈希集构造是一种胜利。测量它,看看。

第二:ToList可以比ToArray更快,因为可以在知道查询结果集的大小之前构建列表。 ToArray基本上必须首先执行ToList,然后将结果复制到精确大小的数组中。因此,如果ToArray不是胜利,ToList可能是。或不。测量它。

第三:我注意到如果您喜欢这种风格,整个事情可以重写成一个查询。

var q = from entry in groupStems
        let grStems = entry.Value
        let grKeywords = new HashSet<string>(WHATEVER)
        where grKeywords.Count >= Settings.MinCount
        select (entry.Key, grKeywords);
var result = q.ToDictionary( ... and so on ... )

这可能不会更快,但可能更容易推理。

答案 2 :(得分:1)

一个建议是改变:

var keywordStems = new Dictionary<string, List<string>>();

为:

var keywordStems = new Dictionary<string, HashSet<string>>();

由于您之后的Contains来电,这应该会产生影响:

var grKeywords = new HashSet<string>((from kw in Keywords
                                                where grStems.All(keywordStems[kw].Contains)
                                                select kw));

因为Contains HashSet通常比List更快。

还考虑改变:

foreach (string gr in groupStems.Keys)
{
    var grStems = groupStems[gr];
    var grKeywords = new HashSet<string>((from kw in Keywords
                                            where grStems.All(keywordStems[kw].Contains)
                                            select kw));
    if (grKeywords.Count >= Settings.MinCount)
    {
        Sets.Add(gr, grKeywords);
    }
}

为:

foreach (var entry in groupStems)
{
    var grStems = entry.Value;
    var grKeywords = (from kw in Keywords
                                          where grStems.All(keywordStems[kw].Contains)
                                          select kw).ToArray();
    if (grKeywords.Length >= Settings.MinCount)
    {
        Sets.Add(entry.Key, new HashSet<string>(grKeywords));
    }
}

HashSet初始化(相对于初始化Array 相对较贵)转换为 if语句,可以提高性能如果相对较少地输入if(在您的评论中,您声明大约25%的时间输入它)。