2009-04-12 2 views
5

Недавно в техническом интервью меня попросили написать программу, чтобы найти высокочастотные слова (слова, которые появляются максимальное количество раз) в учебнике. Программа должна быть спроектирована таким образом, чтобы она обрабатывала весь учебник с минимальной памятью. Производительность не вызывает беспокойства. Я смог запрограммировать, чтобы найти частоту слов, но он потреблял много памяти.Как найти высокочастотные слова в книге в среде с низкой памятью?

Как сделать эту операцию менее интенсивной в памяти? Любые стратегии/решения?

-Snehal

+0

было бы интересно видеть ваше решение! – Codebrain

+0

@Snebal: Не могли бы вы вставить свое решение? –

+0

Я написал код в интервью ..теперь нет. Извините – Snehal

ответ

5

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

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

2

Одним из способов было бы отсортировать список первых.

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

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

+0

Но вам также нужно использовать очень эффективный алгоритм сортировки. – Kredns

+0

«Выступление не вызывает беспокойства»? – chakrit

+0

Heapsort будет работать очень хорошо. – rlbond

2

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

+0

Мне нравится этот ответ, поскольку он «исследует», что «память» действительно означает в контексте этого вопроса и демонстрирует некоторые знания. – Brian

+0

Есть ли у вас примеры использования обертки файловой системы? – Snehal

+0

Вам нужно написать обертку, с которой вы говорите, а не писать в массивы на стек/кучу. Эта обертка записывает обратно в буфер в памяти и/или периодически удаляет содержимое буфера на диск. Таким образом, вы можете использовать фиксированное количество памяти процесса в любое время. – dirkgently

3

Если производительность действительно не вызывает беспокойства, вы можете просто пройти каждое слово по очереди, проверить, находится ли оно в вашем «верхнем N», а если нет, подсчитайте все его вхождения. Таким образом, вы сохраняете только N значений. Конечно, вы будете считать одни и те же слова много раз, но, как вы сказали, производительность не проблема - и код будет тривиальным (что обычно предпочтительнее - при прочих равных условиях).

+0

+1. Правильно, читайте один и тот же файл снова и снова, сохраняя тривиальное количество в памяти сразу, ища это слово. –

+0

это просто говорит то же самое, что я сделал за час до – aleemb

2

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

Другие решения могут быть найдены в ответах на этот смежный вопрос: Space-Efficient Data Structure for Storing a Word List?

4

OK, если вы заинтересованы только в высшей п употребляемые слова, один из способов сделать это в два прохода, причем первый проход на основе модифицированного Bloom Filter. Вместо использования битовой карты для отслеживания вхождения хешей вместо этого используйте целочисленный массив - либо байтовый, 16-разрядный, 32-разрядный, либо даже 64-разрядный в зависимости от вашего размера ввода. Если фильтр Bloom просто устанавливает бит, соответствующий каждому из хэш-значений слова, вы увеличиваете счет в хэш-индексе в массиве.

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

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

+0

Мне это нравится как хороший компромисс между пространством и временем – Mark

4

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

  • разобрать кусок достаточно мал, чтобы для ограничения памяти,
  • пропустить случайное количество текста,
  • повтор, сочетающий в себе накопленные результаты.
  • Остановитесь, если список удовлетворительно сходится.

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

Примечание: Это делает предположение, что наиболее часто встречающиеся слова встречаются чаще всего в тексте, а не только в одном месте в тексте. Для английского текста это предположение верно, поскольку частота слов, подобных «и т. Д.», Повсюду. Если вас беспокоит это требование, попросите алгоритм выполнить хотя бы один проход всего текста.

4

Я, вероятно, получить вниз проголосовали за это ...

Если текст на английском и вы просто хотите, чтобы найти топ-5 наиболее часто встречающихся слов, вот ваша программа:

print "1. the\n"; 
print "2. of\n"; 
print "3. and\n"; 
print "4. a\n"; 
print "5. to\n"; 

Работает быстро и потребляет минимальную память!

+0

+1 для умных. :-) –

+0

превосходный статический ответ :) – lalitm

2

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

I'm Предполагая, что текст хранится «в автономном режиме» где-то, но есть способ перебрать каждое слово в тексте, не загружая весь текст в память.

