繁体   English   中英

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

[英]Most efficient way to retrieve all element of a Dictionary from a list of keys?

我已经有一个Dictionary<DateTime,SomeObject>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;
}

通常,有两种方法可以执行此操作:

  1. 依次浏览requiredTimestamps并在字典中查找每个日期/时间戳。 字典查找为O(1),因此如果要查找k项目,则需要O(k)时间。
  2. 依次浏览字典,并在requiredTimestamps哈希集中提取具有匹配键的关键字。 这将花费O(n)时间,其中n是字典中的项目数。

从理论上讲 ,第一种选择-这是您目前拥有的-是最快的方法。

实际上,当您要查找的项目数少于字典中项目总数的某个百分比时,第一个项目可能会更高效。 也就是说,如果您要在一百万个字典中查找100个键,则第一个选项几乎肯定会更快。 如果您要在一百万个字典中查找500,000个键,则第二种方法可能会更快,因为移至下一个键比查找要快得多。

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

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

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

您可以使用LINQ,但是我怀疑它是否会提高性能,即使有任何区别也可以忽略不计。

您的方法可能是:

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

这样做的好处之一是懒惰的评估,因为您没有填充列表并返回它。

方法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;
}

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

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

如果要对其进行测试,请将其粘贴到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]);
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM