コンテキスト依存の方法で区切り文字で文字列を分割します

StackOverflow https://stackoverflow.com/questions/1019756

  •  06-07-2019
  •  | 
  •  

質問

たとえば、分割したい

str = '"a,b,c",d,e,f'

into

["a,b,c",'d','e','f']

(引用部分を分割しない)この場合、これは

で行うことができます
re.findall('".*?"|[^,]+',str)

ただし、次の場合

str = '"a,,b,c",d,,f'

欲しい

["a,,b,c",'d','','f']

i.e。 pythonのsplit関数のような動作が必要です。おそらくPythonのreライブラリを使用して、これを1つの(小さな)行で行う方法はありますか?

実際、csvモジュールは自分のやりたいことに完璧だと(このサイトで)気づきましたが、reを使用して同様に使用できる正規表現があるかどうか興味があります。

役に立ちましたか?

解決

re.split(',(?=(?:[^"]*"[^"]*")*[^"]*$)', str)

コンマの一致後、前方に奇数の引用符がある場合、コンマは引用符のペア内にある必要があるため、区切り文字としてカウントされません。明らかに、これはエスケープされた引用符の可能性を考慮していませんが、必要に応じて処理できます-正規表現をすでに約2倍twiceくします。 :D

他のヒント

実際のパーサーであるcsvモジュールを使用します。正規表現は、ルールが変更される区切り文字のマッチングを含むほとんどの事柄に対して最適ではありません(または完全に不適切です)(この特定の文法が正規かどうかはわかりません)。この場合は機能する正規表現を作成できるかもしれませんが、かなり複雑になります(特に、「彼は言った、\"お元気ですか\""」のようなケースに対処します)。

このためにステートマシンを記述することは、非常に簡単です。 DFAと正規表現は同じ能力を持っていますが、通常はそれらのうちの1つが目前の問題により適していて、通常、実装する必要のある追加のロジックに大きく依存しています。

フリードルの Mastering Regular Expressions の271ページには、引用される可能性のあるCSVフィールドを抽出するための正規表現がありますが、少し後処理が必要です:

>>> re.findall('(?:^|,)(?:"((?:[^"]|"")*)"|([^",]*))',str)
[('a,b,c', ''), ('', 'd'), ('', 'e'), ('', 'f')]
>>> re.findall('(?:^|,)(?:"((?:[^"]|"")*)"|([^",]*))','"a,b,c",d,,f')
[('a,b,c', ''), ('', 'd'), ('', ''), ('', 'f')]

冗長フラグを使用した同じパターン:

csv = re.compile(r"""
    (?:^|,)
    (?: # now match either a double-quoted field
        # (inside, paired double quotes are allowed)...
        " # (double-quoted field's opening quote)
          (    (?: [^"] | "" )*    )
        " # (double-quoted field's closing quote)
    |
      # ...or some non-quote/non-comma text...
        ( [^",]* )
    )""", re.X)

貪欲でない指定子を使用して、親しくなります。最も近いのは:

>>> re.findall('(".*?"|.*?)(?:,|$)',  '"a,b,c",d,e,f')
['"a,,b,c"', 'd', '', 'f', '']

しかし、ご覧のとおり、最後に冗長な空の文字列があります。これは、文字列がコンマで終わる場合に得られる結果と区別できません:

>>> re.findall('(".*?"|.*?)(?:,|$)', '"a,b,c",d,e,f,')
['"a,,b,c"', 'd', '', 'f', '']

そのため、最後に手動で調整する必要があります-

matches = regex,findall(s)
if not s.endswith(","): matches.pop()

または

matches = regex.findall(s+",")[:-1]

おそらくもっと良い方法があります。

タスクを実行する関数は次のとおりです。

def smart_split(data, delimiter=","):
    """ Performs splitting with string preservation. This reads both single and
        double quoted strings.
    """
    result = []
    quote_type = None
    buffer = ""
    position = 0
    while position < len(data):
        if data[position] in ["\"", "'"]:
            quote_type = data[position]
            while quote_type is not None:
                position += 1
                if data[position] == quote_type:
                    quote_type = None
                    position += 1
                else:
                    buffer += data[position]
        if data[position] == delimiter:
            result.append(buffer)
            buffer = ""
        else:
            buffer += data[position]
        position += 1
    result.append(buffer)
    return result

使用例:

str = '"a,b,c",d,e,f'
print smart_split(str)
# Prints: ['a,b,c', 'd', 'e', 'f']

同じことを行う本当に短い関数を次に示します。

def split (aString):
    splitByQuotes = (",%s,"%aString).split('"')
    splitByQuotes[0::2] = [x.split(",")[1:-1] for x in splitByQuotes[0::2]]
    return [a.strip() \
        for b in splitByQuotes \
        for a in (b if type(b)==list else [b])]

引用符がある文字列を分割し、すべての偶数要素が引用符の外側にあり、奇数要素がすべて引用符内にカプセル化されたものであるリストを作成します。引用符で囲まれたものはそのままで、外側のものはコンマがある場所で分割されます。これで、リストと文字列が交互に並んだリストができました。最後の行で展開します。文字列の先頭をコンマでラップし、中央のコンマを削除する理由は、リスト内の空の要素が空になるのを防ぐためです。空白を処理できるはずです-最後にstrip()関数を追加してきれいな出力を生成しましたが、それは必要ありません。

使用法:

>>> print split('c, , "a,,b,c",d,"moo","f"')
['c', '', 'a,,b,c', 'd', 'moo', 'f']
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top