Поиск префикса стиля автозаполнения

Делаем конкретный пример:

  • У вас есть список всех имен в США.
  • Вы хотите автоматически предлагать завершения в графическом интерфейсе.

Очевидно, что нужно использовать дерево счисления, чтобы получить список имен для данного префикса. Однако при этом не учитывается информация о частоте. Таким образом, вместо того, чтобы просто 5 лучших результатов были первыми лексическими результатами, я хотел бы получить 5 самых распространенных имен:

например Для префикса dan

 (5913, 'Daniel')
 (889, 'Danny')
 (820, 'Dana')
 (272, 'Dan')
 (60, 'Dane')

Есть ли алгоритм дерева дерева, который я пропустил? Конечно, идеальная реализация (если она существует) на мой взгляд, на питоне.

ОБНОВЛЕНИЕ: В целом доволен тем, что предложил Paddy3113, хотя я скажу, что он полностью взрывается, когда я передаю ему файл размером 2,6 ГБ, который является одним из файлов, которые я уменьшаю. Глядя на детали, вывод дает некоторое представление:

samz;Samzetta|Samzara|Samzie
samza;Samzara
samzar;Samzara
samzara;Samzara
samze;Samzetta
samzet;Samzetta
samzett;Samzetta
samzetta;Samzetta
samzi;Samzie
samzie;Samzie

# Format - PREFIX;"|".join(CHOICES).

У нас есть еще несколько дней на баунти, так что я все еще ищу убийственное решение. Поскольку речь идет не только о сокращении, но и о поиске.


person koblas    schedule 24.05.2012    source источник
comment
У вас есть образец списка имен с соответствующими частотами?   -  person Paddy3118    schedule 29.05.2012
comment
Вот буква S — github.com/koblas/FirstLetterS   -  person koblas    schedule 30.05.2012
comment
если он бомбит с файлом 2,5G, это потому, что python хранит много дополнительной информации, и память не может ее удержать. Я решил ту же проблему (проверка правописания), реализовав класс trie в c, затем сделал его .so, связал его с python и использовал.   -  person pranshus    schedule 26.06.2014


Ответы (5)


Да, мы можем использовать попытку. Наиболее частыми именами для узла дерева являются либо (1) имя в этом узле дерева, либо (2) наиболее частое имя дочернего узла узла дерева. Вот некоторый код Python, с которым можно поиграть.

from collections import defaultdict


class trie:
    __slots__ = ('children', 'freq', 'name', 'top5')

    def __init__(self):
        self.children = defaultdict(trie)
        self.freq = 0
        self.name = None
        self.top5 = []

    def __getitem__(self, suffix):
        node = self
        for letter in suffix:
            node = node.children[letter]
        return node

    def computetop5(self):
        candidates = []
        for letter, child in self.children.items():
            child.computetop5()
            candidates.extend(child.top5)
        if self.name is not None:
            candidates.append((self.freq, self.name))
        candidates.sort(reverse=True)
        self.top5 = candidates[:5]

    def insert(self, freq, name):
        node = self[name]
        node.freq += freq
        node.name = name


root = trie()
with open('letter_s.txt') as f:
    for line in f:
        freq, name = line.split(None, 1)
        root.insert(int(freq.strip()), name.strip())
root.computetop5()
print(root['St'].top5)
person just keep walking    schedule 30.05.2012
comment
Это чертовски элегантно... Кроме того, он работает примерно в 2,5 раза быстрее, чем версия dict. Стоит серьезного рассмотрения. - person koblas; 02.06.2012
comment
Я этого не писал, но если вы хотите обновить частоту на одном узле (или вставить новое имя), вы можете перезапустить нерекурсивную версию Computetop5 на этом узле и его предках, что должно быть довольно быстро. - person just keep walking; 02.06.2012
comment
Моя тестовая реализация также преобразует top5 в вычисляемое свойство по запросу, что помогает при предварительном вычислении. - person koblas; 02.06.2012

Не имея никакого представления о настройке, я бы начал с предположения, что у меня есть список имен и их частот, затем создаю словарь, отображающий префиксы в набор имен с этим префиксом, а затем превращаю каждый набор в список только 5 лучших имен w.r.t. частота.

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

8427    OLIVER 
7031    JACK 
6862    HARRY 
5478    ALFIE 
5410    CHARLIE 
5307    THOMAS 
5256    WILLIAM 
5217    JOSHUA 
4542    GEORGE 
4351    JAMES 
4330    DANIEL 
4308    JACOB 
...

