Как извлечь ngram символов из предложений? - питон

Следующая функция word2ngrams извлекает из слова 3 грамма символов:

>>> x = 'foobar'
>>> n = 3
>>> [x[i:i+n] for i in range(len(x)-n+1)]
['foo', 'oob', 'oba', 'bar']

В этом сообщении показано извлечение символьных ngrams для одного слова, Быстрая реализация символа n-граммы с использованием Python.

Но что, если у меня есть предложения и я хочу извлечь ngram-символы, есть ли более быстрый метод, кроме многократного вызова word2ngram()?

Какой будет версия регулярного выражения для получения одинаковых выходных данных word2ngram и sent2ngram? будет ли это быстрее?

Я пробовал:

import string, random, time
from itertools import chain

def word2ngrams(text, n=3):
  """ Convert word into character ngrams. """
  return [text[i:i+n] for i in range(len(text)-n+1)]

def sent2ngrams(text, n=3):
    return list(chain(*[word2ngrams(i,n) for i in text.lower().split()]))

def sent2ngrams_simple(text, n=3):
    text = text.lower()
    return [text[i:i+n] for i in range(len(text)-n+1) if not " " in text[i:i+n]]

# Generate 10000 random strings of length 100.
sents = [" ".join([''.join(random.choice(string.ascii_uppercase) for j in range(10)) for i in range(100)]) for k in range(100)]

start = time.time()
x = [sent2ngrams(i) for i in sents]
print time.time() - start        

start = time.time()
y = [sent2ngrams_simple(i) for i in sents]
print time.time() - start        

print x==y

[вне]:

0.0205280780792
0.0271739959717
True

ОТРЕДАКТИРОВАНО

Метод регулярного выражения выглядит элегантно, но работает медленнее, чем итеративный вызов word2ngram():

import string, random, time, re
from itertools import chain

def word2ngrams(text, n=3):
  """ Convert word into character ngrams. """
  return [text[i:i+n] for i in range(len(text)-n+1)]

def sent2ngrams(text, n=3):
    return list(chain(*[word2ngrams(i,n) for i in text.lower().split()]))

def sent2ngrams_simple(text, n=3):
    text = text.lower()
    return [text[i:i+n] for i in range(len(text)-n+1) if not " " in text[i:i+n]]

def sent2ngrams_regex(text, n=3):
    rgx = '(?=('+'\S'*n+'))'
    return re.findall(rgx,text)

# Generate 10000 random strings of length 100.
sents = [" ".join([''.join(random.choice(string.ascii_uppercase) for j in range(10)) for i in range(100)]) for k in range(100)]

start = time.time()
x = [sent2ngrams(i) for i in sents]
print time.time() - start        

start = time.time()
y = [sent2ngrams_simple(i) for i in sents]
print time.time() - start        

start = time.time()
z = [sent2ngrams_regex(i) for i in sents]
print time.time() - start  

print x==y==z

[вне]:

0.0211708545685
0.0284190177917
0.0303599834442
True

person alvas    schedule 15.03.2014    source источник


Ответы (1)


Почему бы просто не (?=(...))

edit То же самое, но без пробелов (?=(\S\S\S))
edit2 Вы также можете использовать то, что хотите. Бывший. использует только буквы (?=([^\W_]{3}))

Использует просмотр вперед для захвата 3 символов. Затем движок увеличивает позицию 1 раз при каждом совпадении
. Затем захватывает следующие 3.

Результатом foobar является
foo
oob
oba
bar

 # Compressed regex
 #  (?=(...))

 # Expanded regex
 (?=                   # Start Lookahead assertion
      (                     # Capture group 1 start
           .                     # dot - metachar, matches any character except newline
           .                     # dot - metachar
           .                     # dot - metachar
      )                     # Capture group 1 end
 )                     # End Lookahead assertion
person Community    schedule 15.03.2014
comment
извините за нубство, что такое (?=(...))? можно рабочий пример? я пробовал: (?=('foobar')) но получил синтаксическую ошибку. - person alvas; 15.03.2014
comment
Добавлены некоторые комментарии, (?=(...)) - это регулярное выражение. Я не знаю Python, но его следует использовать как регулярное выражение в контексте соответствия всем (получить вывод массива). - person ; 15.03.2014
comment
re.findall(r'(?=(...))','foobar'), вышел: ['foo', 'oob', 'oba', 'bar']. - person alvas; 15.03.2014
comment
Круто... Это то, что вы ищете? - person ; 15.03.2014
comment
да, позвольте мне попробовать профилировать и посмотреть, сколько времени я сэкономлю =). Спасибо за аккуратный трюк с регулярным выражением. Есть ли способ не проверять " "? - person alvas; 15.03.2014
comment
Так что не пробел тогда. Подожди секунду. - person ; 15.03.2014
comment
Да, то же самое, без пробелов (?=(\S\S\S)) - person ; 15.03.2014
comment
@sin, это хорошее регулярное выражение, но оно работает намного медленнее, чем перебор отдельных слов =( - person alvas; 16.03.2014
comment
@alvas - замедление может быть rgx = должно быть скомпилировано только один раз, а не для каждого предложения. Он должен быть предварительно скомпилирован перед итерацией. И вы также можете улучшить скорость регулярного выражения на 10-15%, если будете активно перемещать позицию совпадения. т. е. /(?=(\S\S\S))./ Добавьте модификатор Dot-All (такой же, как /(?=(\S\S\S))[\S\s]/ или /(?s)(?=(\S\S\S))./). - person ; 17.03.2014
comment
Это решило мою проблему; даже не приблизившись к твоему подходу! Спасибо! - person Akbar Hussein; 05.05.2020