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

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

ただいまの
回答率

88.80%

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

解決済

回答 2

投稿

  • 評価
  • クリップ 0
  • VIEW 1,676

bonji

score 37

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

import re

text = "3 1,234 12,34,567 6,368,745"

num_regex = re.compile(r"(\d{,3}(,\d{3})*)")
mo = num_regex.findall(text)
print(text)

>>>[('3', ''),
    ('', ''),
    ('1,234', ',234'),
    ('', ''),
    ('12', ''),
    ('', ''),
    ('34,567', ',567'),
    ('', ''),
    ('6,368,745', ',745'),
    ('', '')]


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

一致したい例は、上では 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にならないのでしょうか?
回答の程よろしくお願いします。

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+3

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+1

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/04/21 15:44 編集

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

    キャンセル

  • 2019/04/21 16:01

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

    キャンセル

  • 2019/04/21 19:24

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

    キャンセル

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

  • ただいまの回答率 88.80%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る