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

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

ただいまの
回答率

88.81%

Pythonで正規表現を使って似たような文字列を複数個取得する際に、それを取得した場所によって場合分けを行うことができるのかどうか

解決済

回答 2

投稿

  • 評価
  • クリップ 1
  • VIEW 367

nsmt

score 17

※まずタイトルがこれでいいのかどうか不安なので、もしこのタイトルよりもこの質問に適切だと思われるタイトルがございましたらそれについてもコメントいただけると嬉しいです。

前提・実現したいこと

とあるゲームの棋譜からデータを抽出しようとしています。
変数textに格納された文字列から「occupation "〇〇"」の部分の〇〇を正規表現を用いてプレイヤーごとにリストの形式で取り出そうとしているのですがうまくいきません。この問題の解決に力を貸していただけると助かります。

text = '''
<table>
 <tr>
  <td>3</td>                            # 3ターン目(ここから先のコメントは全て実際のtextには含まれておりません。)
  <td>...</td>                          # player1
  <td>occupation "Clay Seller"</td>     # player2
  <td>...</td>                          # player3
 </tr>
 <tr>
  <td>2</td>                            # 2ターン目
  <td>occupation "Field Watchman"</td>  # player1
  <td>occupation "Tutor"</td>           # player2
  <td>...</td>                          # player3
 </tr>
 <tr>
  <td>1</td>                            # 1ターン目
  <td>...</td>                          # player1
  <td>occupation "Cook"</td>            # player2
  <td>...</td>                          # player3
 </tr>
</table>
'''

# 望んでいる結果:
player1 = [Field Watchman]
player2 = [Clay Seller, Tutor, Cook]
player3 = []

試したこと・発生している問題

player1 = []
try:
    for a in range(1,4):
        player1_regex = re.compile(r'''
            (<tr>.*?){%s}
                # {}内に変数aを入れて、occupation\s"(.*?)"までにある
                # (<tr>.*?)の個数でターン数を管理し、
                # forループで各ターンから「occupation "〇〇"」を抽出していこうとしている
            (<td>.*?</td>.*?){1}
                # 何ターン目かが書いてある行から、「occupation "(.*?)"」までの間にある
                # (<td>.*?</td>.*?)の個数で何番目のプレイヤーが選択した行動かを判別しようとした
            occupation\s"(.*?)"
            ''' % a, re.VERBOSE | re.DOTALL)
        player1.append(player1_regex.search(text).group(3))
except AttributeError:    # 例外処理でマッチしなかった場合(<td>...</td>となっている部分が該当する時)にエラーが出ないようにした。
    pass
print(player1)
# ['Clay Seller', 'Field Watchman', 'Cook']

player2 = []
try:
    for b in range(1,4):
        player2_regex = re.compile(r'''
            (<tr>.*?){%s}
            (<td>.*?</td>.*?){2}
            occupation\s"(.*?)"
            ''' % b, re.VERBOSE | re.DOTALL)
        player2.append(player2_regex.search(text).group(3))
except AttributeError:
    pass
print(player2)
# ['Clay Seller', 'Tutor', 'Cook']

player3 = []
try:
    for c in range(1,4):
        player3_regex = re.compile(r'''
            (<tr>.*?){%s}
            (<td>.*?</td>.*?){3}
            occupation\s"(.*?)"
            ''' % c, re.VERBOSE | re.DOTALL)
        player3.append(player3_regex.search(text).group(3))
except AttributeError:
    pass
print(player3)
# ['Field Watchman', 'Cook']


上記コードで試しましたが、結果は期待したものとは違っていました。
これがうまくいかない理由は、{}は直前のグループの内容を管理するものではあるが
それ以降の正規表現に影響を与えるものではないからだと分かりました。

次はこのように試しました。

