算法中的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

我已经用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 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”

As
h.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”

As
h.fetch(“a”,0)>=1
返回
false
wh.all?{…}
返回
false
,因此未选择
w=“dogged”


通过对
ba
中的散列进行重新排序(例如,通过减少字长)和/或对
ba
中每个元素的键/值对进行重新排序(例如,通过减少英文文本中的值或增加频率[
'z'
首先,
'e'
最后),可以提高性能.

但在我的版本中,我只迭代字符串一次,为什么这比为字符串中的每个字符调用count更糟糕?为什么构建散列会成为一个问题?我的意思是,我一直认为密钥访问是在恒定的时间内平均执行的。事实上,每个字都要构建一次散列。如果我们假设单词在现实世界中有一些合理的大小上限(比如不超过1000个字母),那么计算单词字符的最坏情况复杂性也是常数时间。在一般情况下,假设是英语单词,我们计算大约4.5个字符,这相当快,可能足够快,以至于初始化新哈希所需的时间是相关的。你应该通过基准测试和分析来确认这些假设。这里的一个教训是不要假设恒定时间总是比线性时间快。这在很大程度上取决于特定算法的具体特征及其所操作的数据集。在实践中,有许多复杂度更差的算法速度更快:例如,在许多复杂的分治排序算法(如timsort)中,当被排序的数组小于某个界限时,会使用插入排序,因为插入排序速度更快。但在我的版本中,我只迭代字符串一次,为什么这比为字符串中的每个字符调用count更糟糕?为什么构建散列会成为一个问题?我的意思是,我一直认为密钥访问是在恒定的时间内平均执行的。事实上,每个字都要构建一次散列。如果我们假设单词在现实世界中有一些合理的大小上限(比如不超过1000个字母),那么计算单词字符的最坏情况复杂性也是常数时间。在一般情况下,假设是英语单词,我们计算大约4.5个字符,这相当快,可能足够快,以至于初始化新哈希所需的时间是相关的。你应该通过基准测试和分析来确认这些假设。这里的一个教训是不要假设恒定时间总是比线性时间快。这在很大程度上取决于特定算法的具体特征