web-dev-qa-db-ja.com

複数の単語境界区切り文字を使用して文字列を単語に分割する

私がやりたいことはかなり一般的な作業だと思いますが、Web上で参考文献が見つかりませんでした。句読点付きのテキストがあり、単語のリストが必要です。

"Hey, you - what are you doing here!?"

あるべき

['hey', 'you', 'what', 'are', 'you', 'doing', 'here']

しかし、Pythonのstr.split()は1つの引数でしか動作しないため、空白で区切った後はすべての単語に句読点が付いています。何か案は?

583
ooboo

正規表現が正当化される場合

import re
DATA = "Hey, you - what are you doing here!?"
print re.findall(r"[\w']+", DATA)
# Prints ['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']
404
RichieHindle

re.split()

re.split(パターン、文字列[、maxsplit = 0])

文字列をpatternの出現箇所で分割します。キャプチャ括弧がpatternで使用されている場合は、パターン内のすべてのグループのテキストも結果リストの一部として返されます。 maxsplitがゼロ以外の場合、最大でmaxsplit分割が行われ、残りの文字列はリストの最後の要素として返されます。 (非互換性に関するメモ:オリジナルのPython 1.5リリースでは、maxsplitは無視されていました。これは後のリリースで修正されました。)

>>> re.split('\W+', 'Words, words, words.')
['Words', 'words', 'words', '']
>>> re.split('(\W+)', 'Words, words, words.')
['Words', ', ', 'words', ', ', 'words', '.', '']
>>> re.split('\W+', 'Words, words, words.', 1)
['Words', 'words, words.']
487
gimel

正規表現なしでこれを行うもう1つの簡単な方法は、以下のように、最初に文字を置き換えることです。

>>> 'a;bcd,ef g'.replace(';',' ').replace(',',' ').split()
['a', 'bcd', 'ef', 'g']
325
Louis LC

非常に多くの回答がありますが、質問の title が文字通り求めるものを効率的に解決する解決策を見つけることができません。異なります)。それで、ここにタイトルの質問に対する答えがあります、それはPythonの標準的で効率的なreモジュールに頼ります:

>>> import re  # Will be splitting on: , <space> - ! ? :
>>> filter(None, re.split("[, \-!?:]+", "Hey, you - what are you doing here!?"))
['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']

ここで、

  • […] one にマッチしています。
  • 正規表現の\-は、(-のように)A-Zが文字範囲インジケーターとして特別に解釈されないようにするためのものです。
  • +は、1つの またはそれ以上の 区切り文字をスキップします(filter()のおかげで省略できますが、一致した区切り文字の間に不必要に空の文字列が生成される)
  • filter(None, …)は、先頭と末尾の区切り文字によって生成された可能性がある空の文字列を削除します(空の文字列は偽のブール値を持つため)。

質問のタイトルで要求されているように、このre.split()は正確に "複数のセパレータで分割"されています。

この解決方法は、他の解決方法に見られる単語中の非ASCII文字に関する問題に対してさらに影響を受けません( ghostdog74の答え への最初のコメントを参照)。

reモジュールは、Pythonのループやテストを「手動で」行うよりもはるかに効率的です(速度と簡潔さにおいて)。

254
Eric O Lebigot

正規表現なしの別の方法

import string
punc = string.punctuation
thestring = "Hey, you - what are you doing here!?"
s = list(thestring)
''.join([o for o in s if not o in punc]).split()
53
ghostdog74

Pro-Tip:Pythonが持っている最も速い文字列操作にはstring.translateを使ってください。

いくつかの証拠...

まず、遅い方法(すみませんpprzemek):

>>> import timeit
>>> S = 'Hey, you - what are you doing here!?'
>>> def my_split(s, seps):
...     res = [s]
...     for sep in seps:
...         s, res = res, []
...         for seq in s:
...             res += seq.split(sep)
...     return res
... 
>>> timeit.Timer('my_split(S, punctuation)', 'from __main__ import S,my_split; from string import punctuation').timeit()
54.65477919578552

