检测字符串中的字符

时间:2015-08-28 19:39:13

标签: ruby string performance

我在代码战中玩ruby。任务是创建一个接受字符串并返回长度为26 10 s的字符串的方法。字符串的26个字符对应于字母表中的每个字母(大写或小写),如果字母在字符串中,则为1,如果不是,则为0。如果字符串中包含aA,则返回字符串的第一个字符为1,否则为0,如果bB }是,第二个是1,依此类推。例如:

change('a   **&  bZ') # => '11000000000000000000000001'

解决方案:

def change input 
  ('a'..'z').to_a.join.gsub(/[#{a.scan(/[a-zA-Z]/).uniq.join}]/i,'1').gsub(/\D/,'0')
end

VS。

def change input 
  ('a'..'z').map { |letter| input.downcase.include?(letter) ? '1' : '0' }.join
end

如何判断哪种解决方案更优化?可以有更优化的。

2 个答案:

答案 0 :(得分:4)

n为输入中的字母数,m为字母表中的字母数。

input.scan(/[a-zA-Z]/).uniq.join

O(n) + O(n) + O(n)。幸运的是,您只执行了一次此操作(当gsub的模式被评估时)。因此,您的复杂性总计为2*O(m) + 3*O(n) + O(m) = O(max(n, m))

<小时/> 另一方面,

input.downcase.include?(letter)

O(n),但会对字母表中的每个字母执行,O(m*n) + O(m) = O(m*n)

<小时/> 因此,第一个解决方案渐渐变得更好,如O(max(n, m)) < O(m*n)

除非您将字母表中的字母数量视为小常量,否则它们都是O(n),这只是基准测试的问题。

你可以看到两者都是线性的:

With regex; first solution

No regex; second solution

在随机 1000 字母字符串上运行 100_000 迭代,得到以下结果(使用 cruby 2.2.2 ):

       user     system      total        real
  36.160000   0.000000  36.160000 ( 36.182512)
   3.910000   0.000000   3.910000 (  3.915191)

所以在实践中,第二种解决方案远非优越。

它也更具可读性。

答案 1 :(得分:1)

不是你的问题的答案(哪一个是最有效的),而是一个使用二进制算法和ascii表的想法:

def change input
    res = 0
    input.each_byte { |c|
        res |= c.between?(97,122) ? 1<<(122-c) : c.between?(65,90) ? 1<<(90-c) : 0
    }
    "%026b" % res
end

s = "Portez ce vieux whisky au juge blond qui fume"
puts change s 

此代码使用ascii范围97-122表示小写字母,65-90表示大写字母。 each_byte为每个字母返回ascii代码c。如果字母为小写(例如x122-c则返回122-120,因此2即相应位的位置。 1<<2向右移动数字1的位并获得100(二进制),然后带res的按位运算符|(OR)给出0 | 100 = 100所以0000 0000 0000 0000 0000 0001 00(没有空格,并添加了前导零)。

优点:字符串只解析一次,不需要创建数组,只需要一个字符串操作(结尾处的格式化字符串)。该算法仅使用处理器能够非常快速地执行的操作。

通知:

此代码能够处理utf8字符串而无需修改,因为多字节字符不使用80(十六进制)下的值。

为了获得更好的表现,您可以使用简单的数字比较替换between?(...,...)方法:

res |= c>96 ? c<123 ? 1<<(122-c) : 0 : c<91 ? c>64 ? 1<<(90-c) : 0 : 0

通过此更改,此代码至少比第二种方式快2倍。