我正在尝试编写一个函数scramble(str1, str2)
,如果true
个字符的一部分可以重新排列以匹配str1
,则返回str2
,否则返回false
。仅使用小写字母(a-z)。不会包含标点符号或数字。例如:
str1 = 'rkqodlw'; str2 = 'world'
应该返回true
。str1 = 'cedewaraaossoqqyt'; str2 = 'codewars'
应该返回true
。str1 = 'katas'; str2 = 'steak'
应该返回false
。这是我的代码:
def scramble(s1, s2)
#sorts strings into arrays
first = s1.split("").sort
second = s2.split("").sort
correctLetters = 0
for i in 0...first.length
#check for occurrences of first letter
occurrencesFirst = first.count(s1[i])
for j in 0...second.length
#scan through second string
occurrencesSecond = second.count(s2[j])
#if letter to be tested is correct and occurrences of first less than occurrences of second
#meaning word cannot be formed
if (s2[j] == s1[i]) && occurrencesFirst < occurrencesSecond
return false
elsif s2[j] == s1[i]
correctLetters += 1
elsif first.count(s1[s2[j]]) == 0
return false
end
end
end
if correctLetters == 0
return false
end
return true
end
我需要帮助优化此代码。请给我建议。
答案 0 :(得分:3)
这是一种有效且类似Ruby的方式。
<强>代码强>
def scramble(str1, str2)
h1 = char_counts(str1)
h2 = char_counts(str2)
h2.all? { |ch, nbr| nbr <= h1[ch] }
end
def char_counts(str)
str.each_char.with_object(Hash.new(0)) { |ch, h| h[ch] += 1 }
end
<强>实施例强>
scramble('abecacdeba', 'abceae')
#=> true
scramble('abecacdeba', 'abweae')
#=> false
<强>解释强>
这三个步骤如下。
str1 = 'abecacdeba'
str2 = 'abceae'
h1 = char_counts(str1)
#=> {"a"=>3, "b"=>2, "e"=>2, "c"=>2, "d"=>1}
h2 = char_counts(str2)
#=> {"a"=>2, "b"=>1, "c"=>1, "e"=>2}
h2.all? { |ch, nbr| nbr <= h1[ch] }
#=> true
最后一句话相当于
2 <= 3 && 1 <= 2 && 1 <= 2 && 2 <=2
方法char_counts
构造有时称为“计数哈希”的方法。要了解char_counts
的工作原理,请参阅Hash::new,尤其是提供默认值作为新参数的效果的说明。简而言之,如果定义了哈希h = Hash.new(0)
,那么如果h
没有密钥k
,则h[k]
会返回默认值,此处为0
(和哈希没有改变。)
假设,对于不同的数据,
h1 = { "a"=>2 }
h2 = { "a"=>1, "b"=>2 }
然后我们会找到1 <= 2 #=> true
但2 <= 0 #=> false
,因此该方法会返回false
。第二个比较是2 <= h1["b"]
。由于h1
没有密钥"b"
,h1["b"]
会返回默认值0
。
方法char_counts
实际上是编写如下表达方法的简短方法。
def char_counts(str)
h = {}
str.each_char do |ch|
h[ch] = 0 unless h.key?(ch) # instead of Hash.new(0)
h[ch] = h[c] + 1 # instead of h[c][ += 1
end
h # no need for this if use `each_with_object`
end
请参阅Enumerable#each_with_object,String#each_char(优先于String.chars,后者生成不需要的临时数组,而前者返回枚举数)和Hash#key?(或{{1 }},Hash#has_key?
或Hash#include?
)。
另类
Hash#member?
我发现方法def scramble(str1, str2)
str2.chars.difference(str1.chars).empty?
end
class Array
def difference(other)
h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
reject { |e| h[e] > 0 && h[e] -= 1 }
end
end
非常有用我建议将它添加到Ruby Core(here)。呃,反应一直没有给人留下深刻的印象。
答案 1 :(得分:1)
一种方式:
def scramble(s1,s2)
s2.chars.uniq.all? { |c| s1.count(c) >= s2.count(c) }
end
另一种方式:
def scramble(s1,s2)
pool = s1.chars.group_by(&:itself)
s2.chars.all? { |c| pool[c]&.pop }
end
又一个:
def scramble(s1,s2)
('a'..'z').all? { |c| s1.count(c) >= s2.count(c) }
end
由于这似乎是from codewars,我在那里提交了我的前两个。两者都被接受了,第一个更快了。然后我看到了其他人的解决方案,并看到有人使用('a'..'z')
并且速度很快,所以我将其包括在内。
代码战“性能测试”没有明确显示,但它们总共长达45000个字母。因此,我对这些解决方案以及Cary(你的速度太慢而不能被包括在内)进行基准测试,重复进行长时间的重复(并且做了100次):
user system total real
Stefan 1 0.812000 0.000000 0.812000 ( 0.811765)
Stefan 2 2.141000 0.000000 2.141000 ( 2.127585)
Other 0.125000 0.000000 0.125000 ( 0.122248)
Cary 1 2.562000 0.000000 2.562000 ( 2.575366)
Cary 2 3.094000 0.000000 3.094000 ( 3.106834)
故事的道德? String#count
在这里很快。就像,快得离谱。几乎难以置信快(我实际上不得不运行额外的测试来相信它)。它大约每秒 19亿个字母(100次26个字母乘以2个字符串~45000个字母,全部在0.12秒内)。请注意,与我自己的第一个解决方案的不同之处仅在于我s2.chars.uniq
,并且将时间从0.12秒增加到0.81秒。这意味着这个双遍一个字符串大约需要52次通过计数的六倍。计数大约快150倍。我确实期望它非常快,因为它可能只是使用C代码搜索字节数组中的一个字节(编辑:looks like it does),但这种速度仍让我感到惊讶。
代码:
require 'benchmark'
def scramble_stefan1(s1,s2)
s2.chars.uniq.all? { |c| s1.count(c) >= s2.count(c) }
end
def scramble_stefan2(s1,s2)
pool = s1.chars.group_by(&:itself)
s2.chars.all? { |c| pool[c]&.pop }
end
def scramble_other(s1,s2)
('a'..'z').all? { |c| s1.count(c) >= s2.count(c) }
end
def scramble_cary1(str1, str2)
h1 = char_counts(str1)
h2 = char_counts(str2)
h2.all? { |ch, nbr| nbr <= h1[ch] }
end
def char_counts(str)
str.each_char.with_object(Hash.new(0)) { |ch, h| h[ch] += 1 }
end
def scramble_cary2(str1, str2)
str2.chars.difference(str1.chars).empty?
end
class Array
def difference(other)
h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
reject { |e| h[e] > 0 && h[e] -= 1 }
end
end
Benchmark.bmbm do |x|
n = 100
s1 = (('a'..'z').to_a * (45000 / 26)).shuffle.join
s2 = s1.chars.shuffle.join
x.report('Stefan 1') { n.times { scramble_stefan1(s1, s2) } }
x.report('Stefan 2') { n.times { scramble_stefan2(s1, s2) } }
x.report('Other') { n.times { scramble_other(s1, s2) } }
x.report('Cary 1') { n.times { scramble_cary1(s1, s2) } }
x.report('Cary 2') { n.times { scramble_cary2(s1, s2) } }
end