首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在C#中合并列表中的日期范围

如何在C#中合并列表中的日期范围
EN

Stack Overflow用户
提问于 2019-07-03 10:39:29
回答 5查看 2.1K关注 0票数 9

我有这样安排的日期清单:

代码语言:javascript
复制
(From, To)
(From, To)
...
(From, To)

我试图找出如何有效地合并范围(它必须相当快,因为它是实时合并金融数据流)。

日期不重叠。

我在想的是:

按时间对所有内容进行排序,然后遍历成对以查看是否对== pairs 2。从合并它们,但这意味着几次迭代。

有没有更好的方法来做到这一点,就像一次传球

以下是一些例子

代码语言:javascript
复制
(2019-1-10, 2019-1-12)
(2019-3-10, 2019-3-14)
(2019-1-12, 2019-1-13)

预期产出:

代码语言:javascript
复制
(2019-1-10, 2019-1-12) + (2019-1-12, 2019-1-13) -> (2019-1-10, 2019-1-13)
(2019-3-10, 2019-3-14) -> (2019-3-10, 2019-3-14)

在实践中,它实际上是关于秒而不是日期,但想法是一样的。

EN

回答 5

Stack Overflow用户

回答已采纳

发布于 2019-07-03 11:16:51

您提到日期永远不会重叠,但我认为编写只合并重叠日期的代码要简单一些。第一步是定义日期范围类型:

代码语言:javascript
复制
class Interval
{
    public DateTime From { get; set; }
    public DateTime To { get; set; }
}

然后,可以定义一个扩展方法,检查两个间隔是否重叠:

代码语言:javascript
复制
static class IntervalExtensions
{
    public static bool Overlaps(this Interval interval1, Interval interval2)
        => interval1.From <= interval2.From
           ? interval1.To >= interval2.From : interval2.To >= interval1.From;
}

请注意,此代码假定为From <= To,因此您可能希望将Interval更改为不可变类型,并在构造函数中验证这一点。

您还需要一种合并两个间隔的方法:

代码语言:javascript
复制
public static Interval MergeWith(this Interval interval1, Interval interval2)
    => new Interval
    {
        From = new DateTime(Math.Min(interval1.From.Ticks, interval2.From.Ticks)),
        To = new DateTime(Math.Max(interval1.To.Ticks, interval2.To.Ticks))
    };

下一步是定义另一个扩展方法,它迭代一个间隔序列并尝试合并连续的重叠间隔。最好使用迭代器块来完成:

代码语言:javascript
复制
public static IEnumerable<Interval> MergeOverlapping(this IEnumerable<Interval> source)
{
    using (var enumerator = source.GetEnumerator())
    {
        if (!enumerator.MoveNext())
            yield break;
        var previousInterval = enumerator.Current;
        while (enumerator.MoveNext())
        {
            var nextInterval = enumerator.Current;
            if (!previousInterval.Overlaps(nextInterval))
            {
                yield return previousInterval;
                previousInterval = nextInterval;
            }
            else
            {
                previousInterval = previousInterval.MergeWith(nextInterval);
            }
        }
        yield return previousInterval;
    }
}

如果两个连续的间隔不重叠,则产生前一个间隔。但是,如果它们重叠,则通过合并两个间隔来更新前一个间隔,并将合并的间隔保留为下一个迭代的前一个间隔。

您的示例数据没有排序,因此在合并必须排序的间隔之前:

代码语言:javascript
复制
var mergedIntervals = intervals.OrderBy(interval => interval.From).MergeOverlapping();

但是,如果对实际数据进行了排序,而您在注释中已经指出了这一点,则可以跳过排序。该算法将对数据进行一次传递,因此是O(n)

票数 19
EN

Stack Overflow用户

发布于 2019-07-03 12:13:49

试试看:

代码语言:javascript
复制
var source = new[]
{
    new { from = new DateTime(2019, 1, 10), to = new DateTime(2019, 1, 12) },
    new { from = new DateTime(2019, 3, 10), to = new DateTime(2019, 3, 14) },
    new { from = new DateTime(2019, 1, 12), to = new DateTime(2019, 1, 13) },
};

var data =
    source
        .OrderBy(x => x.from)
        .ThenBy(x => x.to)
        .ToArray();

var results =
    data
        .Skip(1)
        .Aggregate(
            data.Take(1).ToList(),
            (a, x) =>
            {
                if (a.Last().to >= x.from)
                {
                    a[a.Count - 1] = new { from = a.Last().from, to = x.to };
                }
                else
                {
                    a.Add(x);
                }
                return a;
            });

这是一个很好的查询,它提供了您想要的输出。

票数 5
EN

Stack Overflow用户

发布于 2019-07-03 11:09:42

创建两个字典(即散列映射),一个使用To date作为键,From-To date作为值,另一个以From date作为键。

迭代您的日期范围,并为每个范围检查从日期是否存在作为键在“日期键”字典中,反之亦然。

如果两者都不匹配,那么将范围添加到两个字典中。

如果其中一个中有匹配,而另一个没有匹配,则从两个字典中删除匹配范围(使用适当的键),将新范围与现有范围合并,并将结果添加到这两个字典中。

如果两个字典中都有匹配(所添加的范围填补了一个漏洞),那么从两个字典中删除两个匹配,合并这三个范围(两个现有的和一个新的),并将结果添加到两个字典中。

最后,您的字典包含一组未排序的所有日期范围,您可以通过迭代其中一个字典的键来提取这些日期范围。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/56868258

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档