Wait the light to fall

简单化

焉知非鱼

上周每周挑战的第二个任务是列出某年每个月最后一个星期五的日期。

许多参与者花了很大的力气来创建高效而准确的解决方案:跟踪每个月的最后一天,正确检测闰年,计算出特定日期的月日公式,管理所需的复杂日期运算。

但是,鉴于 Raku 中内置了强大的 Date 类,这些令人钦佩的劳作其实大部分都没有必要。

整个任务可以通过简单地从给定年份的1月1日到12月31日来完成,首先检查每个日期是否是星期五(即"星期几"的值是5),然后检查下一个星期五(即正好一周后的日期)是否恰好在不同的月份。

如果这两个条件都为真,那么我们就有一个"本月最后一个星期五",所以我们只需打印出来。

换句话说:

for Date.new($year,1,1) .. Date.new($year,12,31) -> $date {
    if $date.day-of-week == 5
    && $date.later(:1week).month != $date.month {
        say $date;
    }
}

我们不需要明确地担心每个月有多长,一年是否是闰年,或者如何计算出某一天是否是星期五;Date 类为我们解决了所有这些问题。

而这不是运气,完全是设计好的。Raku 有大量类似这样的内置数据类型;它们是另一种"合适的工具,就在手边"。

当然,因为 Raku 是多范式的,所以有很多其他的方法可以将解决方案包裹在 Date 类的便利性中,这取决于你喜欢如何思考这个世界。

如果你喜欢纯韩式式的解决方案,可以这样写:

say join "\n",
    grep { .later(:1week).month != .month },
    grep { .day-of-week == 5 },
         Date.new($year,1,1) .. Date.new($year,12,31);

或者你更喜欢一个(潜在的)并行的管道,然后通过一系列独立的过滤器和处理器来过滤日期序列:

Date.new($year,1,1) .. Date.new($year,12,31)
    ==> grep( {.day-of-week == 5} )
        ==> grep( {.later(:1week).month != .month} )
            ==> join( "\n" )
                ==> say();

或者你愿意使用纯 OO 的方法,只要在日期列表上调用相应的方法即可:

(Date.new($year,1,1) .. Date.new($year,12,31))
    .grep( {.day-of-week == 5} )
    .grep( {.later(:1week).month != .month} )
    .join( "\n" )
    .say;

你甚至可以把它写成一个老派的必备单行程序:

.say if .day-of-week == 5
     && .later(:1week).month != .month
        for Date.new($year,1,1) .. Date.new($year,12,31);

所以,无论你是喜欢命令式的、函数式的、面向对象的还是流水线式的方法,你都可以简单而干净地解决这个问题。因为 Raku 有必要的内置数据类型来为你完成繁重的工作。

by Damian