Я хочу, чтобы мой вывод выполнял поиск и подсчет частоты слов «конфеты» и «грамм», а также комбинаций «конфеты грамм» и «грамм конфеты» в заданном тексте (целый_файл). В настоящее время я использую следующее код для отображения вхождений «конфеты» и «грамм», но когда я объединяю комбинации в %w, отображаются только слова и частоты «конфеты» и «грамм». Должен ли я попробовать другой способ? Спасибо.
myArray = whole_file.split
stop_words= %w{ candy gram 'candy gram' 'gram candy' }
nonstop_words = myArray - stop_words
key_words = myArray - nonstop_words
frequency = Hash.new (0)
key_words.each { |word| frequency[word] +=1 }
key_words = frequency.sort_by {|x,y| x }
key_words.each { |word, frequency| puts word + ' ' + frequency.to_s }
2 ответа
Похоже, вы ищете n-grams. Вы можете сначала разбить текст на комбинации последовательных слов, а затем подсчитать количество вхождений в результирующем массиве групп слов. Вот пример:
whole_file = "The big fat man loves a candy gram but each gram of candy isn't necessarily gram candy"
[["candy"], ["gram"], ["candy", "gram"], ["gram", "candy"]].each do |term|
terms = whole_file.split(/\s+/).each_cons(term.length).to_a
puts "#{term.join(" ")} #{terms.count(term)}"
end
EDIT: Как было указано в комментариях ниже, я не уделял должного внимания и разбивал файл на каждый цикл, что, очевидно, не очень хорошая идея, особенно если он большой. Я также не учел тот факт, что исходный вопрос, возможно, должен был сортироваться по количеству, хотя это не было задано явно.
whole_file = "The big fat man loves a candy gram but each gram of candy isn't necessarily gram candy"
# This is simplistic. You would need to address punctuation and other characters before
# or at this step.
split_file = whole_file.split(/\s+/)
terms_to_count = [["candy"], ["gram"], ["candy", "gram"], ["gram", "candy"]]
counts = []
terms_to_count.each do |term|
terms = split_file.each_cons(term.length).to_a
counts << [term.join(" "), terms.count(term)]
end
# Seemed like you may need to do sorting too, so here that is:
sorted = counts.sort { |a, b| b[1] <=> a[1] }
sorted.each do |count|
puts "#{count[0]} #{count[1]}"
end
split
вне цикла.
Удалить знаки препинания и преобразовать их в нижний регистр
Первое, что вы, вероятно, захотите сделать, это удалить все знаки препинания из строки, содержащей содержимое файла, а затем преобразовать то, что осталось, в нижний регистр. одно и то же слово. Эти две операции можно выполнять в любом порядке.
Изменить заглавные буквы на строчные легко:
text = whole_file.downcase
Чтобы удалить знаки препинания, вероятно, проще решить, что оставить, чем от чего отказаться. Если мы хотим сохранить только строчные буквы, вы можете сделать это:
text = whole_file.downcase.gsub(/[^a-z]/, '')
То есть заменить пустой строкой все символы, кроме (^
) строчных букв.1
Определить частотность отдельных слов
Если вы хотите подсчитать, сколько раз text
содержит слово 'candy'
, вы можете использовать метод String#scan в строке text
, а затем определить размер возвращаемого массива:
text.scan(/\bcandy\b/).size
scan
возвращает массив с каждым вхождением строки 'candy'
; .size
возвращает размер этого массива. Здесь \b
гарантирует, что 'candy gram'
имеет слово "граница" на каждом конце, которое может быть пробелом, началом или концом строки или файла. Это сделано для того, чтобы предотвратить подсчет «конфеты».
Второй способ — преобразовать строку text
в массив слов, как вы сделали2:
myArray = text.split
Если вы не возражаете, я хотел бы назвать это:
words = text.split
3
Самый прямой способ определить, сколько раз появляется 'candy'
, — использовать метод Enumberable#count, например:
words.count('candy')
Вы также можете использовать метод разности массивов, Array# -, как вы заметили:
words.size - (words - ['candy']).size
Если вы хотите узнать, сколько раз появляется слово «конфета» или «грамм», вы, конечно, можете сделать то же самое для каждого из них и просуммировать два значения. Некоторые другие способы:
words.size - (myArray - ['candy', 'gram']).size
words.count { |word| word == 'candy' || word = 'gram' }
words.count { |word| ['candy', 'gram'].include?(word) }
Определить частоту всех слов, встречающихся в тексте
Ваше использование хэша со значением по умолчанию, равным нулю, было хорошим выбором:
def frequency_of_all_words(words)
frequency = Hash.new(0)
words.each { |word| frequency[word] +=1 }
frequency
end
Я написал это, чтобы подчеркнуть, что words.each...
не возвращает frequency
. Часто вы увидите, что это написано более компактно, используя метод Enumerable#each_with_object, который возвращает хэш ("объект"):
def frequency_of_all_words(words)
words.each_with_object(Hash.new(0)) { |word, h| h[word] +=1 }
end
Когда у вас есть хэш frequency
, вы можете отсортировать его, как вы это делали:
frequency.sort_by {|word, freq| freq }
Или
frequency.sort_by(&:last)
Что вы могли бы написать:
frequency.sort_by {|_, freq| freq }
Так как вы не используете первую блочную переменную. Если вы хотите сначала использовать наиболее часто встречающиеся слова:
frequency.sort_by(&:last).reverse
Или
frequency.sort_by {|_, freq| -freq }
Все это даст вам массив. Если вы хотите преобразовать его обратно в хеш (сначала с наибольшими значениями):
Hash[frequency.sort_by(&:last).reverse]
Или в Руби 2.0+,
frequency.sort_by(&:last).reverse.to_h
Подсчитайте, сколько раз появляется подстрока
Теперь подсчитаем, сколько раз появляется строка 'candy gram'
. Вы можете подумать, что мы могли бы использовать String#scan
для строки, содержащей весь файл, как мы делали ранее4:
text.scan(/\bcandy gram\b/).size
Первая проблема заключается в том, что это не поймает 'candy\ngram'; т. е. когда слова разделены символом новой строки. Мы могли бы исправить это, изменив регулярное выражение на /\bcandy\sgram\b/
. Вторая проблема заключается в том, что «конфетка грамм» могла быть «конфеткой». Gram' в файле, и в этом случае вы можете не захотеть его учитывать.
Лучше использовать метод Enumerable#each_cons. в массиве words
. Проще всего показать вам, как это работает, на примере:
words = %w{ check for candy gram here candy gram again }
#=> ["check", "for", "candy", "gram", "here", "candy", "gram", "again"]
enum = words.each_cons(2)
#=> #<Enumerator: ["check", "for", "candy", "gram", "here", "candy",
# "gram", "again"]:each_cons(2)>
enum.to_a
#=> [["check", "for"], ["for", "candy"], ["candy", "gram"],
# ["gram", "here"], ["here", "candy"], ["candy", "gram"],
# ["gram", "again"]]
each_cons(2)
возвращает перечислитель; Я преобразовал его в массив, чтобы отобразить его содержимое.
Итак, мы можем написать
words.each_cons(2).map { |word_pair| word_pair.join(' ') }
#=> ["check for", "for candy", "candy gram", "gram here",
# "here candy", "candy gram", "gram again"]
И наконец:
words.each_cons(2).map { |word_pair|
word_pair.join(' ') }.count { |s| s == 'candy gram' }
#=> 2
1 Если вы также хотите сохранить тире, для слов с дефисом измените регулярное выражение на /[^-a-z]/
или /[^a-z-]/
.
2 Обратите внимание на String#split, что .split
совпадает с .split(' ')
и .split(/\s+/)
).
3 Кроме того, соглашение об именах Ruby заключается в использовании строчных букв и знаков подчеркивания («змеиный регистр») для переменных и методов, таких как my_array
.
Похожие вопросы
Новые вопросы
ruby
Ruby - это многоплатформенный динамический объектно-ориентированный интерпретируемый язык с открытым исходным кодом. Тег [ruby] предназначен для вопросов, связанных с языком Ruby, включая его синтаксис и его библиотеки. Вопросы Ruby on Rails должны быть помечены [ruby-on-rails].