Разделить строки на слова с несколькими разделителями слов - PullRequest
596 голосов
/ 29 июня 2009

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

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

должно быть

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

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

Ответы [ 32 ]

502 голосов
/ 29 июня 2009

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.']
417 голосов
/ 29 июня 2009

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

import re
DATA = "Hey, you - what are you doing here!?"
print re.findall(r"[\w']+", DATA)
# Prints ['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']
336 голосов
/ 27 августа 2011

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

>>> 'a;bcd,ef g'.replace(';',' ').replace(',',' ').split()
['a', 'bcd', 'ef', 'g']
263 голосов
/ 18 мая 2014

Так много ответов, но я не могу найти никакого решения, которое бы эффективно выполняло то, что буквально просит заголовок вопросов (разбивая на несколько возможных разделителей - вместо этого многие ответы удаляют все, что не является Слово, которое отличается). Итак, вот ответ на вопрос в названии, основанный на стандартном и эффективном модуле re Python:

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

где:

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

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

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

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

53 голосов
/ 21 июля 2009

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

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()
38 голосов
/ 30 августа 2012

Совет: используйте 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()!

24 голосов
/ 26 мая 2010

Вроде поздний ответ :), но у меня была похожая дилемма и я не хотел использовать модуль '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']
10 голосов
/ 05 мая 2011
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.

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

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

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

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

Обратите внимание, что при любом подходе можно также рассмотреть возможность использования string.punctuation вместо списка, определенного вручную.

Вариант 1 - re

Я был удивлен, что пока не получил ответа, 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()
4 голосов
/ 29 июня 2009

попробуйте это:

import re

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

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

...