Эффективный Python для подсчета совпадений пар слов? - PullRequest
0 голосов
/ 22 января 2019

Я бы хотел эффективный Pythonic способ подсчета соседних пар слов в тексте. Эффективно, потому что нужно хорошо работать с большими наборами данных.

Важным является и способ подсчета.

Рассмотрим этот упрощенный пример:

words_list = "apple banana banana apple".split()

Я могу создать соседние пары, используя:

word_pair_list = zip(words_list[:-1], words_list[1:])

Затем я могу подсчитать их Python, используя

word_pair_ctr = collections.Counter(word_pair_list)

Это дает мне

(('apple', 'banana'), 1)
(('banana', 'banana'), 1)
(('banana', 'apple'), 1)

Обратите внимание, что 'apple' и 'apple' не являются соседней парой.

Но я хочу, чтобы порядок пар не учитывался. Это означает, что ('apple', 'banana') и ('banana', 'apple') должны считаться одинаковыми, а число должно быть

(('apple', 'banana'), 2)
(('banana', 'banana'), 1)

Я не могу найти Pythonic способ сделать это, который не требует, чтобы я посещал каждый элемент в списке слов, который становится неэффективным для более крупного текста.

Я счастлив использовать в качестве библиотек также обыкновенных скиппи, нампи и панд.

Ответы [ 2 ]

0 голосов
/ 22 января 2019

Вы можете использовать модифицированную версию парной функции из официальной документации (https://docs.python.org/3.8/library/itertools.html)), чтобы прочитать ваш список по парам и одновременно изменить порядок членов каждой пары:

l = "apple banana banana apple".split()
def pairwise(iterable):
    """s -> (s0,s1), (s1,s2), (s2, s3), ..."""
    a, b = itertools.tee(iterable)
    next(b, None)
    return ((a, b) if a < b else (b, a) for a, b in zip(a, b))
>>> list(pairwise(l))
<class 'list'>: ['apple', 'banana', 'banana', 'apple']
>>> collections.Counter(pairwise(l))
Counter({('apple', 'banana'): 2, ('banana', 'banana'): 1})

Надеюсь, это поможет!

0 голосов
/ 22 января 2019

Существует несколько решений со встроенными функциями.

Отображение word_pair_list на frozenset:

word_pair_ctr = collections.Counter(map(frozenset, word_pair_list))

Результат:

Counter({frozenset({'apple', 'banana'}): 2, frozenset({'banana'}): 1})

Второй набор может выглядеть странно, но это только потому, что наборы содержаттолько один идентичный элемент.Извлечение все равно будет работать, т.е. word_pair_ctr[frozenset(["banana", "banana"])] равно 1.

Вам необходимо использовать frozenset, а не обычный набор, потому что нормальные наборы не могут быть хешируемыми и поэтому не могут быть ключами в словаре (или счетчике).).

Сортировка пар перед вставкой в ​​счетчик:

word_pair_ctr = collections.Counter(map(lambda x: tuple(sorted(x)), word_pair_list))

Результат выглядит следующим образом:

Counter({('apple', 'banana'): 2, ('banana', 'banana'): 1})

Хотя это может выглядеть лучше, вынеобходимо убедиться, что вы обращаетесь к счетам таким же образом, то есть word_pair_ctr[tuple(sorted([word1, word2]))], что может показаться даже более запутанным, чем предыдущее решение.

Счетчик подкласса

Третий вариант - сделатьВаш собственный класс счетчиков, который делает все это за вас.

class BiDirectionalCounter(collections.Counter):
    def __init__(self, iterable):
        super().__init__(map(lambda x: tuple(sorted(x)), iterable))
    def __getitem__(self, items):
        return super().__getitem__(tuple(sorted(items)))

Это, по-видимому, работает:

>>> BidirectionalCounter(word_pair_list)
BidirectionalCounter({('apple', 'banana'): 2, ('banana', 'banana'): 1})

Но чтобы по-настоящему работать, вам нужно реализовать все соответствующие dunder методы, т.е. __setitem__, __add__, __iadd__, ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...