次に、私たちはre.findall()を使います(提案された答えによって与えられるように)。はるかに高速:

>>> timeit.Timer('findall(r"\w+", S)', 'from __main__ import S; from re import findall').timeit()
4.194725036621094

最後に、translateを使います。

>>> from string import translate,maketrans,punctuation 
>>> T = maketrans(punctuation, ' '*len(punctuation))
>>> timeit.Timer('translate(S, T).split()', 'from __main__ import S,T,translate').timeit()
1.2835021018981934

説明:

string.translateはCで実装されており、Pythonの多くの文字列操作関数とは異なり、string.translateしないは新しい文字列を生成します。そのため、文字列の置換を実行するのと同じくらいの速さです。

ただし、この魔法を実行するためには変換テーブルが必要なので、少し厄介です。 maketrans()便利な関数を使って変換テーブルを作ることができます。ここでの目的は、不要な文字をすべてスペースに変換することです。一対一の代用品です。また、新しいデータは生成されません。だからこれは速いです!

次に、古き良きsplit()を使います。 split()はデフォルトですべての空白文字を処理し、それらを分割のためにまとめます。結果はあなたが望む単語のリストになります。そしてこのアプローチはre.findall()よりもほぼ4倍高速です。

37
Dave

ちょっと遅い答え:)、しかし私は同じようなジレンマを持っていて 're'モジュールを使いたくありませんでした。

def my_split(s, seps):
    res = [s]
    for sep in seps:
        s, res = res, []
        for seq in s:
            res += seq.split(sep)
    return res

print my_split('1111  2222 3333;4444,5555;6666', [' ', ';', ','])
['1111', '', '2222', '3333', '4444', '5555', '6666']
23
pprzemek
join = lambda x: sum(x,[])  # a.k.a. flatten1([[1],[2,3],[4]]) -> [1,2,3,4]
# ...alternatively...
join = lambda lists: [x for l in lists for x in l]

それからこれはスリーライナーになります:

fragments = [text]
for token in tokens:
    fragments = join(f.split(token) for f in fragments)

説明

これがHaskellでListモナドとして知られているものです。モナドの背後にある考え方は、「モナドの中に」いったん何かがあなたを連れ出すまで「あなたは「モナドの中にとどまる」ということです。たとえばHaskellで、Pythonのrange(n) -> [1,2,...,n]関数をListにマッピングするとしましょう。結果がリストの場合は、リストに追加されますので、map(range, [3,4,1]) -> [0,1,2,0,1,2,3,0]のようになります。これはmap-append(またはmappend、あるいはおそらくそのようなもの)として知られています。ここでの考えは、あなたが適用しているこの操作(トークンで分割する)を持っているということです、そしてあなたがそれをする時はいつでも、あなたは結果をリストに結合します。

これを関数に抽象化し、デフォルトでtokens=string.punctuationを持つことができます。

このアプローチの利点:

  • このアプローチは(単純な正規表現ベースのアプローチとは異なり)任意の長さのトークン(正規表現はより高度な構文でも実行できます)で機能します。
  • あなたは単なるトークンに制限されていません。各トークンの代わりに任意のロジックを使用することができます。たとえば、「トークン」の1つは、括弧のネストの仕方によって分割される関数になります。
10
ninjagecko

まず、正規表現やstr.translate(...)ベースのソリューションが最もパフォーマンスが良いということで、私は他の人々と同意したいと思います。私のユースケースでは、この関数のパフォーマンスはそれほど重要ではなかったので、その基準で検討したアイデアを追加したいと思いました。

私の主な目標は、他のいくつかの答えからのアイデアを、単なる正規表現の単語以上のものを含む文字列に対して有効な1つの解決策に一般化することです。

どんな方法でも、手動で定義されたリストの代わりにstring.punctuationを使うことを検討するかもしれないことに注意してください。

