Perl 6 - 日期難題

reddit 上使用 Perl 6 處理日期

描述


下面的日期, 有些使用了 M D Y 格式, 有些使用了 Y M D 格式, 還使用了任意分隔符! 請把這些散亂的文本解析成合適的 ISO 8601 (YYYY-MM-DD) 格式化日期。

假設只有以 4 個數字開頭的日期使用 Y M D 格式, 其它的使用 M D Y 格式。

輸入樣本


2/13/15
1-31-10
5 10 2015
2012 3 17
2001-01-01
2008/01/07

輸出樣本


2015-02-13
2010-01-31
2015-05-10
2012-03-17
2001-01-01
2008-01-07

擴展挑戰 [中級]


使用 2014-12-24 作為相對日期的基準。

當添加 days(天數) 時, 要考慮到每月會有不同的天數, 忽略閏年。

當添加月和年時, 使用整個 units, 以至于:

one month before october 10 is september 10

one year after 2001-04-02 is 2002-04-02

one month after january 30 is february 28 (not march 1)

Sally's inputs:


tomorrow
2010-dec-7
OCT 23
1 week ago
next Monday
last sunDAY
1 year ago
1 month ago
last week
LAST MONTH
10 October 2010
an year ago
2 years from tomoRRow
1 month from 2016-01-31
4 DAYS FROM today
9 weeks from yesterday

Sally's expected outputs:


2014-12-25
2010-12-01
2014-10-23
2014-12-17
2014-12-29
2014-12-21
2013-12-24
2014-11-24
2014-12-15
2014-11-24
2010-10-10
2013-12-24
2016-12-25
2016-02-28
2014-12-28
2015-02-25

smls 大神給出了完整的 grammar:

my $today = Date.new(2014, 12, 24);