player1 = []
try:
    player1_r1 = re.compile(r'''
        <tr>.*?
        <td>.*?</td>.*?
        occupation\s"(.*?)"
        ''', re.VERBOSE | re.DOTALL)
    player1.append(player1_r1.search(text).group(1))
    player1_r2 = re.compile(r'''
        <tr>.*?<tr>.*?
        <td>.*?</td>.*?
        occupation\s"(.*?)"
        ''', re.VERBOSE | re.DOTALL)
    player1.append(player1_r2.search(text).group(1))
    player1_r3 = re.compile(r'''
        <tr>.*?<tr>.*?<tr>.*?
        <td>.*?</td>.*?
        occupation\s"(.*?)"
        ''', re.VERBOSE | re.DOTALL)
    player1.append(player1_r3.search(text).group(1))
except AttributeError:
    pass
print(player1)
# ['Clay Seller', 'Field Watchman', 'Cook']


長いので、player1の分だけ載せました。
{}を使わずに「<tr>.*?」をそのまま連ねることで「<tr>.*?」の個数を指定して
3ターン目...該当無し...実際はClay Sellerを取得しちゃってる
2ターン目...Field Watchman...予定通りField Watchmanを取得
1ターン目...該当無し...実際はCookを取得
思い通りに動かない理由は、「<tr>.*?」1つ連ねただけだと、これが1つ以上ある時はいつでもマッチしてしまうからです。
「<tr>.*?<tr>.*?」なら、「<tr>.*?」が2つ以上ある時はマッチする。

では、どうすれば「<tr>.*?」がtextの最初から数えて1つしかない場合にしかマッチしないようにしたりするのでしょうか?
正規表現を使ったもの以外でもよいので、何か方法があれば教えてくださると助かります。
よろしくお願いします。

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 2

checkベストアンサー

+1

pandas 使って2次元テーブルのまま処理したほうが簡単な気がしますが、駄目でしょうかね?

import pandas as pd
import io

text = '''
<table>
 <tr>
  <td>3</td>
  <td>...</td>
  <td>occupation "Clay Seller"</td>
  <td>...</td>
 </tr>
 <tr>
  <td>2</td>
  <td>occupation "Field Watchman"</td>
  <td>occupation "Tutor"</td>
  <td>...</td>
 </tr>
 <tr>
  <td>1</td>
  <td>...</td>
  <td>occupation "Cook"</td>
  <td>...</td>
 </tr></table>
'''

df = pd.read_html(io.StringIO(text))[0]
df.columns = ['ターン','Player1','Player2','Player3']
# Player1~Player3の列に対して正規表現を実施
for col in ['Player1','Player2','Player3']:
    df[col] = df[col].str.extract(r'occupation\s"(.*)"')
print(df)
#   ターン         Player1      Player2 Player3
#0    3             NaN  Clay Seller     NaN
#1    2  Field Watchman        Tutor     NaN
#2    1             NaN         Cook     NaN

# Player1 を表示
print(df['Player1'].dropna().values)
#['Field Watchman']
# Player2 を表示
print(df['Player2'].dropna().values)
#['Clay Seller' 'Tutor' 'Cook']
# Player3 を表示
print(df['Player3'].dropna().values)
#[]

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/04/03 12:06

    教えていただいたコードを入れてみたところ望んでいたリストを得ることができました。
    何を勉強すればこの問題を解決できるのかさえ分からなかったので、本当に助かりました。
    今からpandasについて学び、似たような問題で躓かないようにします!
    本当にありがとうございました。

    キャンセル

0

Python は分からないのですが, 正規表現を使わなくても宜しいのでしたら, 一行ずつ読んで状況に応じて取り込めばいいだけではないでしょうか.

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/04/03 12:00

    そういった方法もあるかもしれないのですね。
    既に別の方が回答してくださった内容で問題は解決できましたが、今後のためにその方法での解決法も模索してみます。
    解答してくださり本当にありがとうございました!

    キャンセル

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

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

関連した質問

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

  • トップ
  • Python 3.xに関する質問
  • Pythonで正規表現を使って似たような文字列を複数個取得する際に、それを取得した場所によって場合分けを行うことができるのかどうか