オプション1 - re.sub

今のところ答えがないのを見て驚いたのは、 re.sub(...) です。私はそれがこの問題への単純で自然なアプローチだと思います。

import re

my_str = "Hey, you - what are you doing here!?"

words = re.split(r'\s+', re.sub(r'[,\-!?]', ' ', my_str).strip())

このソリューションでは、re.sub(...)の内側にre.split(...)の呼び出しを入れ子にしました - ただし、パフォーマンスが重要な場合は、正規表現を外側にコンパイルすることが有益な場合があります。

オプション2 - str.replace

これはもう少し行ですが、正規表現で特定の文字をエスケープする必要があるかどうかをチェックしなくても拡張できるという利点があります。

my_str = "Hey, you - what are you doing here!?"

replacements = (',', '-', '!', '?')
for r in replacements:
    my_str = my_str.replace(r, ' ')

words = my_str.split()

代わりにstr.replaceを文字列にマッピングできればいいのですが、不変の文字列ではできないと思いますし、文字のリストに対してマッピングしてもすべての文字に対してすべての置換が実行されるでしょう。過度に聞こえます。 (編集:機能的な例については次のオプションを見てください。)

オプション3 - functools.reduce

(Python 2では、reduceはfunctoolsからインポートせずにグローバル名前空間で利用可能です。)

import functools

my_str = "Hey, you - what are you doing here!?"

replacements = (',', '-', '!', '?')
my_str = functools.reduce(lambda s, sep: s.replace(sep, ' '), replacements, my_str)
words = my_str.split()
9
Taylor Edmiston

これを試して:

import re

phrase = "Hey, you - what are you doing here!?"
matches = re.findall('\w+', phrase)
print matches

これは['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']を表示します

4
Corey Goldberg

Reモジュール関数re.splitを使う代わりに、パンダのseries.str.splitメソッドを使って同じ結果を得ることができます。

まず、上記の文字列でシリーズを作成してから、そのシリーズにメソッドを適用します。

thestring = pd.Series("Hey, you - what are you doing here!?") thestring.str.split(pat = ',|-')

パラメータ pat は区切り文字を取り、分割文字列を配列として返します。ここでは、2つの区切り文字は|を使用して渡されます。 (またはオペレータ)。出力は以下のとおりです。

[Hey, you , what are you doing here!?]

4

Replaceを2回使用します。

a = '11223FROM33344INTO33222FROM3344'
a.replace('FROM', ',,,').replace('INTO', ',,,').split(',,,')

結果は次のとおりです。

['11223', '33344', '33222', '3344']
4
jeroen

私は re が好きですが、これがない私の解決策は次のとおりです。

from itertools import groupby
sep = ' ,-!?'
s = "Hey, you - what are you doing here!?"
print [''.join(g) for k, g in groupby(s, sep.__contains__) if not k]

sep .__ contains__ は、in演算子によって使用されるメソッドです。基本的には同じです

lambda ch: ch in sep

しかしここではもっと便利です。

groupby は文字列と関数を取得します。それはその関数を使って文字列をグループに分割します:functionの値が変わるといつでも - 新しいグループが生成されます。だから、 sep .__ contains__ がまさに必要なものです。

groupby はペアのシーケンスを返します。ここで、pair [0]は関数の結果、pair [1]はグループです。 'kでなければ' を使用して、区切り記号を使用してグループを除外します( sep .__ contains__ の結果は区切り記号でTrueになります)。これですべてです。これで、各グループがWordである一連のグループができました(グループは実際には反復可能なので、文字列に変換するには join を使用します)。

この解決法は、文字列を分離するために関数を使用するので、非常に一般的です(あなたが必要とする任意の条件で分割することができます)。また、中間の文字列やリストは作成されません( join を削除すると、各グループが反復子になるため、式が遅延する可能性があります)。

4
monitorius

