記事スピナーの正規表現の作成方法
質問
次のようなものがあるとしましょう:
{{こんにちは|こんにちは|こんにちは} {world | earth} | {Goodbye | farewell} {noobs | n3wbz | n00blets}}
そして、私はそれを次のいずれかに変えたい:
Hello world
Goodbye noobs
Hi earth
farewell n3wbz
// etc.
「スピニング」の方法に注意を払う構文はネストされています。私たちが知っている限りでは、10億層の深さにネストできます。
上記の例のようにネストすると、正規表現が混乱し、結果が正しくないことを除いて、これを簡単に行うことができます。
.NET言語またはPythonのいずれかで例を示していただけますか?
解決
re.subn を使用した簡単な方法は、置換文字列の代わりに関数を受け入れることもできます。
import re
from random import randint
def select(m):
choices = m.group(1).split('|')
return choices[randint(0, len(choices)-1)]
def spinner(s):
r = re.compile('{([^{}]*)}')
while True:
s, n = r.subn(select, s)
if n == 0: break
return s.strip()
これは単に、満たす最も深い選択をすべて置き換え、選択がなくなるまで繰り返します。 subn
は、結果と置換の数を含むタプルを返します。これは、処理の終了を検出するのに便利です。
select()
の私のバージョンは、 random.choice()
を使用するBobinceのものに置き換えることができ、ランダムセレクターだけにしたい場合はよりエレガントです。選択ツリーを構築する場合は、上記の関数を拡張できますが、現在の場所を追跡するためにグローバル変数が必要になるため、関数をクラスに移動することは理にかなっています。これは単なるヒントです。実際には元々の質問ではなかったので、このアイデアは開発しません。
最後に、Unicode文字列が必要な場合は r.subn(select、s、re.U)
を使用する必要があることに注意してください( s = u" {...}"
)
例:
>>> s = "{{Hello|Hi|Hey} {world|earth} | {Goodbye|farewell} {noobs|n3wbz|n00blets}}"
>>> print spinner(s)
'farewell n3wbz'
編集: sub
を subn
に置き換えて無限ループを回避し(指摘してくれたBobinceに感謝)、さらに効率的にします。 {([^ {}] +)}
を {([^ {}] *)}
に置き換えて、空の中括弧も抽出しました。これにより、不正な形式のパターンに対してより堅牢になります。
可能な限り一行に収めたい人向け(個人的にはお勧めしません):
def spin(s):
while True:
s, n = re.subn('{([^{}]*)}',
lambda m: random.choice(m.group(1).split("|")),
s)
if n == 0: break
return s.strip()
他のヒント
かなり単純で、ブレースセットに別のブレースセットを含めることを禁止してから、内側の一致から置換を行うことを繰り返し呼び出します:
def replacebrace(match):
return random.choice(match.group(1).split('|'))
def randomizebraces(s):
while True:
s1= re.sub(r'\{([^{}]*)\}', replacebrace, s)
if s1==s:
return s
s= s1
>>> randomizebraces('{{Hello|Hi|Hey} {world|earth}|{Goodbye|farewell} {noobs|n3wbz|n00blets}}')
'Hey world'
>>> randomizebraces('{{Hello|Hi|Hey} {world|earth}|{Goodbye|farewell} {noobs|n3wbz|n00blets}}')
'Goodbye noobs'
この正規表現インバータは pyparsing を使用して、一致する文字列を生成します(+および*などの無制限の繰り返し記号は許可されません)。 {}を()に置き換えて元の文字列を正規表現にすると、インバーターは次のリストを生成します。
Helloworld
Helloearth
Hiworld
Hiearth
Heyworld
Heyearth
Goodbyenoobs
Goodbyen3wbz
Goodbyen00blets
farewellnoobs
farewelln3wbz
farewelln00blets
(スペースが折りたたまれていることは知っていますが、このコードはこの問題を攻撃する方法についてのアイデアを提供してくれるでしょう。)
re.finditerを使用して基本的な解析ツリーを構築し、ネストレベルを決定します。それを行うには、正規表現一致オブジェクトのspan属性を使用します。
text = '{{Hello|Hi|Hey} {world|earth} | {Goodbye|farewell} {noobs|n3wbz|n00blets}}'
import re
re_bracks = re.compile(r'{.+?}')
# subclass list for a basic tree datatype
class bracks(list):
def __init__(self, m):
self.m = m
# icky procedure to create the parse tree
# I hate these but don't know how else to do it
parse_tree = []
for m in re_bracks.finditer(text):
if not this_element:
# this first match
parse_tree.extend(element(m))
else:
# ... and all the rest
this_element = bracks(m)
this_start, this_end = m.span()
# if this match is nested in the old one ...
if this_start < previous_start and this_end > previous_end:
# nest it inside the previous one
previous_element.extend(this_element)
else:
# otherwise make it a child of the parse_tree
parse_tree.extend(element(m))
previous_element = this_element
previous_start, previous_end = this_start, this_end
これにより、括弧で囲まれた式のネストの深さがわかります。パイプに同様のロジックをいくつか追加すると、問題を解決できるようになります。
インスピレーションについては、 dadaエンジンをご覧になることをお勧めします。
これに触発されたものをスキームで実装し、スキームのASTを活用してニーズを表現しました。
具体的には、一般的にパーサーとして正規表現を使用しようとすることを強くお勧めします。