1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【正規表現】先頭を除く、文字列中の記号を置換する

Last updated at Posted at 2020-01-15

#お題

__+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+)と、「先頭の否定」と「それに連なる数字の否定」を表現する。

#実装

regexp.pl
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;

これもやっぱり末尾にスペースがあった場合残る。

  1. akajiroさんからTwitterでご指摘いただきました。

1
0
9

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?