私は自分自身をPythonに精通していて、同じことが必要でした。 findallの解決策はより良いかもしれませんが、私はこれを思い付きました:

tokens = [x.strip() for x in data.split(',')]
3
Leon Starr

Python 3では、 PY4E - Python for Everybody のメソッドを使うことができます。

これらの問題を解決するには、文字列メソッドlowerpunctuation、およびtranslateを使用します。 translateは最も微妙なメソッドです。これがtranslateのドキュメントです。

your_string.translate(your_string.maketrans(fromstr, tostr, deletestr))

fromstr内の文字をtostr内の同じ位置にある文字で置き換え、deletestr内にあるすべての文字を削除します。 fromstrおよびtostrは空ストリングにすることができ、deletestrパラメーターは省略することができます。

あなたは "句読点"を見ることができます:

In [10]: import string

In [11]: string.punctuation
Out[11]: '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'  

あなたの例では:

In [12]: your_str = "Hey, you - what are you doing here!?"

In [13]: line = your_str.translate(your_str.maketrans('', '', string.punctuation))

In [14]: line = line.lower()

In [15]: words = line.split()

In [16]: print(words)
['hey', 'you', 'what', 'are', 'you', 'doing', 'here']

詳しくは、以下を参照してください。

3
Jeremy Anifacc

maketransを使用して翻訳することで、簡単かつきちんとそれを行うことができます

import string
specials = ',.!?:;"()<>[]#$=-/'
trans = string.maketrans(specials, ' '*len(specials))
body = body.translate(trans)
words = body.strip().split()
2
Ritesh Sinha

2つの文字列(分割するソース文字列と区切り文字のsplitlist文字列)を入力として受け取り、分割した単語のリストを出力する関数を作成します。

def split_string(source, splitlist):
    output = []  # output list of cleaned words
    atsplit = True
    for char in source:
        if char in splitlist:
            atsplit = True
        else:
            if atsplit:
                output.append(char)  # append new Word after split
                atsplit = False
            else: 
                output[-1] = output[-1] + char  # continue copying characters until next split
    return output
1
user852006

これを実現するもう1つの方法は、Natural Language Tool Kit( nltk )を使用することです。

import nltk
data= "Hey, you - what are you doing here!?"
Word_tokens = nltk.tokenize.regexp_tokenize(data, r'\w+')
print Word_tokens

これは、印刷されます:['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']

この方法の最大の欠点は、 nltkパッケージをインストールする必要があることです

トークンを入手したら、残りのnltkパッケージで たくさんの楽しいこと を実行できるという利点があります。

1
tgray

まず第一に、ループでRegEx操作を実行する前に必ずre.compile()を使用してください。これは通常の操作よりも速く動作するからです。

だからあなたの問題のために最初にパターンをコンパイルしそしてそれからアクションを実行する。

import re
DATA = "Hey, you - what are you doing here!?"
reg_tok = re.compile("[\w']+")
print reg_tok.findall(DATA)
1
shrikant

まず第一に、あなたの意図は実際に分割関数の区切り文字として句読点を使用することではないと思います。あなたの説明は、結果として得られる文字列から句読点を単に削除したいことを示唆しています。

私はこれをかなり頻繁に遭遇します、そして私の通常の解決策はreを必要としません。

リスト内包を含む1線形ラムダ関数

import stringが必要です):

split_without_punc = lambda text : [Word.strip(string.punctuation) for Word in 
    text.split() if Word.strip(string.punctuation) != '']

# Call function
split_without_punc("Hey, you -- what are you doing?!")
# returns ['Hey', 'you', 'what', 'are', 'you', 'doing']


機能(トラディショナル)

伝統的な関数として、これはまだ(import stringに加えて)リスト内包を持つ2行だけです:

def split_without_punctuation2(text):

    # Split by whitespace
    words = text.split()

    # Strip punctuation from each Word
    return [Word.strip(ignore) for Word in words if Word.strip(ignore) != '']

