Ruby [Problem, Ruby, Array] 如何检测一个字符串中包括某个数组中的元素?

crayygy · 2016年05月24日 · 最后由 arth 回复于 2016年05月29日 · 6905 次阅读

举个例子,有若干个字符串(约 10w 行数据)

string = "Ruby China is a community of Ruby developers."
keywords = ["Ruby", "community"]

如何判断这个 string 中包括这个数组中的任何一个元素呢?

想到的一个很丑的写法是这样的:

if string.include? keywords[0] or string.include? keywords[1]
  # do something
end

keywords 数组是变化的并且最多会有 100+,因此这种写法不可能成立的...

然后改了一下成这样:

keywords.each { | keyword | result.push(string) if string.include? keyword } 

但是这样会造成如果这一句话包括多个关键字的话,会出现很多次。

所以想请教一下,有没有什么方法能够判断这个字符串是否包括数组中存在的这些子串,或者有更好地判断方法?

谢谢!

a = ["1", "2", "3"]
b = "1  2 3 4 5"
c = ["6"]
p "hello" if !(a & b.split(//)).empty?
p "haha" if !(c & b.split(//)).empty?

一个简单的写法。求交集。

#1 楼 @hooopo 谢谢!感觉很有用,我再继续研究一下!

#2 楼 @AlexShawn 谢谢!这个思路感觉挺独特的,测试了一下,10w 行数据耗时有点长,最后出来的数据也有点小问题。可能是我哪里写错了?

#4 楼 @crayygy 简单的写法肯定会影响效率的,在这个例子里可以把字符串的数据简单去重。

string.split(//).uniq

当然按照楼上用字典树的方法是更好的。

不用考虑效率的写法:

keywords.any?{|k| string.include?(k)}

use elasticsearch!

个人觉得英文单词可能用元音字母出现的位置做索引效率更好

代码

string = "Ruby China is a community of Ruby developers."
# 加长string长度, 增加游戏难度
string_1000 = string * 1000
# 随机生成100个不定长串
keywords = 100.times.collect { SecureRandom.hex(rand(5) + 1) }
# 预先拼接好正则式
reg = Regexp.union(keywords)
# 匹配整词
reg_fullword = Regexp.new("\\b#{Regexp.escape(keywords.join('|'))}\\b")

n = 1_000

Benchmark.bm do |x|
  puts 'string'
  # 使用select
  x.report { n.times { keywords.select { |keyword| string.include?(keyword) } } }
  # 使用index
  x.report { n.times { string.index(reg).nil? } }
  # 使用index匹配整词
  x.report { n.times { string.index(reg_fullword).nil? } }
  # 使用any
  x.report { n.times { keywords.any? { |k| string.include?(k) } } }

  puts

  puts 'string_1000'
  x.report { n.times { keywords.select { |keyword| string_1000.include?(keyword) } } }
  x.report { n.times { string_1000.index(reg).nil? } }
  x.report { n.times { string_1000.index(reg_fullword).nil? } }
  x.report { n.times { keywords.any? { |k| string_1000.include?(k) } } }
end

结果

       user     system      total        real
string
   0.030000   0.000000   0.030000 (  0.027699)
   0.000000   0.000000   0.000000 (  0.001021)
   0.000000   0.000000   0.000000 (  0.000865)
   0.020000   0.000000   0.020000 (  0.025702)

string_1000
   2.170000   0.010000   2.180000 (  2.175521)
   0.000000   0.000000   0.000000 (  0.001211)
   0.020000   0.000000   0.020000 (  0.012870)
   2.130000   0.000000   2.130000 (  2.136878)

#9 楼 @zfjoy520 学习了!刚刚接触 Ruby 不久,感觉要学习的地方还很多呀

第一反应是用正则,贴出 (没有测试性能):

string = "Ruby China is a community of Ruby developers."
keywords = ["Ruby", "community"]

reg = Regexp.new keywords.join("|");
# if want to test true or false
if string =~ reg
  # do something
end
# if want to get all matched string
p string.scan(reg)
# => ["Ruby", "community", "Ruby"]

希望有帮助吧 个人觉得用 string.include? t 这样的写法是 ruby 的专有福利,属于黑魔法。 正则则是通用的玩法,所有的语言都支持,都可以这样玩,这样折腾。 我一个同事给我的影响很大,他擅长用一切语言写应用。哪怕这语言重来都没摸过,他都开着 google 用它写应用。因为他的能力与语言无关。

哦,才看到前面已经帖出了类似方案。下次先看完回复再回复。😪

#13 楼 @suffering 我想了一个方法,能麻烦帮忙看下有没有什么问题吗?

keywords.each do |keyword|
  if line.include? keyword
      # do_something
      break
  end
end

#14 楼 @crayygy , 行不行在irb里跑一下不就知道了吗。这个问题之所以还有讨论的必要,是因为不同的实现方案效率不同。@zfjoy520 的回复里不仅给出了主要的实现的方案,还帖出了benchmark.

尽量不要用正则。正则的解析很慢。
考虑把 keywords 存为树结构。减少比较次数。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号