Ruby 如何有效地确定字符串中100个字符的每个块中特定字符的百分比?
我试图计算任意长度字符串的每个100块子字符串中特定字符的百分比。我有一个如下所示的工作版本,但给定的字符串可能非常长—10秒,数千到数百万个字符 字符串将由不超过8个不同的字符组成:A、B、C、D、E、F、G和H 我需要扫描每个100个字符的块,并确定该块中给定字符的百分比。如果百分比大于确定的量,则记录块索引。我发现很难解释什么是“100字符块”。我不需要将字符串分成100个字符的块,我需要从每个字符开始读取接下来的99个字符,然后对每个字符重复直到结束。比如读[0..99]、[1..100]、[2..101]、[3..102]、[4..103]等等 我目前正在强制计算,但速度相当慢。有没有一种聪明的方法可以让这更有效Ruby 如何有效地确定字符串中100个字符的每个块中特定字符的百分比?,ruby,algorithm,Ruby,Algorithm,我试图计算任意长度字符串的每个100块子字符串中特定字符的百分比。我有一个如下所示的工作版本,但给定的字符串可能非常长—10秒,数千到数百万个字符 字符串将由不超过8个不同的字符组成:A、B、C、D、E、F、G和H 我需要扫描每个100个字符的块,并确定该块中给定字符的百分比。如果百分比大于确定的量,则记录块索引。我发现很难解释什么是“100字符块”。我不需要将字符串分成100个字符的块,我需要从每个字符开始读取接下来的99个字符,然后对每个字符重复直到结束。比如读[0..99]、[1..100
def calculate_percentage_errors full_string, searched_character, percentage_limit
# full_string: ABCDGFGEDCBADDEGDCGGBCDEEFGAAAC.......
# searched_character: A
# percentage_limit: 0.5
n = 0
error_index = []
while n < (full_string.length - 99) do
#grab the string 1..100, 2..101 ....
sub_string = full_string[n..(n+99)]
# determine the number of characters in the string
character_count = (100 - sub_string.gsub(searched_character, '').length)
if (character_count/100.0) > percentage_limit
# record the index if percentage exceeds limit
error_index << [(n+1),(n+100)]
end
n += 1
end
return error_index
end
如果您必须在100个字符的同一块中搜索多个不同的字符,您可能需要一次搜索:
def chars_in_block(block)
result = Hash.new(0)
block.each_char { |c| result[c] += 1 }
result
end
这将返回一个散列,然后可以根据您的规则进行过滤。它将保证您只通过一次。使用上一块的计数。最多更改2次。让我举个例子。如果在块2..101中有5次A出现,并且您想要计算3..102的计数,您可以简单地检查位置2处是否有A,位置102处是否有A。例如,如果102处有A,而不是2处,则计数将为6。你需要再看三个案子。我相信用这个会快得多 以下是一些示例代码:
def calculate_percentage_errors full_string, searched_character, percentage_limit
count = full_string[0..99].count(searched_character)
error_index = []
error_index << full_string[0..99] if count / 100.0 > percentage_limit
1.upto(full_string.length - 100).each do |index|
count -= 1 if searched_character == full_string[index - 1]
count += 1 if searched_character == full_string[index + 99]
error_index << full_string[index, index + 99] if count / 100.0 > percentage_limit
end
error_index
end
要拥有100个字符的数组窗口,可以使用from Enumerable mixin。所以不是
while n < (full_string.length - 99) do
sub_string = full_string[n..(n+99)]
# .. your code ..
n += 1
end
因为它只使用迭代器,所以它应该更高效、更快
如果需要错误索引的索引,可以使用from枚举器类
这是您的代码重写
def calculate_percentage_errors(full_string, searched_character, percentage_limit)
# full_string: ABCDGFGEDCBADDEGDCGGBCDEEFGAAAC.......
# searched_character: A
# percentage_limit: 0.5
error_index = []
threshold = (percentage_limit * 100)
count = nil
full_string.each_char.each_cons(100).with_index do |sub_string, index|
# count searched characters the first time, then adjust as characters are read
if count.nil?
count = sub_string.count(searched_character)
else
count += 1 if sub_string.last == searched_character
end
# record the index if percentage exceeds limit
error_index << [index + 1, index + 100] if count > threshold
# adjust count
count -= 1 if sub_string.first == searched_character
end
return error_index
end
编辑:更新答案,只对每个字符计数2次,正如@Max建议的那样,您不必在每个索引位置进行检查 假设错误限制完整字符串长度乘以百分比限制为n,并且在位置[i,100]处的子字符串中得到字符A的m个计数。如果m小于n,则可以跳过该索引,以便下一个要检查的索引为[i+n-m,100],因为对于任何j: i
def calculate_percentage_errors full_string, searched_character, percentage_limit
limit = (full_string.length * percentage_limit / 100.0).to_i
error_index = []
i = 0
while i < (full_string.length - 99) do
margin = limit - full_string[i, 100].count(searched_character)
if margin > 0
i += margin
else
error_index << [i + 1, i + 100]
i += 1
end
end
error_index
end
使用每个字符和索引在字符离开块时向后查看:
def calc_errors string, char, threshold
errors = []
count = 0
string.each_char.with_index do |c, i|
count += 1 if c == char
count -= 1 if i > 99 and string[i - 100] == char
if i >= 99
if count > threshold
errors << [i - 99, i]
end
end
end
errors
end
与其他答案不同,它可以访问100次字符,此算法只访问每个字符两次:一次是在进入块时,一次是在离开块时。请将此视为扩展注释。请不要投票;反对票勉强接受。这只是@Ivaylo建议的算法的一种实现方式 编辑:就在我即将发布的时候,我看到@Ivaylo已经实现了。无论如何,我会把这篇文章作为另一种表述发表,但同样,请把它当作对他的回答的评论 代码
@伊瓦洛佩特罗夫:这应该是一个答案,而不是评论!很好的建议,Ivaylo,但在您提供代码之前,不要使用+1。这将访问每个字符100次,因为块重叠。每个字符都是获得连续块的有效方法,但当块移动时,您仍然会对每个字符进行100次计数。@Max您是对的,可以避免计数。当OP正在努力阅读“100个字符块”时,我发现这是一个很好的机会来使用每个选项。@Max更新了答案。出于演示的目的,我坚持使用每种方法,但我更喜欢您的解决方案:可读性更强、更简单。与其他答案相同的问题是:full_string[I,100]。计数会完全取消滑动块的效率,因为它会重新访问前一块中的字符。啊,我只是注意到您的算法步骤不同。但是,我无法使您的代码适用于除0以外的任何百分比限制。这基本上与我的答案相同,尽管您的实现可能看起来更好:
def calc_errors string, char, threshold
errors = []
count = 0
string.each_char.with_index do |c, i|
count += 1 if c == char
count -= 1 if i > 99 and string[i - 100] == char
if i >= 99
if count > threshold
errors << [i - 99, i]
end
end
end
errors
end
def bad_blocks(str, contents, block_size, max_pct_per_block)
nbr_blocks = str.size-block_size+1
return nil if nbr_blocks < 1
max_per_block = max_pct_per_block.to_f * block_size / 100.0
# g[c] is the number of times c appears in the first block
g = block_size.times.with_object(Hash.new {|h,k|h[k]=0}) {|i,g|g[str[i]]+=1}
# Enumerate blocks
(nbr_blocks).times.with_object(Hash.new {|h,k| h[k]=[]}) do |b,h|
contents.each_with_object([]) { |c,a| h[b] << c if g[c] > max_per_block }
g[str[b]] -= 1
g[str[b+block_size]] += 1
end
end
str = "ABCCDCEEAFFFGAGG"
bad_blocks(str, 'A'..'G', 5, 40)
#=> {1=>["C"], 2=>["C"], 7=>["F"], 8=>["F"], 9=>["F"], 11=>["G"]}
bad_blocks(str, 'A'..'G', 5, 20)
#=> {0=>["C"], 1=>["C"], 2=>["C"], 3=>["C", "E"], 4=>["E"], 5=>["E"],
# 6=>["E", "F"], 7=>["F"], 8=>["F"], 9=>["F"], 10=>["F", "G"], 11=>["G"]}