split_without_punctuation2("Hey, you -- what are you doing?!")
# returns ['Hey', 'you', 'what', 'are', 'you', 'doing']

それはまた自然に収縮とハイフンで囲まれた言葉をそのままにしておくでしょう。分割の前にハイフンをスペースに変換するには、常にtext.replace("-", " ")を使用できます。

ラムダまたはリスト理解なしの一般関数

より一般的な解決策(排除する文字を指定できる場合)については、そしてリストの内包表記なしでは、次のようになります。

def split_without(text: str, ignore: str) -> list:

    # Split by whitespace
    split_string = text.split()

    # Strip any characters in the ignore string, and ignore empty strings
    words = []
    for Word in split_string:
        Word = Word.strip(ignore)
        if Word != '':
            words.append(Word)

    return words

# Situation-specific call to general function
import string
final_text = split_without("Hey, you - what are you doing?!", string.punctuation)
# returns ['Hey', 'you', 'what', 'are', 'you', 'doing']

もちろん、ラムダ関数はいつでも指定された文字列に一般化することができます。

1
cosmicFluke

これは、説明付きの答えです。

st = "Hey, you - what are you doing here!?"

# replace all the non alpha-numeric with space and then join.
new_string = ''.join([x.replace(x, ' ') if not x.isalnum() else x for x in st])
# output of new_string
'Hey  you  what are you doing here  '

# str.split() will remove all the empty string if separator is not provided
new_list = new_string.split()

# output of new_list
['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']

# we can join it to get a complete string without any non alpha-numeric character
' '.join(new_list)
# output
'Hey you what are you doing'

または一行で、我々はこのようにすることができます:

(''.join([x.replace(x, ' ') if not x.isalnum() else x for x in st])).split()

# output
['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']

更新された答え

1
Tasneem Haider

私は以下があなたのニーズを満たすための最良の答えだと思います:

\W+はおそらくこの場合に適していますが、他の場合には適していない可能性があります。