Следующий код создает словарь:

from collections import defaultdict

MAX_SUGGEST = 5

def gen_autosuggest(name_freq_file_name):
    with open(name_freq_file_name) as f:
        name2freq = {}
        for nf in f:
            freq, name = nf.split()
            if name not in name2freq:
                name2freq[name] = int(freq)
    pre2suggest = defaultdict(list)
    for name, freq in sorted(name2freq.items(), key=lambda x: -x[1]):
        # in decreasing order of popularity
        for i, _ in enumerate(name, 1):
            prefix = name[:i]
            pre2suggest[prefix].append((name, name2freq[name]))
    # set max suggestions
    return {pre:namefs[:MAX_SUGGEST]
            for pre, namefs in pre2suggest.items()}

if __name__ == '__main__':
    pre2suggest = gen_autosuggest('2010boysnames_popularity_engwales2.txt')

Если вы дадите dict свой префикс, он вернет ваши предложения (вместе с их частотами в этом случае, но при необходимости их можно отбросить:

>>> len(pre2suggest)
15303
>>> pre2suggest['OL']
[('OLIVER', 8427), ('OLLIE', 1130), ('OLLY', 556), ('OLIVIER', 175), ('OLIWIER', 103)]
>>> pre2suggest['OLI']
[('OLIVER', 8427), ('OLIVIER', 175), ('OLIWIER', 103), ('OLI', 23), ('OLIVER-JAMES', 16)]
>>> 

Смотри, нет попыток :-)

Убийца времени

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

>>> import pickle
>>> 
>>> savename = 'pre2suggest.pcl'
>>> with open(savename, 'wb') as f:
    pickle.dump(pre2suggest, f)


>>> # restore it
>>> with open(savename, 'rb') as f:
    p2s = pickle.load(f)


>>> p2s == pre2suggest
True
>>> 
person Paddy3118    schedule 29.05.2012
comment
Я взглянул на FirstLetterS от koblas — оно было опубликовано после моего представления. Я думаю, что, заменив формат файла с имени, затем frq на freq, затем имя, а затем заглавные имена, тогда кишки моего примера выше будут работать с его данными. - person Paddy3118; 30.05.2012
comment
Это работает. Хотя для запуска реального набора данных 1.3G требуется более 3 минут. Можем ли мы сделать лучше? Поскольку мне нужно будет загрузить набор данных 3G, набор данных 1.3G и набор данных 1G одновременно. - person koblas; 30.05.2012
comment
Отредактировано, чтобы использовать список имен, более похожий на список имен из koblas, и добавил ссылку на мои входные данные. - person Paddy3118; 30.05.2012
comment
Отредактировано, чтобы добавить информацию о травлении для экономии времени (если список имен довольно статичен между вызовами). Если список имен меняется, но вы знаете (существенно меньший) набор частот имен, которые были изменены, я бы выбрал name2freq и просто прочитал список изменений. - person Paddy3118; 30.05.2012
comment
Уже сделал рассол... перешел на sqlite, чтобы сократить время запуска. Все еще надеюсь на лучшее, чем предварительно вычисленный словарь. - person koblas; 30.05.2012
comment
Ваш список имен статичен? - person Paddy3118; 30.05.2012

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

person Justin    schedule 24.05.2012
comment
Я думал об этом, но мне интересно, было ли это где-то более формализовано. - person koblas; 24.05.2012

Вот идея, как это сделать:

Создайте строку trie и сохраните целое число с каждым узлом в дереве. Этот узел указывает количество имен, которые используют этот узел. Таким образом, вы будете увеличивать все узлы имени, когда это имя вставляется в дерево.

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

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

person Simeon Visser    schedule 24.05.2012

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

Я бы предложил использовать DBM для хранения предварительно вычисленного словаря. По сути, это словарь, содержимое которого хранится на диске и просматривается по мере того, как вы ссылаетесь на элементы. Подробнее см. http://docs.python.org/library/anydbm.html. Единственным недостатком является то, что значения должны быть строками, поэтому вам нужно будет хранить список, разделенный запятыми, скажем, из 5 первых записей, и разбивать его при поиске.

Это будет иметь гораздо более быстрое время запуска, чем рассол, так как БД не нужно загружать. Это также намного проще, чем использование sqlite.

person xorsyst    schedule 31.05.2012