#お題
__+12 345(6789)0123__のような電話番号を拾った時に、先頭の+
はそのままに、数字だけを拾いたい、みたいなことを考えたがググっても出なかったので記事にします。
Perl使いなので文中のコードはPerlですが、他の言語でも一緒でしょう。知らんけど。
#実装に必要な知識
##「数字以外の文字」にマッチする\D
\d
は皆さんご存知だと思います。その否定が\D
。要するに[^\d]
。
とりあえず電話番号の区切りを半角スペースに統一したい場合は大雑把にやるとこう。
$phone =~ s/\D/ /g;
しかしこれだと文字列の先頭に(
や+
がある時にそれも空白に変えてしまう。
##否定の先読みの表現(?!)
以下は私なりの理解。
要するに、__肯定のパターン(?=)
の否定が(?!)
と理解するのが一番混乱が少ない。
「特定の文字列(パターンから)始まず」かつ「そのあとに__続く何か」を拾うには、(?!\A)(\w+)
などとする。
##文字列の先頭\A
および行の先頭^
これに関する違いを解説した記事を見つけました。どうやら、__\A
の方がマッチするパターンが狭い__らしい。
通常、先頭にマッチするには^
で用が足りるけど、今回のような案件にはこのメタ文字への理解が必須。
今回のお題では別に^
で構わないようです。1
\a
はベル文字が先に定義されているため、\A
を否定するには(?!\A)
または(?!^)
となる。
「先頭ではなく、数字でもない文字」にマッチさせるには(?!\A)(\D+)
または(?!^)(\D+)
と、「先頭の否定」と「それに連なる数字の否定」を表現する。
#実装
use feature qw(say);
use strict;
use warnings;
my $phone = optimize(' +81 0120(893)893 ');
say $phone; # +81 0120 893 893
use Unicode::Japanese;
my $value = Unicode::Japanese->new(' +81 0120(893)893 ')->z2h->get;
$phone = optimize($value);
say $phone; # +81 0120 893 893
exit;
sub optimize {
my $phone = shift;
$phone =~ s/^ //s; # まずは冒頭のスペースを取り除く
$phone =~ s/(?:(?!\A)\D|\()+/ /sg;
#↑ 「先頭ではなく、数字でもない文字」または単純に`(`が先頭にある場合スペースに置換。
$phone =~ s/^ //s; # 再び冒頭のスペースを取り除く
$phone =~ s/ $//s; # 末尾のスペースを取り除く
return $phone;
}
#募集
「それ一行でできるぜ?」って猛者がおりましたらコメントや編集リクエストをお願いいたします。
#募集結果
@indometacin さんのアプローチ(JavaScript)
「数字直後の数字ではないキャラクタを削除」すれば良いので\A
を使う必要がそもそもない、ということが判明。目から鱗のコードでした。
console.log('+81 123(456)789'.replace(/(\d)\D+/g, '$1'));
//+81123456789
だけど誰かが\A
や(?!\A)
について調べたいと思った時のためにこの記事はそのままにしておきます。
##@earthdiver1 さんのアプローチ(Perl)
###(?|pattern)
(branch resetパターン)を使うワンライナー
$phone =~ s/(?|^\s*(\+?)\D*|())(\d+)\D+/$1$2 /g;
末尾に半角スペースがついちゃう以外は完璧。
$phone =~ s/ $//s; # 末尾のスペースを取り除く
ま、簡単に取れますのでこれでいいような。
###(?<NAME>pattern)(名前付きキャプチャ)を使うワンライナー
前述のbranch resetパターンが使えない環境でもこうは書ける
$phone =~ s/(?:^\s*(?<p>\+?)\D*|(?<p>))(\d+)\D+/$+{p}$3 /g;
これもやっぱり末尾にスペースがあった場合残る。