【问题标题】:Find the closest DateTime key in Dictionary<DateTime, double>在 Dictionary<DateTime, double> 中查找最近的 DateTime 键
【发布时间】:2023-03-10 16:00:01
【问题描述】:

我将 DateTime 的 Date 部分作为查找值,并希望在 Dictionary&lt;DateTime, double&gt; 类型的字典中检索匹配值。请注意,日期时间键仅存储为日期部分。

我的问题是可能没有与我的查找值匹配的键。然后我想做的是找到最近的 previous dateTime.Date 键和匹配值。

现在,我知道字典不是按键排序的。我可以使用 SortedDictionary 但出于特定原因更喜欢使用 Dictionary 或者切换到 List 集合(可以预先排序)。我的问题是,在这种情况下你会建议做什么:保留字典结构并减少我的查找值直到找到匹配的键会更有效吗?或者使用列表集合并使用Linq会更好吗?每个字典包含大约 5000 个键/值对。另外,请注意我寻找一个计算效率很高的解决方案,因为查找的频率非常高(可能数十万次(每次查找都保证与之前的任何值不同)

【问题讨论】:

  • 你有什么具体原因要避免SortedDictionary
  • 5000 并不是一个巨大的数字。您需要多久找到一次?
  • @TimSchmelter,我将集合存储为序列化的 json 字符串,并且在序列化排序字典方面没有很好的经验。但是,如果在这里使用 SortedDictionary 最有意义,我当然可以转换为不同的结构。
  • @SriramSakthivel,好问题。将其添加到我的问题中。这就是问题所在,我需要它具有极高的计算效率,因为这种查找发生在非常高的频率上。
  • 使用有序集合是否更好取决于您的应用程序。如果查找 DateTime 是您做的主要事情,那是有道理的。否则,您可以遍历这些项目并将它们与您要查找的值进行比较。

标签: c# linq datetime dictionary collections


【解决方案1】:

为什么要担心早产儿眼科

去做

只有那么如果它很慢,那么你就有问题了 用探查器测量它

然后理解开始于您尝试其他方法并分析它们。

答案是:如果您以任何方式进行操作并且没有性能问题,那么您只是节省了时间并设法做一些其他有用的事情来增加您一天的价值。

过早的优化不仅没有意义,您通常会完全错误地判断应该查看的位置。

【讨论】:

    【解决方案2】:

    由于您需要快速,我认为最好的办法是使用BinarySearch 的结果。这需要一个已排序的List&lt;T&gt;

    int result = myList.BinarySearch(targetDate);
    if (result >= 0)
        return myList[result];
    else
    {
        int nextLarger = ~result;
        // return next smaller, or if that doesn't exist, the smallest one
        return myList[Math.Max(0, nextLarger - 1)];
    }
    

    应该可以创建一个结合Dictionary&lt;TKey,TValue&gt; 和排序的List&lt;TKey&gt; 的类,该类仍然像Dictionary&lt;TKey,TValue&gt; 一样序列化。序列化可能很简单(在 Json.NET 中),只需在您的类上添加 [JsonConverter(typeof(KeyValuePairConverter))]

    为了完整起见,以防其他人将来阅读此内容,如果速度不是很重要,您可以更简单地使用以下内容:

    var result = myDict.Keys.Where(x => x < targetDate).Max();
    

    【讨论】:

    • 只是一个关于 linq 查询的快速问题:在此特定示例中,OrderByDescending 是否应该位于 Where 子句之前?
    • 无论哪种方式,您都会得到相同的结果。我希望我编写它的方式会更快,因为您不会对那么多项目进行排序。再说一次,我could be wrong
    • 我验证了,你对这两者实际上都是正确的,我猜排序更快,因为集合比原来的小?
    • 对。实际上,我刚刚意识到可以使用Max() 来改进它。我建议您对两种方式(二进制搜索和 Where/Max)进行分析,看看哪种方式效果更好。
    • 改进后的查询实际上执行得很好,比包含另一种排序的查询快大约十倍。虽然二进制搜索方法是顶级的。
    【解决方案3】:

    我会使用自定义结构和集合来存储这些信息:

    public struct DateValue
    {
        public DateValue(DateTime date, double val)
            : this()
        {
            this.Date = date;
            this.Value = val;
        }
        public DateTime Date { get; set; }
    }
    

    这是一个集合的可能实现,它包含所有DateValues 并封装返回最近的逻辑。它使用List.BinarySearch 来查找它。如果它没有找到直接匹配,它使用BinarySearch 的逻辑来检测最近的是:

    指定值在指定数组中的索引,如果值为 成立。如果未找到值且值小于一个或多个 数组中的元素,一个负数,它是按位补码 大于 value 的第一个元素的索引。如果值 未找到且值大于数组中的任何元素,a 负数,它是(的索引的按位补码 最后一个元素加 1)。

    public class DateValueCollection : List<DateValue>, IComparer<DateValue>
    {
        public DateValueCollection() { }
    
        public DateValueCollection(IEnumerable<DateValue> dateValues, bool isOrdered)
        {
            if (isOrdered)
                base.AddRange(dateValues);
            else
                base.AddRange(dateValues.OrderBy(dv => dv.Date));
        }
    
        public DateValue GetNearest(DateTime date)
        {
            if (base.Count == 0)
                return default(DateValue);
    
            DateValue dv = new DateValue(date, 0);
            int index = base.BinarySearch(dv, this);
    
            if (index >= 0)
            {
                return base[index];
            }
            // If not found, List.BinarySearch returns the complement of the index
            index = ~index;
    
            DateValue[] all;
            if(index >= base.Count - 1)
            {
                // proposed index is last, check previous and last
                all = new[] { base[base.Count - 1], base[base.Count - 2] };
            }
            else if(index == 0)
            {
                // proposed index is first, check first and second
                all = new[] { base[index], base[index + 1] };
            }
            else
            {
                // return nearest DateValue from previous and this
                var thisDV = base[index];
                var prevDV = base[index - 1];
                all = new[]{ thisDV, prevDV };
            }
            return all.OrderBy(x => (x.Date - date).Duration()).First();
        }
    
        public int Compare(DateValue x, DateValue y)
        {
            return x.Date.CompareTo(y.Date);
        }
    }
    

    快速测试:

    var dateVals = new[] { 
        new DateValue(DateTime.Today.AddDays(10), 1), new DateValue(DateTime.Today, 3), new DateValue(DateTime.Today.AddDays(4), 7) 
    };
    var dvCollection = new DateValueCollection(dateVals, false);
    DateValue nearest = dvCollection.GetNearest(DateTime.Today.AddDays(1));
    

    【讨论】:

    • 这看起来很有说服力,让我消化一下,我回来报告。
    • @MattWolf:当然还有改进的空间(例如,可以“注入”一个无序的集合),但它应该给你一个想法。请注意,static 比较器是多余的,我已将其删除。由于该类实现了IComparer&lt;DateValue&gt;,您可以使用base.BinarySearch(dv, this)
    • 我真的很喜欢所提出的解决方案,而且速度非常快,尽管我最终更喜欢 Tim S. 的准系统方法。如果可以的话,我会多次投票。
    猜你喜欢
    • 2016-07-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多