質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

正規表現

正規表現とは特定の文字列によるパターンマッチングを行う際に用いられる宣言型プログラミングです。

Q&A

解決済

2回答

1263閲覧

python 正規表現でのfindallについて

bonji

総合スコア37

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

正規表現

正規表現とは特定の文字列によるパターンマッチングを行う際に用いられる宣言型プログラミングです。

0グッド

0クリップ

投稿2019/04/21 02:38

始めまして、プログラミング初心者です。
早速ですが、ご質問です。以下の正規表現のコードをjupyter Notebook(windows10)で実行しています。

python3

1import re 2 3text = "3 1,234 12,34,567 6,368,745" 4 5num_regex = re.compile(r"(\d{,3}(,\d{3})*)") 6mo = num_regex.findall(text) 7print(text) 8 9>>>[('3', ''), 10 ('', ''), 11 ('1,234', ',234'), 12 ('', ''), 13 ('12', ''), 14 ('', ''), 15 ('34,567', ',567'), 16 ('', ''), 17 ('6,368,745', ',745'), 18 ('', '')] 19 20

このコードで実現したいことは、 三桁ごとにカンマが付いた数字のみに一致させたいです。

一致したい例は、上では 3 1,234 6,368,745
一致して欲しくない例は、上では12,34,567 他では、1234です。

なって欲しいイメージは

[("3",""),("1,234",",234"),("6,368,745",",368,745")] です。

また、上記のコードの実行結果('6,368,745', ',745')の最後がなぜ、,368,745にならないのでしょうか?
回答の程よろしくお願いします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

仰るような動作をさせたいのであれば、御呈示の正規表現に対して以下 3 点の修正が必要かと存じます。

1) 0 文字ではマッチしないよう、パターンに最低 1 文字の制限を入れる

現状のパターンでは、パターン全体が 空の文字列に対しても真となる ため、一つ結果が得られる度、続く 0 文字にマッチして ('', '') という値が得られてしまっています。少なくとも 1 文字以上の数字が必須であることを示すため、 \d{,3}\d{1,3} へと変更したほうが良いでしょう。

2) 数字の一部を取り出してしまわないよう、区切り文字を明示する

現状のパターンでは、 どこからどこまでがひとまとまりの数字か が明示されていないため、数字の 一部分でもパターンに一致してしまえば正しいと見做されてしまう 状態です。その結果、 12,34,567 という値が 1234,567 の二つに分ければ OK と判断され取り出されてしまっています。

具体的には、例えばパターン先頭に (?:^| ) (文字列先頭または半角スペースに一致) を、パターン末尾に (?=$| ) (文字列末尾または半角スペースが一文字先に存在) を入れると良いのではないでしょうか。

※ここでパターン末尾で肯定先読みを使っているのは、単純にマッチさせてしまうと、次の数字のまとまりの判定時、先頭に期待する半角スペースを既に前のマッチで消費済みとして見失ってしまうためです

3) パターン繰り返しの括弧と、取り出しの括弧を分ける

6,368,745 の第二グループが ,745 になっているのは、パターンを繰り返すための括弧をそのまま取り出している為です。実際、御書きのパターンでは 括弧の中が ,\d{3} となっているわけですから、これにマッチするのは ,368 または ,745 のどちらかになる 、と申し上げれば御分り頂けますでしょうか。このうち最後にマッチした,745 が結果として返ってきています。

この部分は、本当に取り出したいのが パターンを繰り返した後の全体なのであれば、繰り返しを含めて括弧で囲んで取り出す 書き方としましょう。つまり、 (,\d{3})* となっている部分を ((?:,\d{3})*) として、アスタリスクまでを含んだ部分を囲ってやれば良いかと思います。

x) 無理な「正規表現一発」に拘らない

以上の修正を加えると、パターンは (?:^| )(\d{1,3}((?:,\d{3})*))(?=$| )という形になり、これで恐らく希望の動作をしてくれるはずです。

但し、修正 2 点目で述べた通り、 結局のところ数字と数字の区切りはどこかという情報が必要 なのですから、無理につながった状態で正規表現に掛けようとせず、その区切りで 分けた後に一つ一つ調べていった方が簡単で分かりやすい ように思います。例えば、次のようなコードにしてやれば、あまり正規表現の複雑さに悩まず必要な数字だけを取り出すことが可能です。

python

1mo = [ 2 num for num in text.split(' ') 3 if re.match(r'^\d{1,3}(,\d{3})*$', num) 4]

仮に数字の区切りが正規表現でなければ扱えないほどに複雑であったとしても、「数字を分ける正規表現」と「正しさを判定する正規表現」との 2 回に分けた方が簡単です。特別な理由のない限り、単純なやり方を組み合わせることを推奨致します。

投稿2019/04/21 06:16

argparse

総合スコア1017

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

まず、\d{,3}という書き方はしてはいけません。
m文字以内を表す{,m}なんて量指定子は存在しない。…わけではない。

それからargparseさんもおっしゃっている 「無理な「正規表現一発」に拘らない」は非常に重要なことです。パターンマッチングは複数回に分けてもいいしsplitとかと組み合わせてもいいんです。無理やりな正規表現を使おうとしてハマってしまうのは全く時間と労力の無駄です。

投稿2019/04/21 06:37

KojiDoi

総合スコア13671

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

LouiS0616

2019/04/21 06:45 編集

リンク先の記事が一部誤っているようだったので、向こうにコメントしておきました。 少なくともPythonの正規表現は{,m}をカバーしています。
KojiDoi

2019/04/21 07:01

なるほど。しかし、各言語の独自拡張ということで、やはり避けたほうがいいように思います。実はperlで検証してみようとしてハマりました(perlでは%d{,m}は通常文字列扱いになるので結果が全く違ってしまう)。
bonji

2019/04/21 10:24

回答ありがとうございます。 やはり、一つの正規表現でマッチを狙うのはプログラマーとしてはダメなのですね。 これを機に、無理な正規表現の一発マッチをしないようにします。 本当にありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問