filter(None, re.compile('[ |,|\-|!|?]').split( "Hey, you - what are you doing here!?")
0
nemozhp

これが、複数の区切り文字を使った分割です。

def msplit( str, delims ):
  w = ''
  for z in str:
    if z not in delims:
        w += z
    else:
        if len(w) > 0 :
            yield w
        w = ''
  if len(w) > 0 :
    yield w
0
Martlark

@oobooと同じ問題を抱えていて、@ ghostdog74が私に影響を与えたというこのトピックを見つける

str1='adj:sg:nom:m1.m2.m3:pos'
splitat=':.'
''.join([ s if s not in splitat else ' ' for s in str1]).split()

スペースで分割したくない場合は、スペースで何かを入力し、同じ文字を使用して分割してください。

0
badas

私がこれまでにテストしたすべてがある時点で失敗したので、私は私自身の解決策を考え出す必要がありました。

>>> import re
>>> def split_words(text):
...     rgx = re.compile(r"((?:(?<!'|\w)(?:\w-?'?)+(?<!-))|(?:(?<='|\w)(?:\w-?'?)+(?=')))")
...     return rgx.findall(text)

少なくとも以下の例では、うまくいっているようです。

>>> split_words("The hill-tops gleam in morning's spring.")
['The', 'hill-tops', 'gleam', 'in', "morning's", 'spring']
>>> split_words("I'd say it's James' 'time'.")
["I'd", 'say', "it's", "James'", 'time']
>>> split_words("tic-tac-toe's tic-tac-toe'll tic-tac'tic-tac we'll--if tic-tac")
["tic-tac-toe's", "tic-tac-toe'll", "tic-tac'tic-tac", "we'll", 'if', 'tic-tac']
>>> split_words("google.com [email protected] split_words")
['google', 'com', 'email', 'google', 'com', 'split_words']
>>> split_words("Kurt Friedrich Gödel (/ˈɡɜːrdəl/;[2] German: [ˈkʊɐ̯t ˈɡøːdl̩] (listen);")
['Kurt', 'Friedrich', 'Gödel', 'ˈɡɜːrdəl', '2', 'German', 'ˈkʊɐ', 't', 'ˈɡøːdl', 'listen']
>>> split_words("April 28, 1906 – January 14, 1978) was an Austro-Hungarian-born Austrian...")
['April', '28', '1906', 'January', '14', '1978', 'was', 'an', 'Austro-Hungarian-born', 'Austrian']
0
Wood
def get_words(s):
    l = []
    w = ''
    for c in s.lower():
        if c in '-!?,. ':
            if w != '': 
                l.append(w)
            w = ''
        else:
            w = w + c
    if w != '': 
        l.append(w)
    return l

使い方は次のとおりです。

>>> s = "Hey, you - what are you doing here!?"
>>> print get_words(s)
['hey', 'you', 'what', 'are', 'you', 'doing', 'here']
0
inspectorrr

私はreplace()のやり方が一番好きです。次の手順では、文字列splitlistに定義されているすべての区切り文字をsplitlist内の最初の区切り文字に変更してから、その区切り文字でテキストを分割します。 splitlistがたまたま空の文字列であるかどうかも考慮します。空の文字列のない単語のリストを返します。

def split_string(text, splitlist):
    for sep in splitlist:
        text = text.replace(sep, splitlist[0])
    return filter(None, text.split(splitlist[0])) if splitlist else [text]

これが私の考えです….

def split_string(source,splitlist):
    splits = frozenset(splitlist)
    l = []
    s1 = ""
    for c in source:
        if c in splits:
            if s1:
                l.append(s1)
                s1 = ""
        else:
            print s1
            s1 = s1 + c
    if s1:
        l.append(s1)
    return l

>>>out = split_string("First Name,Last Name,Street Address,City,State,Zip Code",",")
>>>print out
>>>['First Name', 'Last Name', 'Street Address', 'City', 'State', 'Zip Code']

私は最近これを行う必要がありましたが、標準ライブラリのstr.split関数といくらか一致する関数が欲しかった、この関数は0または1の引数で呼ばれたとき標準ライブラリと同じように振る舞います。

def split_many(string, *separators):
    if len(separators) == 0:
        return string.split()
    if len(separators) > 1:
        table = {
            ord(separator): ord(separator[0])
            for separator in separators
        }
        string = string.translate(table)
    return string.split(separators[0])

_ note _ :この関数は、セパレータが単一の文字で構成されている場合にのみ役立ちます(私のユースケースのように)。

0
justinfay

私はpprzemekの解決法が好きです。デリミタが単一の文字であることを想定しておらず、正規表現を利用しようとしないからです(セパレータの数が長くなりすぎるとうまくいきません)。

わかりやすくするために、上記の解決策をより読みやすくしたものを次に示します。

def split_string_on_multiple_separators(input_string, separators):
    buffer = [input_string]
    for sep in separators:
        strings = buffer
        buffer = []  # reset the buffer
        for s in strings:
            buffer = buffer + s.split(sep)

    return buffer
0
Everett

可逆的な操作が必要な場合(区切り記号を保存してください)、この関数を使用することができます。

def tokenizeSentence_Reversible(sentence):
    setOfDelimiters = ['.', ' ', ',', '*', ';', '!']
    listOfTokens = [sentence]

    for delimiter in setOfDelimiters:
        newListOfTokens = []
        for ind, token in enumerate(listOfTokens):
            ll = [([delimiter, w] if ind > 0 else [w]) for ind, w in enumerate(token.split(delimiter))]
            listOfTokens = [item for sublist in ll for item in sublist] # flattens.
            listOfTokens = filter(None, listOfTokens) # Removes empty tokens: ''
            newListOfTokens.extend(listOfTokens)

        listOfTokens = newListOfTokens

    return listOfTokens
0
Nadav B