我在代码战中玩ruby。任务是创建一个接受字符串并返回长度为26 1
和0
s的字符串的方法。字符串的26个字符对应于字母表中的每个字母(大写或小写),如果字母在字符串中,则为1
,如果不是,则为0
。如果字符串中包含a
或A
,则返回字符串的第一个字符为1
,否则为0
,如果b
或B
}是,第二个是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
如何判断哪种解决方案更优化?可以有更优化的。
答案 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)
,这只是基准测试的问题。
你可以看到两者都是线性的:
在随机 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
。如果字母为小写(例如x
)122-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倍。