У меня есть строка слов; давайте назовем их bad
:
bad = "foo bar baz"
Я могу сохранить эту строку, поскольку пробел разделил строку, или как список:
bad = bad.split(" ");
Если у меня есть другая строка, как так:
str = "This is my first foo string"
Что постившийся путь состоит в том, чтобы проверить если какое-либо слово от bad
строка в моей строке сравнения, и что состоит в том, чтобы удалить самый быстрый путь, сказало слово, если это найдено?
#Find if a word is there
bad.split(" ").each do |word|
found = str.include?(word)
end
#Remove the word
bad.split(" ").each do |word|
str.gsub!(/#{word}/, "")
end
Если список плохих слов становится огромным, хеширование выполняется намного быстрее:
require 'benchmark'
bad = ('aaa'..'zzz').to_a # 17576 words
str= "What's the fasted way to check if any word from the bad string is within my "
str += "comparison string, and what's the fastest way to remove said word if it's "
str += "found"
str *= 10
badex = /\b(#{bad.join('|')})\b/i
bad_hash = {}
bad.each{|w| bad_hash[w] = true}
n = 10
Benchmark.bm(10) do |x|
x.report('regex:') {n.times do
str.gsub(badex,'').squeeze(' ')
end}
x.report('hash:') {n.times do
str.gsub(/\b\w+\b/){|word| bad_hash[word] ? '': word}.squeeze(' ')
end}
end
user system total real
regex: 10.485000 0.000000 10.485000 ( 13.312500)
hash: 0.000000 0.000000 0.000000 ( 0.000000)
bad = "foo bar baz"
=> "foo bar baz"
str = "Это моя первая строка foo"
=> "Это моя первая строка foo"
( str.split ('') - bad.split ('')). join ('')
=> "Это моя первая строка"
Все решения имеют проблемы с ловлей плохих слов, если случай не совпадает. Решение regex проще всего исправить, добавив флаг ignore-case:
badex = /\b(#{bad.split.join('|')})\b/i
Кроме того, используя "String".include? ("Строка")
приведет к проблемам с границами с первым и последним словами в строке или строках, где целевые слова имеют знаки препинания или расставлены через дефис. Тестирование для этих ситуаций приведет к тому, что потребуется много другого кода. Из-за этого я думаю, что решение regex является лучшим. Он не самый быстрый, но он будет более гибким прямо из коробки, и, если другие алгоритмы будут настроены для обработки складывания регистров и сложных слов, решение regex может продвинуться вперед.
#!/usr/bin/ruby require 'benchmark' bad = 'foo bar baz comparison' badex = /\b(#{bad.split.join('|')})\b/i str = "What's the fasted way to check if any word from the bad string is within my comparison string, and what's the fastest way to remove said word if it's found?" * 10 n = 10_000 Benchmark.bm(20) do |x| x.report('regex:') do n.times { str.gsub(badex,'').gsub(' ',' ') } end x.report('regex with squeeze:') do n.times{ str.gsub(badex,'').squeeze(' ') } end x.report('array subtraction') do n.times { (str.split(' ') - bad.split(' ')).join(' ') } end end
Я сделал переменную str намного длиннее, чтобы рутина работала немного сложнее.
user system total real regex: 0.740000 0.010000 0.750000 ( 0.752846) regex with squeeze: 0.570000 0.000000 0.570000 ( 0.581304) array subtraction 1.430000 0.010000 1.440000 ( 1.449578)
Doh!, я слишком привык к тому, как другие языки справляются со своими бенчмарками. Теперь я заставил его работать и выглядеть лучше!
Просто небольшой комментарий о том, как это выглядит, как OP пытается сделать: удаление слов из черного списка легко обмануть, и больно поддерживать. L33t-sp34k делает тривиальным пронзтимые слова. В зависимости от области применения,люди будут считать это игрой, чтобы найти способы протолкнуть оскорбительные слова через фильтрацию. Лучшим решением, которое я нашел, когда меня попросили поработать над этим, было создать генератор, который создавал бы все вариации слова и сбрасывал их в базу данных, где какой-то процесс мог бы проверить как можно скорее, а не в режиме реального времени. Проверка миллиона небольших строк может занять некоторое время, если вы ищете длинный список оскорбительных слов; Я уверен, что мы могли бы придумать целый список вещей, которые кто-то сочтет оскорбительными, но это упражнение для другого дня.
Я не видел ничего похожего в Ruby на Regexp::Assemble Perl, но это был хороший способ решить такого рода проблемы. Вы можете передать массив слов, а также параметры для складывания регистра и границ слов, и он выплюнет шаблон regex, который будет соответствовать всем словам, а их общие черты, как считается, приведут к наименьшему шаблону, который будет соответствовать всем словам в списке.Проблема после этого заключается в том, чтобы найти, какое слово в исходной строке соответствовало хитам, найденным шаблоном, поэтому их можно удалить. Различия в регистре слов и хитах в составных словах делают эту замену более интересной.
И мы даже не будем вдаваться в слова, которые являются доброкачественными или оскорбительными в зависимости от контекста.
Я добавил немного более полный тест для теста вычитания массива, чтобы соответствовать тому, как он должен работать в реальном фрагменте кода. Предложение if
указано в ответе, теперь это отражает его:
#!/usr/bin/env ruby
require 'benchmark'
bad = 'foo bar baz comparison'
badex = /\b(#{bad.split.join('|')})\b/i
str = "What's the fasted way to check if any word from the bad string is within my comparison string, and what's the fastest way to remove said word if it's found?" * 10
str_split = str.split
bad_split = bad.split
n = 10_000
Benchmark.bm(20) do |x|
x.report('regex') do
n.times { str.gsub(badex,'').gsub(' ',' ') }
end
x.report('regex with squeeze') do
n.times{ str.gsub(badex,'').squeeze(' ') }
end
x.report('bad.any?') do
n.times {
if (bad_split.any? { |bw| str.include?(bw) })
(str_split - bad_split).join(' ')
end
}
end
x.report('array subtraction') do
n.times { (str_split - bad_split).join(' ') }
end
end
с двумя тестовыми запусками:
ruby test.rb
user system total real
regex 1.000000 0.010000 1.010000 ( 1.001093)
regex with squeeze 0.870000 0.000000 0.870000 ( 0.873224)
bad.any? 1.760000 0.000000 1.760000 ( 1.762195)
array subtraction 1.350000 0.000000 1.350000 ( 1.346043)
ruby test.rb
user system total real
regex 1.000000 0.010000 1.010000 ( 1.004365)
regex with squeeze 0.870000 0.000000 0.870000 ( 0.868525)
bad.any? 1.770000 0.000000 1.770000 ( 1.775567)
array subtraction 1.360000 0.000000 1.360000 ( 1.359100)
Обычно я стараюсь не оптимизировать без измерений, но вот вам ваг:
Чтобы сделать это быстро, вы должны итерировать каждую строку один раз. Вы хотите избежать цикла с плохими внутренними сравнениями count * str count. Итак, вы можете построить большой regexp и gsub с ним.
(добавление вариантов foo для проверки границ слов работает)
str = "This is my first foo fooo ofoo string"
=> "This is my first foo fooo ofoo string"
badex = /\b(#{bad.split.join('|')})\b/
=> /\b(foo|bar|baz)\b/
str.gsub(badex,'').gsub(' ',' ')
=> "This is my first fooo ofoo string"
Конечно, огромный результирующий regexp может быть таким же медленным, как подразумеваемая вложенная итерация в моем другом ответе. Единственный способ узнать это - измерить.
bad = %w(foo bar baz)
str = "This is my first foo string"
# find the first word in the list
found = bad.find {|word| str.include?(word)}
# remove it
str[found] = '' ;# str => "This is my first string"
Я бы сделал такой бенчмарк:
bad = "foo bar baz".split(' ')
str = "This is my first foo string".split(' ')
# 1. What's the fasted way to check if any word from the bad string is within my comparison string
p bad.any? { |bw| str.include?(bw) }
# 2. What's the fastest way to remove said word if it's found?
p (str - bad).join(' ')
any? будет быстро проверять, как только увидит совпадение. Если вы можете упорядочить ваши плохие слова по их вероятности, вы можете сэкономить несколько циклов.
Включить? метод - это то, что вам нужно. Спецификация ruby String гласит:
str.include? (Строка) -> true или false Возвращает true, если str содержит данную строку или символ.
"привет" .include? "lo" -> true
"hello" .include? "ol" -> false
"привет" .include? ? h -> true
Обратите внимание, что он имеет O (n), а то, что вы задумали, - O (n ^ 2)