算法中的Ruby可枚举#计数性能问题
我已经用Ruby解决了一些性能问题,但我不明白这些问题 这是我的密码:算法中的Ruby可枚举#计数性能问题,ruby,algorithm,Ruby,Algorithm,我已经用Ruby解决了一些性能问题,但我不明白这些问题 这是我的密码: def word_subsets(a, b) occurrence_map = max_occurrences(b) a.select do |w| word_occurrence = letter_occurrence(w) occurrence_map.all? { |k, v| v <= word_occurrence[k] } end end def
def word_subsets(a, b)
occurrence_map = max_occurrences(b)
a.select do |w|
word_occurrence = letter_occurrence(w)
occurrence_map.all? { |k, v| v <= word_occurrence[k] }
end
end
def letter_occurrence(word)
Hash.new(0).tap do |occurrences|
word.each_char { |c| occurrences[c] += 1 }
end
end
def max_occurrences(b)
Hash.new(0).tap do |occurrences|
b.each do |word|
word_occurrences = letter_occurrence(word)
word.each_char { |c| occurrences[c] = [occurrences[c], word_occurrences[c]].max }
end
end
end
这导致了重要的性能改进,我真的不明白为什么。我不明白为什么,因为我最初的编写方式并没有为每个字符调用count方法。我认为调用这个方法会迭代数组,所以我不会这样做,而是只计算一次所有发生的事件。但我的方法似乎更糟,我不知道为什么
使用count方法确实提高了代码的性能,但我认为它仍然很糟糕,因此如果您能给我一些建议或线索,那就太好了。在您的版本中,您确实没有使用
count
。相反,你做了更糟糕的事情。您仍然在迭代字符串(在字母\u出现处),并且还正在构建哈希(这是额外的工作)
Ruby的字符串#count
(另一个版本正在使用)是。这一点以及它没有构建临时哈希的事实可以解释性能上的差异。在您的版本中,您确实没有使用count
。相反,你做了更糟糕的事情。您仍然在迭代字符串(在字母\u出现处),并且还正在构建哈希(这是额外的工作)
Ruby的字符串#count
(另一个版本正在使用)是。这一点以及它没有构建临时散列的事实可以解释性能上的差异。既然您的答案已经得到回答,我将提出一个我认为应该相对有效的替代解决方案。关键是在迭代a
中的单词之前,我为b
中的每个单词创建计数哈希
a = ["dangled", "glad", "gladden", "dogged"]
b = ["gad", "lag", "dad"]
ba = b.map { |w| w.each_char.with_object(Hash.new(0)) { |c,h| h[c] += 1 } }
#=> [{"g"=>1, "a"=>1, "d"=>1}, {"l"=>1, "a"=>1, "g"=>1}, {"d"=>2, "a"=>1}]
a.select do |w|
ah = w.each_char.with_object(Hash.new(0)) { |c,h| h[c] += 1 }
ba.all? { |wh| wh.all? { |c,cnt| ah.fetch(c,0) >= cnt } }
end
#=> ["dangled", "gladden"]
当w=“dogged”
在a.中选择do | w |……
时,我们获得ah
的以下信息:
ah = w.each_char.with_object(Hash.new(0)) { |c,h| h[c] += 1 }
#=> {"d"=>2, "o"=>1, "g"=>2, "e"=>1}
当wh={“g”=>1,“a”=>1,“d”=>1}
时
wh.all? { |c,cnt| ah.fetch(c,0) >= cnt }
首先执行
c = "g"
cnt = 1
ah.fetch("g", 0) >= 1 #=> 2 >= 1 => true
由于“g”
通过了测试,我们接下来计算
c = "a"
cnt = 1
ah.fetch("a", 0) >= 1 #=> 0 >= 1 => false
ah.fetch(“a”,0)
返回0
,因为ah
没有键“a”
Ash.fetch(“a”,0)>=1
返回false
,wh.all?{…}
返回false
,因此未选择w=“dogged”
通过对ba
中的散列进行重新排序(例如,通过减少字长)和/或对ba
中每个元素的键/值对进行重新排序(例如,通过减少英文文本中的值或增加频率['z'
首先,'e'
最后),可以提高性能.既然您的答案已经得到了回答,我将提出一个我认为应该相对有效的替代解决方案。关键是在迭代a
中的单词之前,我为b
中的每个单词创建计数哈希
a = ["dangled", "glad", "gladden", "dogged"]
b = ["gad", "lag", "dad"]
ba = b.map { |w| w.each_char.with_object(Hash.new(0)) { |c,h| h[c] += 1 } }
#=> [{"g"=>1, "a"=>1, "d"=>1}, {"l"=>1, "a"=>1, "g"=>1}, {"d"=>2, "a"=>1}]
a.select do |w|
ah = w.each_char.with_object(Hash.new(0)) { |c,h| h[c] += 1 }
ba.all? { |wh| wh.all? { |c,cnt| ah.fetch(c,0) >= cnt } }
end
#=> ["dangled", "gladden"]
当w=“dogged”
在a.中选择do | w |……
时,我们获得ah
的以下信息:
ah = w.each_char.with_object(Hash.new(0)) { |c,h| h[c] += 1 }
#=> {"d"=>2, "o"=>1, "g"=>2, "e"=>1}
当wh={“g”=>1,“a”=>1,“d”=>1}
时
wh.all? { |c,cnt| ah.fetch(c,0) >= cnt }
首先执行
c = "g"
cnt = 1
ah.fetch("g", 0) >= 1 #=> 2 >= 1 => true
由于“g”
通过了测试,我们接下来计算
c = "a"
cnt = 1
ah.fetch("a", 0) >= 1 #=> 0 >= 1 => false
ah.fetch(“a”,0)
返回0
,因为ah
没有键“a”
Ash.fetch(“a”,0)>=1
返回false
,wh.all?{…}
返回false
,因此未选择w=“dogged”
通过对ba
中的散列进行重新排序(例如,通过减少字长)和/或对ba
中每个元素的键/值对进行重新排序(例如,通过减少英文文本中的值或增加频率['z'
首先,'e'
最后),可以提高性能.但在我的版本中,我只迭代字符串一次,为什么这比为字符串中的每个字符调用count更糟糕?为什么构建散列会成为一个问题?我的意思是,我一直认为密钥访问是在恒定的时间内平均执行的。事实上,每个字都要构建一次散列。如果我们假设单词在现实世界中有一些合理的大小上限(比如不超过1000个字母),那么计算单词字符的最坏情况复杂性也是常数时间。在一般情况下,假设是英语单词,我们计算大约4.5个字符,这相当快,可能足够快,以至于初始化新哈希所需的时间是相关的。你应该通过基准测试和分析来确认这些假设。这里的一个教训是不要假设恒定时间总是比线性时间快。这在很大程度上取决于特定算法的具体特征及其所操作的数据集。在实践中,有许多复杂度更差的算法速度更快:例如,在许多复杂的分治排序算法(如timsort)中,当被排序的数组小于某个界限时,会使用插入排序,因为插入排序速度更快。但在我的版本中,我只迭代字符串一次,为什么这比为字符串中的每个字符调用count更糟糕?为什么构建散列会成为一个问题?我的意思是,我一直认为密钥访问是在恒定的时间内平均执行的。事实上,每个字都要构建一次散列。如果我们假设单词在现实世界中有一些合理的大小上限(比如不超过1000个字母),那么计算单词字符的最坏情况复杂性也是常数时间。在一般情况下,假设是英语单词,我们计算大约4.5个字符,这相当快,可能足够快,以至于初始化新哈希所需的时间是相关的。你应该通过基准测试和分析来确认这些假设。这里的一个教训是不要假设恒定时间总是比线性时间快。这在很大程度上取决于特定算法的具体特征