問題へのアプローチを次に示します (正規表現を使用していませんが、正規表現を使用できる場所が 1 つあります)。問題を 2 つの関数に分割します。1 つは文字列をカンマ区切りの断片に分割し、各断片を処理するparseTags
関数 ( )、もう 1 つは文字列を取得して有効なタグに処理する関数 ( sanitizeTag
) です。注釈付きのコードは次のとおりです。
# This function takes a string with commas separating raw user input, and
# returns a list of valid tags made by sanitizing the strings between the
# commas.
def parseTags(str):
# First, we split the string on commas.
rawTags = str.split(',')
# Then, we sanitize each of the tags. If sanitizing gives us back None,
# then the tag was invalid, so we leave those cases out of our final
# list of tags. We can use None as the predicate because sanitizeTag
# will never return '', which is the only falsy string.
return filter(None, map(sanitizeTag, rawTags))
# This function takes a single proto-tag---the string in between the commas
# that will be turned into a valid tag---and sanitizes it. It either
# returns an alphanumeric string (if the argument can be made into a valid
# tag) or None (if the argument cannot be made into a valid tag; i.e., if
# the argument contains only whitespace and/or punctuation).
def sanitizeTag(str):
# First, we turn non-alphanumeric characters into whitespace. You could
# also use a regular expression here; see below.
str = ''.join(c if c.isalnum() else ' ' for c in str)
# Next, we split the string on spaces, ignoring leading and trailing
# whitespace.
words = str.split()
# There are now three possibilities: there are no words, there was one
# word, or there were multiple words.
numWords = len(words)
if numWords == 0:
# If there were no words, the string contained only spaces (and/or
# punctuation). This can't be made into a valid tag, so we return
# None.
return None
elif numWords == 1:
# If there was only one word, that word is the tag, no
# post-processing required.
return words[0]
else:
# Finally, if there were multiple words, we camel-case the string:
# we lowercase the first word, capitalize the first letter of all
# the other words and lowercase the rest, and finally stick all
# these words together without spaces.
return words[0].lower() + ''.join(w.capitalize() for w in words[1:])
実際、このコードを実行すると、次のようになります。
>>> parseTags("tHiS iS a tAg, \t\n!&#^ , secondcomment , no!punc$$, ifNOSPACESthenPRESERVEcaps")
['thisIsATag', 'secondcomment', 'noPunc', 'ifNOSPACESthenPRESERVEcaps']
このコードには、明確にする価値のあるポイントが 2 つあります。まず in の使い方str.split()
ですsanitizeTags
。これは に変わりますa b c
が['a','b','c']
、str.split(' ')
生成されるのは['','a','b','c','']
です。これはほぼ間違いなくあなたが望む動作ですが、1 つのコーナー ケースがあります。文字列を考えてみましょうtAG$
。は$
スペースに変わり、分割によって取り除かれます。したがって、これはtAG
の代わりに になりtag
ます。これはおそらくあなたが望むものですが、そうでない場合は注意が必要です。私がすることは、その行を に変更することですwords = re.split(r'\s+', str)
。これにより、文字列が空白で分割されますが、先頭と末尾の空の文字列が残ります。ただし、使用するように変更parseTags
することもできますrawTags = re.split(r'\s*,\s*', str)
。これらの両方の変更を行う必要があります。'a , b , c'.split(',') becomes ['a ', ' b ', ' c']
、そうではありません必要な動作ですがr'\s*,\s*'
、コンマの周りのスペースも削除します。先頭と末尾の空白を無視しても、違いは重要ではありません。そうでない場合は、注意が必要です。
最後に、正規表現を使用せず、代わりにstr = ''.join(c if c.isalnum() else ' ' for c in str)
. 必要に応じて、これを正規表現に置き換えることができます。(編集: Unicode と正規表現に関する不正確な部分をここで削除しました。) Unicode を無視すると、この行を次のように置き換えることができます。
str = re.sub(r'[^A-Za-z0-9]', ' ', str)
これは、リストされた文字 (ASCII 文字と数字)以外[^...]
のすべてに一致するために使用されます。ただし、Unicode をサポートする方が優れており、それも簡単です。そのような最も単純なアプローチは、
str = re.sub(r'\W', ' ', str, flags=re.UNICODE)
ここでは、\W
単語以外の文字に一致します。単語の文字は、文字、数字、またはアンダースコアです。指定するとflags=re.UNICODE
(Python 2.7 より前では使用できません。代わりr'(?u)\W'
に以前のバージョンと2.7 で使用できます)、文字と数字はどちらも適切な Unicode 文字です。それがなければ、それらは単なる ASCII です。アンダースコアが必要ない場合は|_
、正規表現に追加してアンダースコアも一致させ、それらもスペースに置き換えることができます。
str = re.sub(r'\W|_', ' ', str, flags=re.UNICODE)
この最後のものは、正規表現を使用しないコードの動作と正確に一致すると思います。
また、これらのコメントなしで同じコードを書く方法は次のとおりです。これにより、いくつかの一時変数を削除することもできます。変数が存在するコードを好むかもしれません。それはただの好みの問題です。
def parseTags(str):
return filter(None, map(sanitizeTag, str.split(',')))
def sanitizeTag(str):
words = ''.join(c if c.isalnum() else ' ' for c in str).split()
numWords = len(words)
if numWords == 0:
return None
elif numWords == 1:
return words[0]
else:
return words[0].lower() + ''.join(w.capitalize() for w in words[1:])
新たに望ましい動作を処理するには、2 つのことを行う必要があります。まず、最初の単語の大文字化を修正する方法が必要です。最初の文字が小文字の場合は全体を小文字にし、最初の文字が大文字の場合は最初の文字以外をすべて小文字にします。それは簡単です。直接確認するだけです。第二に、句読点を完全に見えないように扱いたい: 後続の単語を大文字にすべきではありません。繰り返しますが、これは簡単です。上記と同様のものを処理する方法についても説明します。非英数字、非空白文字をスペースに変換するのではなく、すべて除外します。これらの変更を組み込むことで、
def parseTags(str):
return filter(None, map(sanitizeTag, str.split(',')))
def sanitizeTag(str):
words = filter(lambda c: c.isalnum() or c.isspace(), str).split()
numWords = len(words)
if numWords == 0:
return None
elif numWords == 1:
return words[0]
else:
words0 = words[0].lower() if words[0][0].islower() else words[0].capitalize()
return words0 + ''.join(w.capitalize() for w in words[1:])
このコードを実行すると、次の出力が得られます
>>> parseTags("tHiS iS a tAg, AnD tHIs, \t\n!&#^ , se@%condcomment$ , No!pUnc$$, ifNOSPACESthenPRESERVEcaps")
['thisIsATag', 'AndThis', 'secondcomment', 'NopUnc', 'ifNOSPACESthenPRESERVEcaps']