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

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

新規登録して質問してみよう
ただいま回答率
85.48%
正規表現

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

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

3回答

2125閲覧

正規表現でのマッチが期待通りにならない

NCC1701

総合スコア1680

正規表現

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

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

1クリップ

投稿2019/10/04 01:42

前提・実現したいこと

文字列からキーワードを抽出するために、正規表現でのマッチを試みています。が、期待した結果を得られません。正規表現での式が下手なところに原因があるはずですが、どのように式を組み立てれば良いか教えてください。

python

1a = "cat:AA|a" 2b = "cat:AA;BBB|b" 3c = "cat:AA;BBB;CCCC|c"

キーワードはカテゴリ名のあと:の後から始まり;で区切られ|で終わります。
上記の文字列、a, b, c があったとして、cat:のあと、AAなどのキーワード自体が可変長で、なおかつ、キーワードの個数も可変長です(上の場合aはキーワード1個、bはキーワード2個などのように)

発生している問題・エラーメッセージ

python

1import re 2a = "cat:AA|a" 3b = "cat:AA;BBB|b" 4c = "cat:AA;BBB;CCCC|c" 5regex = re.compile("^([^:]+):([^;|]+);?([^;|]+)?") 6re.findall(regex, a);re.findall(regex, b);re.findall(regex, c) 7 8>>> 9[('cat', 'AA', '')] # <- 最後尾の長さゼロの文字列''が不要 10[('cat', 'AA', 'BBB')] 11[('cat', 'AA', 'BBB')] # <- 'CCCC'が取得できてない

期待している結果

python

1('cat', 'AA') 2('cat', 'AA', 'BBB') 3('cat', 'AA', 'BBB', 'CCCC')

補足情報(FW/ツールのバージョンなど)

python 3.7

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

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

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

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

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

guest

回答3

0

二段階に分けて考えると楽です。

  • まず : と | の間を抽出して、
  • 次に ; で分割

Python

1for src in [a, b, c]: 2 ret = re.search(r'([a-z]+):([A-Z;]+)\|', src) 3 if not ret: 4 continue 5 6 ret = [ret.group(1), *ret.group(2).split(';')] 7 print(ret)

仕様に依っては次のように書いても良いでしょう。

Python

1for src in [a, b, c]: 2 ret = re.findall(r'[:;]?([a-zA-Z]+)(?=[:;|])', src) 3 print(ret)

あるいは、これくらい単純化できるかもしれません。

Python

1for src in [a, b, c]: 2 ret = re.split(r'[:;|]', src)[:-1] 3 print(ret)

投稿2019/10/04 01:51

編集2019/10/04 01:59
LouiS0616

総合スコア35660

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

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

0

最初は、つぎのようにするとよいです。

  1. 入力文字列をその意味を解釈して内部的なデーア後続に変換
  2. その青での処理ようにデータ後続を変換する。

zz.py

pythpn3

1import re 2 3def parse(line): 4 m = re.search(r"^(.*)\:(.*)\|(.*)?", line) 5 return { 6 "cat": m.group(1), 7 "tags": sorted(set(m.group(2).split(";"))), 8 "name": m.group(3) 9 } 10 11a = "cat:AA|a" 12b = "cat:AA;BBB|b" 13c = "cat:AA;BBB;CCCC|c" 14d = "cat:Z;CCCC;BBB;A;BBB;CCCC|d" 15 16# dict に変換する 17ary = map(parse, (a, b, c, d)) 18 19# 表示用に整形する 20ary2 = map(lambda x: [x["cat"]] + [t for t in x["tags"]], ary) 21print(list(ary2)) 22

実行例
イメージ説明

ここでは キーワードの意味をすこしだけ考慮して、重複を除去する / ソートする といったことも行っています。

開映と変に分けて行うのは処理が冗長になりがちです。
この質問の場合なら、直接 表示用のデーアにしてしまうようなことも考えてのよいかもしれません。
(ただし、将来的に 入寮データの変化、処理用データ後続の変化があったときに対応しにくくなります)

zz0.py

python3

1import re 2 3a = "cat:AA|a" 4b = "cat:AA;BBB|b" 5c = "cat:AA;BBB;CCCC|c" 6 7ary = map(lambda x: re.sub(":|;", "|", x).split("|")[:-1], [a, b, c])

実行例:
イメージ説明

区切り文字をすべて "|" にしてしまってから、 split して 最後の単語を取り除くということで実現させてしまっています。

なお、この回答例では入力データのエラーチェックをしていません。
エラーチェックをしだすと、コードはもっと複雑になっていきます。

投稿2019/10/04 22:17

katoy

総合スコア22324

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

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

0

ベストアンサー

こんにちは

キャプチャしないグループ (?:・・・)を使って、以下でどうでしょう?

Python3

1regex = re.compile("(?:^[^:]+)|(?:[^:;|]+)|(?:[^;|]+$)")

以下は、Repl.it に作成した動作確認用のコードです。

画面上部中央のrunボタンをクリックすると実行され、以下のように、結果が右側のコンソールに出力されます。

イメージ説明

追記

質問者さまから頂きましたコメントにより見直しまして、より短い正規表現の以下でも、意図通りのリストが得られます。

python

1regex = re.compile("[^:;|]+")

投稿2019/10/04 03:54

編集2019/10/04 07:02
jun68ykt

総合スコア9058

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

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

NCC1701

2019/10/04 06:43

