python での「忘れがち」シリーズ:「contains any of these」、re.finditer

純粋に Python だけのネタは久々なのである。

ひとつめ。

なんつーか、必要になるたびに「あうあ、またこのパターンか」と思っている、とある「一撃必殺技がないよくある処理」の話。ずっと思ってたけど、ちょっと今更気付いたこともあったので、一応書き留めておこうかと。

見出しの通り「これらのいずれかが含まれろうかいや?」判定なんだけどね、なんで「気分悪い」と感じるかといえば、こっちは「一撃」だからなの:

似て非なるもの
1 >>> s = "apple"
2 >>> # s はペンかパイナップかアッポーか?
3 >>> s in ("pen", "pineapple", "apple")
4 True

「これらのうちのいずれか」という判定ね、これは。

けれども、「ペンかパイナップかアッポーが含まれるか?」は?

似て非なるもの
1 >>> s = "penpineappleapplepen"
2 >>> # ペンパイナッッポーか?
3 >>> any([t in s for t in ("pen", "pineapple", "apple")])
4 True
5 >>> all([t in s for t in ("pen", "pineapple", "apple")])
6 True

まぁ内包表現と any、all などのサポートのおかげで多少は自己満足に足るのだけれど、どっちにしたって「自力でループる」必要があることには変わりはない。「containsAny」があればいいのに、て話だ。

とまぁずっと思ってきたのだけれど、そして、シーケンス一般に関してはそれは揺るがない事実ではあるのだけれど、ただふと、文字列(またはバイト列)についてだけは、よくよく考えたら、正規表現でもいいのだよね:

1 >>> import re
2 >>> s = "penpineappleapplepen"
3 >>> # ペンパイナッッポーか?
4 >>> re.search("(pen|pineapple|apple)", s)
5 <re.Match object; span=(0, 3), match='pen'>

なんというか、一日中コーディングしているとしたなら、それこそ一時間以内に一回とかみたいに頻繁に必要になるものだと思うんだけれど、「あ、そうか、正規表現でいいじゃん」と思いついた記憶がかなり少ない。ちょっと強めに意識に刻んでおいたほうがいいな、と思った、て話でしたとさ。

もう一つの re.finditer の件は、忘れて別のものに頼ってしまうかどうかはなんとなく半々な気がする。

こちらを忘れてしまう原因は「発想力」とは関係ないところで起こってた、ワタシの場合。何かつーと、「findall はつかえねー」という記憶だけが強く刻まれてるから、なのよね。

「マッチしたもん全部くれぃ」というニーズの半分は、実に「マッチした文字列をくれぃ」だけではないんだわ、きっと半分くらいは「マッチした位置もくれぃ」なんだよ。つまり Match オブジェクトの列挙を返してくれるものがかなり多くのケースで「欲しいもの」なのだけど、findall が「(Match オブジェクトでなく)マッチした文字列全部を返す」であることにガッカリし、「仕方なく re.search を繰り返すコードを書く」としてしまう。

実はまさに「声優世代表のおとも」ネタで、最初やっちまったのよね。結果的に採用したのはこれ:

 1 import re
 2 
 3 def _split(s, seprgx, minlen=1):
 4     result = []
 5     st = 0
 6     for m in re.finditer(seprgx, s):
 7         e = m.span()[0]
 8         ns = s[st:e]
 9         if len(ns.encode("utf-8")) >= minlen:
10             result.append(ns)
11             st = m.span()[1]
12     if st < len(s):
13         result.append(s[st:])
14     return result

最初間違って書いてしまった(しかもワタシの意図と違う間違ったことしてる)のはこれ:

 1 import re
 2 
 3 def _split(s, seprgx, minlen=1):
 4     rx = re.compile(seprgx)
 5     result = []
 6     pos, st = 0, 0
 7     m = rx.search(s)
 8     while m and m.span() != (0, 0):
 9         e = m.span()[0]
10         if len(s[st:pos + e].encode("utf-8")) >= minlen:
11             ns = s[st:pos + e]
12             result.append(ns)
13             st = pos + m.span()[1]
14         pos += m.span()[1]
15         m = rx.search(s[pos:])
16     if st < len(s):
17         result.append(s[st:])
18     return result

posのトラッキングを自力るのは繊細で間違いやすいのよ、finditer を思い出せなかったことによる損失は結構デカい。

どちらのケースについても、その問題に「久しぶり」に出くわした場合に、「強く印象に残っている記憶」に引きずられてしまう、という点で共通してるかもしれんね。前者は「そういうもんはない」と思い過ぎてる、てことだし、後者は「findall はつかえねー」の方だけ記憶しちゃってる、てことだから。

で、「明日のオレ」としては。「contains any of these パターンには re もおともにどーぞ」+「findall だけ記憶すんなや、finditer はセットぞ」の2つを、強く、強く印象づけておくこと、てことな。