トラック
/
Factor
Factor
/
演習
/
台帳の見張り番
台帳の見張り番

台帳の見張り番

学習演習

はじめに

基本はもう身についていますね。\dや[A-Z]のような文字クラス、{4}や+のような量指定子、そしてmatches?、all-matching-subseqs、re-replaceというワードです。この演習では、正規表現のうち構造と文脈を表す部分を扱います。それらも同じregexpボキャブラリーにあり、同じR/ /リテラルで書きます。

アンカー

アンカーは、文字ではなく位置にマッチします。

^    ! the start of the string
$    ! the end of the string
\b   ! a word boundary

$は文字列の終わりを意味するので、文字としてのドル記号を表したいときは\$と書きます。

単語境界(\b)とは、単語文字とそうでない文字が接する位置です。単語文字とは、英字、数字、アンダースコアのことです。それ以外(空白、句読点、文字列の両端)はすべて境界とみなされます。つまり\bcat\bは、catが単独で現れるときにだけマッチします。

USING: regexp ;

"cat nap" R/ \bcat\b/ re-contains? .   ! => t
"scatter" R/ \bcat\b/ re-contains? .   ! => f   (cat sits inside a word)

グループ化と選択

丸括弧(( ))はパターンの一部をグループ化し、量指定子や|(「これまたはあれ」)をグループ全体に適用できるようにします。

USING: regexp ;

"abab" R/ (ab)+/ matches? .                ! => t   (+ applies to "ab")
"dog"  R/ cat|dog/ matches? .              ! => t   (either word)
"$100.50" R/ \$\d+(\.\d{2})?/ matches? .   ! => t
"$100"    R/ \$\d+(\.\d{2})?/ matches? .   ! => t   (the group is optional)

最後のパターンは、「ドル記号、1桁以上の数字、その後にドットと2桁の数字からなる省略可能なグループ」という意味です。

注意:Factorでは、グループはパターンを構造化するだけです。一部の正規表現ツールと違い、グループがマッチしたテキストを引き出すことはできません。キャプチャグループの抽出はできず、\1のような後方参照もサポートされていません。目印の周りのテキストを取り出したいときは、代わりにルックアラウンド(後述)を使ってください。

ルックアラウンド:文脈でマッチし、消費しない

ルックアラウンドは、現在の位置の直前または直後にあるものを調べますが、その文字をマッチの一部にはしません。これは「ゼロ幅」で、判定した内容は結果に含まれません。

(?=...)    ! lookahead:           ... must follow
(?!...)    ! negative lookahead:  ... must NOT follow
(?<=...)   ! lookbehind:          ... must come before
(?<!...)   ! negative lookbehind: ... must NOT come before

ルックアラウンドは選択的です。同じ文字でも、隣に何があるかによってマッチしたりしなかったりします。単なる\d+はすべての数値を拾いますが、ルックビハインドとルックアヘッドを使うと、隣が条件を満たすものだけが残ります。

USING: regexp ;

"3 for $10 or 10 for $30" R/ \d+/ all-matching-subseqs .
! => { "3" "10" "10" "30" }   (every number)

"3 for $10 or 10 for $30" R/ (?<=\$)\d+/ all-matching-subseqs .
! => { "10" "30" }            (only the prices — the bare 3 and 10 drop)

"buy 2 at 15% off, 4 at 30% off" R/ \d+(?=%)/ all-matching-subseqs .
! => { "15" "30" }            (only the discounts — the bare 2 and 4 drop)

オプション

閉じ/のあとに文字を付けると、パターン全体の動作を変えられます。iを付けると大文字と小文字を区別しなくなります。

USING: regexp ;

"REFUND"  R/ refund/i matches? .       ! => t
"Refund"  R/ refund/i matches? .       ! => t

各マッチを変換する

all-matching-subseqsはマッチしたテキストを渡してくれます。map-matchesはもう一歩進んで、各マッチに対してクオテーションを実行し、その結果を集めます。クオテーションはマッチのstartインデックス、endインデックス、そして文字列全体を受け取ります。subseqがその3つからマッチしたテキストを作り、それを変換します。

map-matches ( string regexp quot: ( start end string -- obj ) -- seq )
USING: math regexp sequences ;

"a1 b22 c333" R/ \d+/ [ subseq length ] map-matches .
! => { 1 2 3 }     (the length of each run of digits)

副作用だけを目的とした仲間のeach-matchは、各マッチに対してクオテーションを実行しますが、何も集めません。

説明

小さな会社の帳簿を付けていると、毎日の取引は整っていないテキストの行として届きます。ここでは正規表現を使って金額を検証し、目印となる記号ごとに数値を抜き出し、よく確認する必要がある行に印を付けます。

1. 金額を検証する

正しい形式の金額は、$、1つ以上の数字、そして省略可能な小数部分でできています。小数部分は、ドットとそれに続くちょうど2桁の数字です。$100や$100.50は正しい形式ですが、$100.5はそうではありません。valid-amount?を定義し、文字列の全体が正しい形式の金額であるときにtを返すようにしましょう。

"$100.50" valid-amount? .   ! => t
"$100"    valid-amount? .   ! => t
"$100.5"  valid-amount? .   ! => f

2. ドル金額を抜き出す

dollar-amountsを定義し、その行で$の直後に続くすべての数値を順番に返すようにしましょう。返すのは数字だけで、$は含みません。

"spent $100 and $25 today" dollar-amounts .
! => { "100" "25" }

3. パーセントを抜き出す

percentagesを定義し、直後に%が続くすべての数値を返すようにしましょう。

"up 5% then down 12%" percentages .
! => { "5" "12" }

4. 行に印を付ける

flagged?を定義し、その行にrefundまたはchargebackが大文字と小文字のどのような組み合わせで含まれていてもtを返すようにしましょう。

"Issued a REFUND today" flagged? .   ! => t
"a normal sale"         flagged? .   ! => f
GitHubで編集する リンクは新しいウィンドウまたはタブで開きます
Factor Exercism

台帳の見張り番を始める準備はできましたか?

Exercismに登録すれば、47個のコンセプト163個の演習、そして本物の人間によるメンタリングとともに、Factorを学んでマスターできます。すべて無料です。