Затем код F # ниже найдет верхние N слов. Это только структура данных - это отображение пар ключ-значение (слово, частота), и он удерживает только верхний N из них, поэтому использование памяти O (N), которое мало. Время выполнения - O (numWordsInText^2), что плохо, но приемлемо с учетом проблемных ограничений. Суть алгоритма проста, для каждого слова в тексте, подсчитывать, сколько раз оно происходит, и если оно работает в режиме наилучшего -N, затем добавьте его в список и удалите предыдущую минимальную запись.

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

#light 
// some boilerplate to grab a big piece of text off the web for testing 
open System.IO 
open System.Net 
let HttpGet (url: string) = 
    let req = System.Net.WebRequest.Create(url) 
    let resp = req.GetResponse() 
    let stream = resp.GetResponseStream() 
    let reader = new StreamReader(stream) 
    let data = reader.ReadToEnd() 
    resp.Close() 
    data 
let text = HttpGet "http://www-static.cc.gatech.edu/classes/cs2360_98_summer/hw1" 
let words = text.Split([|' ';'\r';'\n'|], System.StringSplitOptions.RemoveEmptyEntries) 
// perhaps 'words' isn't actually stored in memory, but so long as we can 
// 'foreach' over all the words in the text we're good 
let N = 5 // how many 'top frequency' words we want to find 
let FindMin map = 
    // key-value pair with mininum value in a map 
    let (Some(seed)) = Map.first (fun k v -> Some(k,v)) map 
    map |> Map.fold_left 
     (fun (mk,mv) k v -> if v > mv then (mk,mv) else (k,v)) 
     seed 
let Main() = 
    let mutable freqCounts = Map.of_list [ ("",0) ] 
    for word in words do 
     let mutable count = 0 
     for x in words do 
      if x = word then 
       count <- count + 1 
     let minStr,minCount = FindMin freqCounts 
     if count >= minCount then 
      freqCounts <- Map.add word count freqCounts 
     if Seq.length freqCounts > N then 
      freqCounts <- Map.remove minStr freqCounts 
    freqCounts 
    |> Seq.sort_by (fun (KeyValue(k,v)) -> -v) 
    |> Seq.iter (printfn "%A") 
Main() 

Выход:

[the, 75] 
[to, 41] 
[in, 34] 
[a, 32] 
[of, 29] 
0

Ну, если вы хотите совершенно ужасное исполнение ...

Возьмите первое слово в книге, и подсчитать, сколько раз это происходит. Возьмите второе слово в книге, посчитайте, сколько раз это происходит. Если это больше, чем последнее слово, отбросьте последнее слово. И так далее ... вы в конечном итоге считаете одни и те же слова несколько раз, если вы не сохраните их где-нибудь, но если вы действительно хотите свести к минимуму память, это потребует всего несколько целых чисел. Должен работать в O (n^2) время, где n - количество слов в книге.

0

Как создать бинарное дерево ключей слов (поскольку вы продолжаете читать слова из файла). Это помогает искать уже повторяющиеся слова в O (Log (n)). Итак, вы получите O (nLog (n)) для поиска верхнего слова.

Basic алго будет

для каждого слова в файле:

  1. Создать уникальный ключ для данного слова (взвешенная ASCii обугленного, например, «летучая мышь» может быть 1 * «б» + 2 * 'a' + 3 * 'c';
  2. Добавьте это слово в дерево. Если слово уже существует, увеличьте новый счетчик.
  3. Подайте слово и текущий счет в службу поддержкиTop5 (word, count). maintainTop5 () поддерживает динамический список совпадений top5 и связанных слов.

Конец файла у вас есть 5 слов.

1

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

  • Во-первых, разделить входные строки на куски, сортировки каждый кусок и хранить во вторичное хранилище (внешняя сортировка) - O (N журнал N)
  • Прочитайте каждый кусок и в пределах фрагмента, вычислить частоту слов, так в конце этого шага каждый кусок сводится к (уникальное количество слов - частота) внутри куска. O (n)
  • Начните чтение элементов по кускам и агрегату для каждого слова. Поскольку фрагменты отсортированы, вы можете сделать это в O (n)
  • Теперь сохраните кучу минимального приоритета (верхняя часть кучи - минимальный элемент в куче) элементов K. Заполняйте кучу приоритета с помощью первых элементов K, затем для следующего (уникальное слово -финальное число), если его количество больше верхнего элемента в куче, поп-вершина и текущее слово. O (п войти к)

Так окончательная сложность времени O (п (срубы к + § п)) -