如何在 c 中将日期列表转换为日期范围列表#

How Convert Date Lists to DateRanges List in c#

提问人:Sara tabaghchi 提问时间:9/18/2023 最后编辑:EnigmativitySara tabaghchi 更新时间:9/18/2023 访问量:90

问:

这种区别怎么可能:List<DateTime>List<List<DateTime>>1

例如,List 是

var list = new List<DateTime>()
{
    new DateTime(2023, 05, 02),
    new DateTime(2023, 05, 03),
    new DateTime(2023, 05, 04),
    new DateTime(2023, 05, 10),
    new DateTime(2023, 05, 11),
    new DateTime(2023, 05, 20),
};

我希望我的方法返回此列表

{
    2023-05-02,
    2023-05-03,
    2023-05-04
},
{
    2023-05-10,
    2023-05-11
},
{
    2023-05-20
}
C# LINQ 日期范围

评论

2赞 Jon Skeet 9/18/2023
就我个人而言,如果没有 LINQ,我可能会这样做。你有任何代码尝试这样做吗?

答:

1赞 Dmitry Bychenko 9/18/2023 #1

您可以尝试循环并在 Linq 的帮助下构建答案,例如

using System.Linq;

...

private static List<List<DateTime>> Solve(IEnumerable<DateTime> source) {
  List<List<DateTime>> result = new();
 
  if (source is null)
    return result;

  foreach (var date in source.OrderBy(item => item)) 
    if (result.Count > 0 && result.Last().Last().AddDays(1) == date)
      result.Last().Add(date);
    else
      result.Add(new List<DateTime>() { date });

  return result;
}

评论

1赞 Tim Schmelter 9/18/2023
而不是我会使用 .然后,如果他们有时间部分,它也可以工作。result.Last().Last().AddDays(1) == datedt - result.Last().Last()).TotalDays > 1
2赞 Enigmativity 9/18/2023 #2

下面是 LINQ 的答案:

List<List<DateTime>> result =
    list
        .Skip(1)
        .Aggregate(
            new[] { list.Take(1).ToList() }.ToList(),
            (a, x) =>
            {
                if (x.Subtract(a.Last().Last()).Days != 1)
                {
                    a.Add(new List<DateTime>());
                }
                a.Last().Add(x);
                return a;
            });

这给出了:

results

注意:这假设已经订购(根据问题中的列表)。list


或者作为一种方便的扩展方法:

public static List<List<T>> Aggregate<T>(this IEnumerable<T> source, Func<List<List<T>>, T, bool> isCurrent)
{
    List<List<T>> result = new() { new List<T>() };
    using (var e = source.GetEnumerator())
    {
        if (e.MoveNext())
        {
            result.Last().Add(e.Current);
            while (e.MoveNext())
            {
                if (!isCurrent(result, e.Current))
                {
                    result.Add(new List<T>());
                }
                result.Last().Add(e.Current);
            }
        }
        return result;
    }
}

现在是:

List<List<DateTime>> result =
    list
        .Aggregate((a, x) => x.Subtract(a.Last().Last()).Days == 1);

评论

0赞 Enigmativity 9/18/2023
@Ralf - 是的,所有答案都经过了充分测试。
0赞 Charlieface 9/18/2023
var e需要一个.还不如留在本地。另外为什么不是外部列表?usingresult.Last()yield return
0赞 Enigmativity 9/18/2023
@Charlieface - 我不喜欢在底层源可能很热时返回半暴露的枚举对象。
0赞 Orion 9/18/2023 #3

循环遍历日期并检查当前日期是否与上一个日期相差不到 1 天。如果是,则将其添加到当前列表中,否则开始一个新列表。

private List<List<DateTime>> GetListsOfConsecutiveDates(List<DateTime> dates)
{
    var result = new List<List<DateTime>>();

    var currentList = new List<DateTime>();
    var previousDate = dates.First();
    currentList.Add(previousDate);

    foreach (var date in dates.Skip(1))
    {
        if (date - previousDate <= TimeSpan.FromDays(1))
        {
            currentList.Add(date);
        }
        else
        {
            result.Add(currentList);
            currentList = new List<DateTime> { date };
        }

        previousDate = date;
    }

    result.Add(currentList);
    return result;
}
1赞 Tim Schmelter 9/18/2023 #4

如果您的逻辑需要访问下一个或上一个项目,我不会使用 LINQ。简单易读且高效。如果他们有时间,以下工作也有效:foreach

List<List<DateTime>> result = new();
foreach (DateTime dt in list.OrderBy(d => d))// omit OrderBy if the list is already sorted
{
    if(result.Count == 0 || (dt - result[^1].Last()).TotalDays > 1)
    {
        result.Add(new List<DateTime>{dt});
    }
    else
    {
        result[^1].Add(dt);
    }
}

评论

0赞 Sara tabaghchi 9/18/2023
索引运算符在 C#7.3 中不起作用
1赞 Tim Schmelter 9/18/2023
@Saratabaghchi:然后使用 LINQ 或索引器。 检查序列是否具有索引器并使用该索引器,因此它也很有效。Lastresult[result.Count-1]Enumerable.Last
0赞 Charlieface 9/18/2023 #5

这是一个完全流媒体的版本。任何地方都没有创建列表,整个事情使用 .IEnumerable

它假设原始源是(以便我们可以使用有效的算法),并且它已经排序。List<T>SkipTake

public static IEnumerable<IEnumerable<T>> GroupUp<T>(this List<T> source, Func<T, T, bool> isSameGroupFunc)
{
    for (var start = 0; start < source.Count; start++)
    {
        var end = start;
        while (end + 1 < source.Count)
        {
            if (!isSameGroupFunc(source[end], source[end + 1]))
                break;

            end++;
        }
        yield return SkipTake(source, start, end + 1);
        start = end;
    }
}

    
private static IEnumerable<T> SkipTake<T>(List<T> source, int start, int count)
{
    for (var i = start; i < count; i++)
        yield return source[i];
}

你像这样使用它:

var results = list.GroupUp((a, b) => a.AddDays(1) >= b);

dotnetfiddle