Самый эффективный способ извлечь все элементы справочника из списка ключей?
У меня есть экземпляр 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 ответа:
Метод 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; }
тестовые данные и время (на наборе из 5000 элементов С 125 найденными ключами):
Ваш исходный метод (миллисекунды): 2,06032186895335
Способ 1 (миллисекунды): 0,53549626223609Метод 2: Один из способов сделать это незначительно быстрее перебирать меньшее множество и выполнять поиск по большему набору. В зависимости от разницы в размерах вы получите некоторую скорость.
Вы используете словарь и хэш-набор, поэтому ваш поиск по любому из них будет 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
В общем, есть два способа сделать это:
- последовательно просмотрите
requiredTimestamps
и найдите каждую отметку даты/времени в словаре. Поиск по словарю - это O(1), поэтому если есть элементыk
, которые нужно искать, это займет O (k) времени.- последовательно просмотрите словарь и извлеките те, которые имеют соответствующие ключи в наборе хэшей
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); }
Один положительный момент в этом-ленивая оценка, так как вы не заполняете список и не возвращаете его.
Вот несколько различных способов сделать это-производительность все в значительной степени одинакова, так что вы можете выбрать на основе читабельности.
Вставьте это в 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]); } }