ありがとうございます。ご回答くださった内容で目的は達成できているのですが、キャプチャしないグループ化について、まだ理解が及んでいないので、もう少しお付き合いください。 ドキュメントでは説明に「マッチを実行したあとで回収することも、そのパターン中で以降参照することも できません 。」とあるのにre.findallではしっかりと結果を得られています。これは「回収している」ことにはならないのでしょうか?(後方参照については理解してます) 試しに(?:を外した regex = re.compile("^.*?:|[^:;|]+|[^;|]+$") でやったら、同じ結果を得られました。なので、ますますわからなくなっております。 よろしくお願いします。
jun68ykt

2019/10/04 06:59

コメントありがとうございます。 確かに仰るとおりで、 regex = re.compile("^.*?:|[^:;|]+|[^;|]+$") でOKでしたので、キャプチャしないグループ化は不要でした。グループ化したのは | で区切られる各パートを ( ) でくくらないと意図通りにならないかなと思ったためです。 さらにもっと詰めると、以下でもいけそうです。 regex = re.compile("[^:;|]+") https://repl.it/@jun68ykt/Q215284-2 > ドキュメントでは説明に「マッチを実行したあとで回収することも、そのパターン中で以降参照することも できません 。」とある とのことですが、その一文はどこに書かれたものでしょうか?Pythonの公式ドキュメントの findall の説明 https://docs.python.org/ja/3/library/re.html#re.findall には、そのように書かかれていないので、引用元が気になった次第です。
NCC1701

2019/10/04 07:50

回答ありがとうございます。 引用元は 正規表現のシンタックス の項目です https://docs.python.org/ja/3/library/re.html#regular-expression-syntax findallの場合はマッチしたらどんどん結果のリストに加えていくのでしょうか?findallの振る舞いが私が考えているのと違う気がしています。関連しそうな質問もありました https://teratail.com/questions/212731 キャプチャしないグループ化や先読み後読みは、使いこなせるようになれると1UPした気になれそうです。
jun68ykt

2019/10/04 10:30

ご返信ありがとうございます。 > findallの場合はマッチしたらどんどん結果のリストに加えていくのでしょうか? はい。そのような理解で、ドキュメント のfindallの項にある re.findall(pattern, string, flags=0) string 中の pattern による全ての重複しないマッチを、文字列のリストとして返します。 string は左から右へ走査され、マッチは見つかった順で返されます。 という説明と齟齬はないので、良いかと思います。 > 引用元は 正規表現のシンタックス の項目です そちら拝読しまして、キャプチャしないグループの説明に 「マッチを実行したあとで回収することも、そのパターン中で以降参照することも できません 。」 と書かれているのを確認しました。 これについて、まず、キャプチャしないグループを使う効用を確認しますと、私の回答に書いた(追記ではないほうの)コードで使った正規表現は以下 regex = re.compile("(?:^[^:]+)|(?:[^:;|]+)|(?:[^;|]+$)") ですが、これの、キャプチャしないグループ (?: ・・・) を、キャプチャする(通常の)グループに修正して regex = re.compile("(^[^:]+)|([^:;|]+)|([^;|]+$)") とすると、結果は、 [('cat', '', ''), ('', 'AA', ''), ('', 'a', '')] [('cat', '', ''), ('', 'AA', ''), ('', 'BBB', ''), ('', 'b', '')] [('cat', '', ''), ('', 'AA', ''), ('', 'BBB', ''), ('', 'CCCC', ''), ('', 'c', '')] というものになります。(確認用: https://repl.it/@jun68ykt/Q215284-3 ) このような結果になるのは、 https://docs.python.org/ja/3/library/re.html#re.findall に以下のように説明されています。 「パターン中に 1 つ以上のグループがあれば、グループのリストを返します。パターンに複数のグループがあればタプルのリストになります。」 上記の結果の差異をふまえて、キャプチャしないグループの説明に 「マッチを実行したあとで回収することも、そのパターン中で以降参照することも できません 。」 とあるにも関わらず、 regex = re.compile("(?:^[^:]+)|(?:[^:;|]+)|(?:[^;|]+$)") で意図通りの結果になることが疑問とのことでしたが、 findall が収拾してくるのは、各 (?: ・・・ ) にマッチしたものではなく、 (?:^[^:]+)|(?:[^:;|]+)|(?:[^;|]+$) という正規表現全体にマッチした部分だからです。 今回のように、正規表現自体の中に様々な工夫をするというのも考え所ですが、もうひとつ考えるべきなのは、その正規表現がプログラミング言語の正規表現ライブラリの各種メソッドでどのように使われどのような結果になるのかという、プログラムからの利用の仕方という点です。とはいえ、これは暗記するようなものではないので、都度都度、試行していって、正規表現でやればうまくできそうな課題があったときに、正規表現自体の組み方と、その組み方だと正規表現ライブラリの提供するメソッドのどれを使えば上手くいくか、ということについての直感や着想が上達されていくものと思います。 > キャプチャしないグループ化や先読み後読みは、使いこなせるようになれると1UPした気になれそうです。 はい。なれますよ! さらにUPしようと思えば、リンクを挙げてくださっている https://docs.python.org/ja/3/library/re.html#regular-expression-syntax の中に以下の一文 「正規表現に関する理論と実装の詳細については Friedl 本 [Frie09] か、コンパイラの構築に関するテキストを参照してください。」 がありますが、ここで挙げられている参考文献の Friedl 本 [Frie09] の日本語訳は以下です。 https://www.oreilly.co.jp/books/9784873113593/ もし上記をまだお読みになったことがなければ、これの少なくとも第3章まで、できれば第6章までを読むと、かなり UPします。ただしこれを読むと、先読み、後読み、キャプチャしないグループといった技を駆使したくなり、ときどき、今回の私の回答のような、実はもっと簡単な正規表現で出来たという、"滑り方" をしてしまうという副作用がありますが、一読をお勧めします。
NCC1701

2019/10/04 10:59

ありがとうございました。とても勉強になりました
jun68ykt

2019/10/04 11:12

どういたしまして。解決されたようで、よかったです ????
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問