从键列表中检索Dictionary的所有元素的最有效方法?

时间:2016-04-05 14:34:00

标签: c# algorithm dictionary processing-efficiency

我有一个c#Dictionary<DateTime,SomeObject>个实例。

我有以下代码:

private Dictionary<DateTime, SomeObject> _containedObjects = ...;//Let's imagine you have ~4000 items in it

public IEnumerable<SomeObject> GetItemsList(HashSet<DateTime> requiredTimestamps){
    //How to return the list of SomeObject contained in _containedObjects
    //Knowing that rarely(~<5% of the call), one or several DateTime of "requiredTimestamps" may not be in _containedObjects
}

我正在寻找如何返回包含所提供的键之一引用的所有元素的IEnumerable<SomeObject>。唯一的问题是这个方法会经常调用,我们可能并不总是在参数中都有每个给定的键。

那么有什么比这更有效的方法:

private Dictionary<DateTime, SomeObject> _containedObjects = ...;//Let's imagine you have ~4000 items in it

public IEnumerable<SomeObject> GetItemsList(HashSet<DateTime> requiredTimestamps){
    List<SomeObject> toReturn = new List<SomeObject>();
    foreach(DateTime dateTime in requiredTimestamps){
        SomeObject found;
        if(_containedObjects.TryGetValue(dateTime, out found)){
            toReturn.Add(found);
        }
    }
    return toReturn;
}

4 个答案:

答案 0 :(得分:2)

一般来说,有两种方法可以做到这一点:

  1. 按顺序浏览requiredTimestamps并在字典中查找每个日期/时间戳。字典查找是O(1),因此如果要查找k项,则需要O(k)时间。
  2. 按顺序浏览字典,并在requiredTimestamps哈希集中提取具有匹配键的字典。这将花费O(n)时间,其中n是字典中的项目数。
  3. 理论上,第一个选项 - 你现在拥有的 - 将是最快的方式。

    在实践中,当您查找的项目数量少于字典中项目总数的某个百分比时,第一个项目可能会更有效率。也就是说,如果你在一百万字典中查找100个密钥,第一个选项几乎肯定会更快。如果您在一百万字典中查找500,000个密钥,第二种方法可能会更快,因为移动到下一个密钥要比查找更快。

    您可能希望针对最常见的情况进行优化,我怀疑这种情况正在查找相对较小比例的密钥。在这种情况下,您描述的方法几乎肯定是最好的方法。但唯一可以确定的方法就是衡量。

    您可能考虑的一个优化是预先调整输出列表的大小。这将避免重新分配。因此,当您创建toReturn列表时:

    List<SomeObject> toReturn = new List<SomeObject>(requiredTimestamps.Count);
    

答案 1 :(得分:1)

您可以使用LINQ,但我怀疑它是否会增加任何性能,即使有任何差异,它也可以忽略不计。

您的方法可能是:

public IEnumerable<SomeObject> GetItemsList(HashSet<DateTime> requiredTimestamps)
{
    return _containedObjects.Where(r => requiredTimestamps.Contains(r.Key))
                            .Select(d => d.Value);
}

对此有一个好处是懒惰评估,因为您没有填充列表并返回它。

答案 2 :(得分:1)

方法1: 要使此显着更快 - 这不是通过更改算法,而是通过在方法中创建_containedObjects的本地副本并引用本地副本进行查找。

示例:

public static IEnumerable<int> GetItemsList3(HashSet<DateTime> requiredTimestamps)
{
    var tmp = _containedObjects;

    List<int> toReturn = new List<int>();
    foreach (DateTime dateTime in requiredTimestamps)
    {
        int found;

        if (tmp.TryGetValue(dateTime, out found))
        {
            toReturn.Add(found);
        }
    }
    return toReturn;
}

测试数据和时间(在找到125个键的5000个项目的集合上):
您的原始方法(毫秒):2,06032186895335
方法1(毫秒):0,53549626223609

方法2: 使这一点快得多的一种方法是迭代较小的集合并在较大的集合上进行查找。根据尺寸差异,您将获得一些速度。

您正在使用Dictionary和HashSet,因此您对其中任何一个的查找都将为O(1)。

示例:如果_containedObjects的项目数少于requiredTimestamps,我们会遍历_containedObjects(否则请使用您的方法进行反向)

public static IEnumerable<int> GetItemsList2(HashSet<DateTime> requiredTimestamps)
{
    List<int> toReturn = new List<int>();
    foreach (var dateTime in _containedObjects)
    {
        int found;

        if (requiredTimestamps.Contains(dateTime.Key))
        {
            toReturn.Add(dateTime.Value);
        }
    }
    return toReturn;
}

测试数据和时间(_containedObjects设置为5000,requiredTimestamps设置10000项,找到125个键:
你的原始方法(毫秒):3,88056291367086
方法2(毫秒):3,31025939438943

答案 3 :(得分:0)

以下是一些不同的方法 - 性能几乎相同,因此您可以根据可读性进行选择。

如果你想测试它,请将它粘贴到LinqPad中 - 否则只需要收集你需要的任何代码。

我认为从可读性的角度来看,我个人最喜欢的是方法3.方法4当然是可读的,但却有一个令人不快的特性,即它会在每个所需的时间戳中对字典进行两次查找。

void Main()
{
    var obj = new TestClass<string>(i => string.Format("Element {0}", i));

    var sampleDateTimes = new HashSet<DateTime>();
    for(int i = 0; i < 4000 / 20; i++)
    {
        sampleDateTimes.Add(DateTime.Today.AddDays(i * -5));
    }
    var result = obj.GetItemsList_3(sampleDateTimes);
    foreach (var item in result)
    {
        Console.WriteLine(item);
    }
}

class TestClass<SomeObject>
{
    private Dictionary<DateTime, SomeObject> _containedObjects;

    public TestClass(Func<int, SomeObject> converter)
    {
        _containedObjects = new Dictionary<DateTime, SomeObject>();
        for(int i = 0; i < 4000; i++)
        {
            _containedObjects.Add(DateTime.Today.AddDays(-i), converter(i));
        }
    }

    public IEnumerable<SomeObject> GetItemsList_1(HashSet<DateTime> requiredTimestamps)
    {
        List<SomeObject> toReturn = new List<SomeObject>();
        foreach(DateTime dateTime in requiredTimestamps)
        {
            SomeObject found;
            if(_containedObjects.TryGetValue(dateTime, out found))
            {
                toReturn.Add(found);
            }
        }
        return toReturn;
    }

    public IEnumerable<SomeObject> GetItemsList_2(HashSet<DateTime> requiredTimestamps)
    {
        foreach(DateTime dateTime in requiredTimestamps)
        {
            SomeObject found;
            if(_containedObjects.TryGetValue(dateTime, out found))
            {
                yield return found;
            }
        }
    }    

    public IEnumerable<SomeObject> GetItemsList_3(HashSet<DateTime> requiredTimestamps)
    {
        return requiredTimestamps
            .Intersect(_containedObjects.Keys)
            .Select (k => _containedObjects[k]);
    }

    public IEnumerable<SomeObject> GetItemsList_4(HashSet<DateTime> requiredTimestamps)
    {
        return requiredTimestamps
            .Where(dt => _containedObjects.ContainsKey(dt))
            .Select (dt => _containedObjects[dt]);
    }
}