grammar MessyDate {
    rule TOP {
        |    <date>                 { make $<date>.made } # 跟在 regex 后面的花括號是閉包
        | :i <duration> ago         { make $today.earlier: |$<duration>.made     }
        | :i <duration> from <date> { make $<date>.made.later: |$<duration>.made }
    }

    rule date {
        | [ || <month> (<sep>?) <day>   [$0 <year>]?
            || <day>   (<sep>?) <month> [$0 <year>]?
            || <year>  (<sep>?) <month>  $0 <day>    ]
          { make Date.new: $<year>.made//$today.year, |$<month day>?.made }

        | :i today          { make $today     }
        | :i yesterday      { make $today - 1 }
        | :i tomorrow       { make $today + 1 }
        | :i last <weekday> { make $today - ($today.day-of-week - $<weekday>.made) % 7 || 7 }
        | :i next <weekday> { make $today + ($<weekday>.made - $today.day-of-week) % 7 || 7 }
        | :i last <unit>    { make $today.earlier: |($<unit>.made => 1) }
        | :i next <unit>    { make $today.later:   |($<unit>.made => 1) }
    }

    rule duration {
        <count> <unit> { make $<unit>.made => $<count>.made }
    }

    token year {
        | <number(4)>        { make +$<number>       } # <number(4)> 是擴展的 <...> 語法, 實際是方法調用
        | <number(2, 0..49)> { make 2000 + $<number> }
        | <number(2, 50..*)> { make 1900 + $<number> }
    }

    token month {
        | <number(1..2, 1..12)> { make +$<number> }
        | :i Jan[uary]?   { make  1 } # [...] 是非捕獲分組
        | :i Feb[ruary]?  { make  2 }
        | :i Mar[ch]?     { make  3 }
        | :i Apr[il]?     { make  4 }
        | :i May          { make  5 }
        | :i Jun[e]?      { make  6 }
        | :i Jul[y]?      { make  7 }
        | :i Aug[ust]?    { make  8 }
        | :i Sep[tember]? { make  9 }
        | :i Oct[ober]?   { make 10 }
        | :i Nov[ember]?  { make 11 }
        | :i Dec[ember]?  { make 12 }
    }

    token day { <number(1..2, 1..31)> { make +$<number> } }

    token weekday {
        | :i Mon[day]?    { make 1 }
        | :i Tue[sday]?   { make 2 }
        | :i Wed[nesday]? { make 3 }
        | :i Thu[rsday]?  { make 4 }
        | :i Fri[day]?    { make 5 }
        | :i Sat[urday]?  { make 6 }
        | :i Sun[day]?    { make 7 }
    }

    token sep   { <[-/.\h]> } # <[...]> 是 Perl 6 中的字符類
    token count { (<[0..9]>+) { make +$0 }  |  an? { make 1 } }
    token unit  { :i (day|week|month|year) s? { make $0.lc } }

    multi token number ($digits)        {  <[0..9]> ** {$digits} }
    multi token number ($digits, $test) { (<[0..9]> ** {$digits}) <?{ +$0 ~~ $test }> }
}

for lines() {
    say MessyDate.parse($_).made // "failed to parse '$_'";
}

在 grammar 中, 有兩個 regex 的變體, ruletoken。rule 默認不會回溯. rule 與 token 的一個重要區別是, rule 這樣的正則采取了 :sigspace 修飾符。 rule 實際上是

    regex :ratchet :sigspace { ... }

的簡寫. ratchet 這個單詞的意思是: (防倒轉的)棘齒, 意思它是不能回溯的! 而 :sigspace 表明正則中的空白是有意義的, 而 token 實際上是

    regex :ratchet { ... }

的簡寫。 所以在 token 中, 若不是顯式的寫上 \s\h\n 等空白符號, 其它情況下就好像空白隱身了一樣, 雖然你寫了, 但是編譯器卻視而不見。

// 在左側匹配失敗時會在右側提供一個默認值。

<number(4)><number(2, 0..49)> 中使用了擴展了的 <...> 元語法。 標識符(例如左面的 number)后面的第一個字符決定了閉合尖括號之前剩余文本的處理。它的底層語義是函數或方法調用, 所以, 如果標識符后面的第一個字符是左圓括號, 那么它要么是方法調用, 要么是函數調用:

<number(4)> 等價于 <number=&number(4)>

<number(2, 0..49)> 等價于 <number=&number(2, 0..49)>

multi token number ($digits)        {  <[0..9]> ** {$digits} }
multi token number ($digits, $test) { (<[0..9]> ** {$digits}) <?{ +$0 ~~ $test }> }

在擴展的 <...> 語法中, 一個前置的 ?{!{ 標示著代碼斷言:

(<[0..9]> ** {$digits}) <?{ +$0 ~~ $test }>

等價于:

(<[0..9]> ** {$digits}) { +$0 ~~ $test or fail }  # + 強制后面的$0為數值上下文, 以匹配 $test 中的數字

上面的兩句代碼, 具名 regex, token, 或 rule 是一個子例程, 所以可以傳遞 參數給具名 token。

這從標準輸入里讀取散亂的日期并把對應的 ISO 日期寫到標準輸出。

它能解析任務描述中的所有日期(包含擴展), 還有 - 然而, 在它們中我得到 4 個人不同結果。請弄清它們是否是錯誤的, 并且為什么是錯的:


2010-dec-7 --> 我得到 2010-12-07 而不是 2010-12-01

last week --> 我得到 2014-12-17 而不是 2014-12-15

1 month from 2016-01-31 --> 我得到 2016-02-29 而不是 2016-02-28

9 weeks from yesterday --> 我得到 2015-02-24 而不是 2015-02-25

有人在評論中問他 make/made 是類中的方法嗎?

是的, 它們是 Match 類的方法。

Match objects(注意 object 是復數)


每個 regex match(并且通過擴展, 每個 grammar token match)的結果被表示為一個 Match 對象。

通過這個對象你能訪問各種信息片段:

  • 匹配到的字符串

  • 關于輸入字符串匹配的開始和結束位置

  • 每個位置捕獲和具名捕獲的sub-matches

  • 與這個匹配有關的 AST 片段, 如果有的話

    AST 片段

token/rule 里面調用 make, 設置將會與當前匹配關聯的 "AST 片段"。然后, 你可以通過在當前結果 Match 對象身上調用 .made 方法來獲取那個關聯數據。

這正是自由形式的插槽, 允許你使用 Match 對象存儲任何你想要的東西并在以后檢索它, 盡管顯而易見這意味著像我那樣創建一個 AST。

在 grammar 中創建 "AST"

在我的 grammar 中每個 token/rule 使用 .made 來取得它的 sub-rule 匹配制造的數據塊(AST), 把它們組合成一個更大的數據塊, 這是為了讓它的父級 rule (parent rule) 能被檢索。等等。

我在每個 token/rule 里面使用這些語法簡寫來引用 sub-matches 的 Match 對象:

  • $0 是指第一個位置子匹配(由 () 捕獲組引起)的Match對象。
  • $<date> 指的是名字為 "date" 的具名 sub-match 的 Match 對象 (通過 <date> 遞歸引用 名為 date 的 token 引起).
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,923評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,740評論 3 420
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,856評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,175評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,931評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,321評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,383評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,533評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,082評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,891評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,618評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,319評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,732評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,987評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,794評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,076評論 2 375

推薦閱讀更多精彩內容