在 DayOfWeek 列表中查找最近的工作日

Find Closest Week Day in DayOfWeek List

这可能是一个新手问题,但这里是。

我有一个方法,其中一个 DayOfWeek 列表被附加到一周中的不同日期(可能是星期三和星期六、星期日、星期一和星期五等)。

鉴于该列表,我需要将它与 Datetime 参数进行比较,找到 DateTime 参数在 DayOfWeek 列表中最接近的星期几,并根据 DateTime 参数在列表。

例如,如果传入的 DateTime 参数是星期日,而我的 DayOfWeek 列表包含星期三和星期六,则需要将该参数移回星期六,因为它在列表中最接近。

同理,如果我的列表中有星期日、星期一和星期六,传入的参数是星期四,那么参数就得移到星期六。

最后,如果参数与列表中的两周天数等距(传入星期三,列表中为星期一和星期五...或传入星期日,列表中为星期二和星期五),然后需要将参数向前移动到下一个最近的工作日(在第一种情况下是星期五,在第二种情况下是星期二)。

最好(至少对我而言),将传入日期的下一个最近工作日的距离转换为 int,这样我就可以执行以下操作:

passedInDate = passedInDate.AddDays(dayOfWeekDistance);
return passedInDate;

但我乐于接受建议。

我试过 LINQ 语句,例如:

int dayOfWeekDistance = targetDayOfWeekList.Min(x => (x - passedInDate));

但无济于事。必须有一些我缺少的花哨的 LINQ 语句。

请注意,如果传入的日期是星期日并且列表中最近的工作日是星期六(类似地,如果传入的日期是星期一,最近的工作日是星期五,则日期需要一直遍历到星期五。

如果我遗漏了什么或者我只是说不清楚,请告诉我。

欢迎大家的帮助!谢谢

让我们把问题分成几个小部分。

注意:以下所有方法都应该像这样放在 class 中

public static class DayOfWeekExtensions
{
}

首先,您希望 Sunday 成为一周的最后一天,而在 DayOfWeek enum 中它被定义在最前面。因此,让我们创建一个函数来解决这个问题:

public static int GetIndex(this DayOfWeek source)
{
    return source == DayOfWeek.Sunday ? 6 : (int)source - 1;
}

然后我们需要一个函数来计算两个 DayOfWeek 值之间的距离(偏移量):

public static int OffsetTo(this DayOfWeek source, DayOfWeek target)
{
    return source.GetIndex() - target.GetIndex();
}

我们还添加一个函数,给定一个主元和两个 DayOfWeek 值,选择两者中最接近的值(应用您的前向优先级规则):

public static DayOfWeek Closest(this DayOfWeek pivot, DayOfWeek first, DayOfWeek second)
{
    int comp = Math.Abs(first.OffsetTo(pivot)).CompareTo(Math.Abs(second.OffsetTo(pivot)));
    return comp < 0 || (comp == 0 && first.GetIndex() > pivot.GetIndex()) ? first : second;
}

现在我们准备好实现从序列中找到最近一天的方法。它可以通过多种方式实现,这里是使用(最后!:) LINQ Aggregate 方法的实现:

public static DayOfWeek? Closest(this IEnumerable<DayOfWeek> source, DayOfWeek target)
{
    if (!source.Any()) return null;
    return source.Aggregate((first, second) => target.Closest(first, second));
}

最后,让我们添加一个计算最近距离的函数:

public static int ClosestDistance(this IEnumerable<DayOfWeek> source, DayOfWeek target)
{
    return source.Closest(target)?.OffsetTo(target) ?? 0;
}

我们完成了。我们刚刚创建了一个简单的可重复使用的小工具 class。

您的情况下的用法是:

int dayOfWeekDistance = targetDayOfWeekList.ClosestDistance(passedInDate.DayOfWeek);

更新:原来你的要求不一样。

应用相同的原理,首先我们需要一个函数来计算一周中两天之间向前和向后距离的最小值,应用前向优先规则。

public static int MinDistanceTo(this DayOfWeek from, DayOfWeek to)
{
    int dist = to - from;
    return dist >= 4 ? dist - 7 : dist <= -4 ? dist + 7 : dist;
}

它所做的基本上是将值从可能的 -6..6 包含范围转换为 -3..3 包含范围内的值。

然后我们只需要一个函数,它将通过使用 Select + Aggregate 实现有问题的方法(它也可以用 Min 和自定义比较器实现).它基本上比较两个绝对距离并再次应用前向优先规则:

public static int MinDistanceTo(this DayOfWeek from, IEnumerable<DayOfWeek> to)
{
    if (!to.Any()) return 0;
    return to.Select(x => from.MinDistanceTo(x)).Aggregate((dist1, dist2) =>
    {
        if (dist1 == dist2) return dist1;
        int comp = Math.Abs(dist1).CompareTo(Math.Abs(dist2));
        return comp < 0 || (comp == 0 && dist1 > 0) ? dist1 : dist2;
    });
}

用法为:

int dayOfWeekDistance = passedInDate.DayOfWeek.MinDistanceTo(targetDayOfWeekList);

借助辅助函数,可以使用 LINQ。

辅助函数使用效用函数计算最接近的星期几,以计算两个 DOW 之间的远期天数:

public int MinDOWDistance(DayOfWeek dow1, DayOfWeek dow2) {
    int FwdDaysDiff(int idow1, int idow2) => idow2 - idow1 + ((idow1 > idow2) ? 7 : 0);
    int fwd12 = FwdDaysDiff((int)dow1, (int)dow2);
    int fwd21 = FwdDaysDiff((int)dow2, (int)dow1);
    return fwd12 < fwd21 ? fwd12 : -fwd21;
}

然后您可以使用 Aggregate 和 LINQ 找到列表中最近的 DOW 和 return 正确的移动天数(和方向):

public int DaysToClosestDOW(DayOfWeek dow1, List<DayOfWeek> dowList) {
    return dowList.Select(dow => {
                                    var cdow = MinDOWDistance(dow1, dow);
                                    return new { dow, dist = cdow, absdist = Math.Abs(cdow) };
                                 })
                  .Aggregate((g1, g2) => (g1.absdist < g2.absdist) ? g1 : ((g1.absdist == g2.absdist) ? ((g1.dist > 0) ? g1 : g2) : g2)).dist;
}

我突然想到我可以使用辅助函数中的 return 和 absdist 的元组,因为它已经知道了。然后我可以在 LINQ 中使用 Tuple:

public (int dist, int absdist) MinDOWDistance(DayOfWeek dow1, DayOfWeek dow2) {
    int FwdDaysDiff(int idow1, int idow2) => idow2 - idow1 + ((idow1 > idow2) ? 7 : 0);
    int fwd12 = FwdDaysDiff((int)dow1, (int)dow2);
    int fwd21 = FwdDaysDiff((int)dow2, (int)dow1);
    if (fwd12 < fwd21)
        return (fwd12, fwd12);
    else
        return (-fwd21, fwd21);
}

public int DaysToClosestDOW(DayOfWeek dow1, List<DayOfWeek> dowList) {
    return dowList.Select(dow => MinDOWDistance(dow1, dow))
                  .Aggregate((g1, g2) => (g1.absdist < g2.absdist) ? g1 : ((g1.absdist == g2.absdist) ? ((g1.dist > 0) ? g1 : g2) : g2)).dist;
}