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

У меня есть строка слов; давайте назовем их 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
7
задан Mike Trpcic 31 March 2010 в 02:51
поделиться

7 ответов

Если список плохих слов становится огромным, хеширование выполняется намного быстрее:

    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)
9
ответ дан 6 December 2019 в 23:04
поделиться

bad = "foo bar baz"

=> "foo bar baz"

str = "Это моя первая строка foo"

=> "Это моя первая строка foo"

( str.split ('') - bad.split ('')). join ('')

=> "Это моя первая строка"

3
ответ дан 6 December 2019 в 23:04
поделиться

Все решения имеют проблемы с ловлей плохих слов, если случай не совпадает. Решение 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)
1
ответ дан 6 December 2019 в 23:04
поделиться

Обычно я стараюсь не оптимизировать без измерений, но вот вам ваг:

Чтобы сделать это быстро, вы должны итерировать каждую строку один раз. Вы хотите избежать цикла с плохими внутренними сравнениями 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 может быть таким же медленным, как подразумеваемая вложенная итерация в моем другом ответе. Единственный способ узнать это - измерить.

0
ответ дан 6 December 2019 в 23:04
поделиться
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"
0
ответ дан 6 December 2019 в 23:04
поделиться

Я бы сделал такой бенчмарк:

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? будет быстро проверять, как только увидит совпадение. Если вы можете упорядочить ваши плохие слова по их вероятности, вы можете сэкономить несколько циклов.

0
ответ дан 6 December 2019 в 23:04
поделиться

Включить? метод - это то, что вам нужно. Спецификация ruby ​​String гласит:

str.include? (Строка) -> true или false Возвращает true, если str содержит данную строку или символ.

"привет" .include? "lo" -> true

"hello" .include? "ol" -> false

"привет" .include? ? h -> true

Обратите внимание, что он имеет O (n), а то, что вы задумали, - O (n ^ 2)

-2
ответ дан 6 December 2019 в 23:04
поделиться
Другие вопросы по тегам:

Похожие вопросы: