Разделить строки на слова с несколькими разделителями слов

Я думаю, что я хочу сделать, это довольно распространенная задача, но я не нашел ссылки в Интернете. У меня есть текст с пунктуацией, и я хочу список слов.

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

должно быть

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

Но Python str.split() работает только с одним аргументом, поэтому у меня есть все слова с пунктуацией после разделения на пробел. Есть идеи?

Ответ 1

Случай, когда регулярные выражения оправданы:

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

Ответ 2

re.split()

re.split(pattern, string [, maxsplit = 0])

Разделить строку на наличие шаблонов. Если в шаблоне используются скобки, то текст всех групп в шаблоне также возвращается как часть результирующего списка. Если 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.']

Ответ 3

Еще один быстрый способ сделать это без регулярного выражения - сначала заменить символы, как показано ниже:

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

Ответ 4

Так много ответов, но я не могу найти никакого решения, которое бы эффективно выполняло то, что буквально запрашивает заголовок вопросов (разбивая на несколько возможных разделителей - вместо этого многие ответы удаляют все, что не является словом, что отличается). Так вот ответ на вопрос в заголовке, которая опирается на стандарт и эффективный 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']

где:

  • […] соответствует одному из разделителей, перечисленных внутри,
  • \- в регулярном выражении здесь для предотвращения специальной интерпретации - как индикатора диапазона символов (как в AZ),
  • + пропускает один или несколько разделителей (это может быть опущено благодаря filter(), но это излишне приведет к появлению пустых строк между согласованными разделителями), и
  • filter(None, …) удаляет пустые строки, возможно, созданные начальным и конечным разделителями (поскольку пустые строки имеют ложное логическое значение).

Этот re.split() точно "разделяется на несколько разделителей", как это было указано в заголовке вопроса.

Это решение, кроме того, неуязвимо для проблем с не-ASCII символами в словах, обнаруженных в некоторых других решениях (см. Первый комментарий к ответу ghostdog74).

Модуль re гораздо эффективнее (по скорости и краткости), чем циклы и тесты Python "вручную"!

Ответ 5

Другой способ, без регулярного выражения

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()

Ответ 6

Pro-Tip: используйте string.translate для самых быстрых операций с строками, которые Python имеет.

Некоторые доказательства...

Во-первых, медленный путь (извините 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() по умолчанию будет работать со всеми пробельными символами, группируя их для разделения. Результатом будет список слов, которые вы хотите. И этот подход почти в 4 раза быстрее, чем re.findall()!

Ответ 7

У меня была похожая дилемма, и я не хотел использовать модуль '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']

Ответ 8

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 известно как Монада Список. Идея монады состоит в том, что однажды "в монаде" вы "остаетесь в монаде", пока что-то не выведет вас. Например, в Haskell, скажем, вы сопоставляете функцию python range(n) -> [1,2,...,n] над списком. Если результатом является List, он будет добавлен в List на месте, поэтому вы получите что-то вроде map(range, [3,4,1]) -> [0,1,2,0,1,2,3,0]. Это называется map-append (или mappend, или, может быть, что-то вроде этого). Идея здесь в том, что у вас есть эта операция, которую вы применяете (разделение на токен), и всякий раз, когда вы это делаете, вы присоединяете результат к списку.

Вы можете абстрагировать это в функцию и по умолчанию tokens=string.punctuation.

Преимущества такого подхода:

  • Этот подход (в отличие от подходов, основанных на наивном регулярном выражении) может работать с токенами произвольной длины (такое регулярное выражение также может выполнять с более сложным синтаксисом).
  • Вы не ограничены только токенами; вы можете иметь произвольную логику вместо каждого токена, например, одним из "токенов" может быть функция, которая разбивается в соответствии с тем, как вложенные круглые скобки.

Ответ 9

Во-первых, я хочу согласиться с другими, что решения на основе регулярных выражений или str.translate(...) являются наиболее эффективными. Для моего варианта использования производительность этой функции была незначительной, поэтому я хотел добавить идеи, которые я рассмотрел с этими критериями.

Моя основная цель заключалась в том, чтобы обобщить идеи из некоторых других ответов на одно решение, которое могло бы работать для строк, содержащих больше, чем просто слова регулярных выражений (т.е. черный список явных подмножеств знаков препинания и символов белого текста).

Обратите внимание, что в любом подходе можно также использовать 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()

Ответ 10

попробуйте следующее:

import re

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

это напечатает ['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']

Ответ 11

Используйте два раза:

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

приводит к:

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

Ответ 12

Мне нравится 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.__ содержит __ - метод, используемый оператором 'in'. В основном это то же самое, что

lambda ch: ch in sep

но здесь более удобно.

groupby получает нашу строку и функцию. Он разбивает строку в группах, используя эту функцию: всякий раз, когда изменяется значение функции - создается новая группа. Итак, sep.__ содержит __ - это именно то, что нам нужно.

groupby возвращает последовательность пар, где пара [0] является результатом нашей функции, а пара [1] - это группа. Используя ', если не k', мы отфильтровываем группы с разделителями (потому что результат sep.__ содержит __ равен True в разделителях). Ну, это все - теперь у нас есть последовательность групп, каждая из которых является словом (группа на самом деле является итерируемой, поэтому мы используем join, чтобы преобразовать ее в строку).

Это решение довольно общее, потому что оно использует функцию для разделения строки (вы можете разделить любое условие). Кроме того, он не создает промежуточные строки/списки (вы можете удалить join, и выражение станет ленивым, так как каждая группа является итератором)

Ответ 13

Вместо использования функции re-re re.split вы можете добиться того же результата, используя метод series.str.split для pandas.

Сначала создайте серию с указанной строкой, а затем примените метод к этой серии.

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

Параметр pat принимает разделители и возвращает разделительную строку в виде массива. Здесь два разделителя передаются с использованием | (или оператором). Вывод выглядит следующим образом:

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

Ответ 14

Я снова познакомился с Python и нуждался в том же. Решение findall может быть лучше, но я придумал следующее:

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

Ответ 15

В Python 3 вы можете использовать метод из PY4E - Python для всех.

Мы можем решить обе эти проблемы, используя строковые методы lower, punctuation и 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]: '!"#$%&\'()*+,-./:;<=>[email protected][\\]^_'{|}~'  

Для вашего примера:

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']

Для получения дополнительной информации вы можете обратиться:

Ответ 16

используя maketrans и переводить, вы можете делать это легко и аккуратно

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

Ответ 17

Другой способ добиться этого - использовать набор инструментов Natural Language Tool (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, как только вы получите свои жетоны.

Ответ 18

Прежде всего, я не думаю, что ваше намерение состоит в том, чтобы фактически использовать пунктуацию в качестве разделителей в разделенных функциях. Ваше описание предполагает, что вы просто хотите исключить пунктуацию из результирующих строк.

Я встречаюсь с этим довольно часто, и для моего обычного решения не требуется re.

Единичная лямбда-функция с пониманием списка:

(требуется 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):

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']

Конечно, вы всегда можете обобщить лямбда-функцию на любую указанную строку символов.

Ответ 19

Прежде всего, всегда используйте re.compile() перед выполнением любой операции RegEx в цикле, потому что он работает быстрее обычного режима.

поэтому для вашей проблемы сначала скомпилируйте шаблон и затем выполните действие над ним.

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

Ответ 20

Вот ответ с некоторым объяснением.

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']

обновленный ответ

Ответ 21

Создайте функцию, которая принимает в качестве входных данных две строки (исходную строку для разделения и строку разделителя разделителей) и выводит список разделенных слов:

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

Ответ 22

получил такую ​​же проблему, как @ooboo и нашел эту тему @ghostdog74 вдохновил меня, может быть, кто-то найдет мое решение полезным

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

введите что-то в место и разделите, используя тот же символ, если вы не хотите разбивать на пробелы.

Ответ 23

Вот мой переход на раскол с несколькими разделителями:

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

Ответ 24

Я думаю, что лучший ответ на ваш вопрос:

\W+, возможно, подходит для этого случая, но может быть непригодным для других случаев.

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

Ответ 25

Вот, что я беру на себя...

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']

Ответ 26

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']

Ответ 27

Мне нравится 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]

Ответ 28

Если вы хотите обратимую операцию (сохраните разделители), вы можете использовать эту функцию:

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

Ответ 29

Недавно мне нужно было сделать это, но я хотел функцию, которая несколько соответствовала функции стандартной библиотеки str.split, эта функция ведет себя так же, как стандартная библиотека, когда 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])

ПРИМЕЧАНИЕ. Эта функция полезна только в том случае, если разделители состоят из одного символа (как в моем случае использования).

Ответ 30

Мне нравится решение 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