如何有效地确定字符串中每个100个字符块中特定字符的百分比?

时间:2014-06-24 15:32:58

标签: ruby algorithm

我正在尝试计算任意长度的任何给定字符串的每个100块子字符串中特定字符的百分比。我有一个如下所示的工作版本,但给定的字符串可能很长 - 数千到数百万个字符。

该字符串将包含不超过8个不同的字符:A,B,C,D,E,F,G和H.

我需要扫描每个100个字符的块并确定该块中给定字符的百分比。如果百分比大于确定的量,则记录块索引。我发现很难解释“100个字符块”是什么。我不需要将字符串拆分为100个字符块,我需要从每个字符开始并读取接下来的99个字符,然后对每个字符重复直到结束。比如,读取[0..99],[1..100],[2..101],[3..102],[4..103]等等。

我目前正在强行进行计算,但速度相当慢。是否有一种聪明的方法可以提高效率?

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

6 个答案:

答案 0 :(得分:4)

使用上一个区块中的计数。它最多改变2.让我举一个例子。如果您在5区块中A出现2..101,并且您想计算3..102的计数,则只需检查位置2是否有A和如果在102位置,您有A。例如,如果A上有102,但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 。你需要再看三个案例。使用它,我相信它会快得多。

以下是一些示例代码:

{{1}}

答案 1 :(得分:3)

当字符离开块时,使用each_char和索引查看后面:

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次的其他答案不同,此算法仅访问每个字符两次:一次进入块时,一次离开时。

答案 2 :(得分:1)

您不必检查每个指数位置。

假设错误限制(完整字符串长度乘以百分比限制)为 n ,并且您在位置{的子字符串中获得字符A m 计数{1}}。如果 m 小于 n ,那么您可以跳过索引,以便下一个要检查的索引是[i, 100],因为任何 m 这样:

i&lt; j&lt; i +(n - m),...................................... .....(1)

[i + (n - m), 100]A的最大数量为 m +(j - i)(当[j, 100]中的任何字符都不是{{1}时,会发生这种情况} [i...j]中的所有字符都是A)。从(1)开始,

m +(j - i)&lt; Ñ

我们知道[i + 100...j + 100]A的计数小于 n

<小时/> 考虑到这一事实,该算法可以改进如下:

A

答案 3 :(得分:1)

请将此视为延伸评论。 (请不要赞成;请求得到的支持。)这只是实现@Ivaylo建议的算法的一种方式。

编辑:正如我即将发布的那样,我看到@Ivaylo已经实现了。无论如何,我会发布这个,作为另一种表述,但请再次将其作为对他答案的评论。

<强>代码

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"]}

答案 4 :(得分:0)

如果你必须在同一个100字符的块中搜索几个(不同的)字符,你可能想要一次性完成:

def chars_in_block(block)
  result = Hash.new(0)
  block.each_char { |c| result[c] += 1 }
  result
end

这将为您提供一个哈希值,然后可以根据您的规则进行过滤。它会保证你只做一次通行证。

答案 5 :(得分:0)

要拥有100个字符的数组窗口,您可以使用Enumerable mixin中的each_cons。而不是

while n < (full_string.length - 99) do
  sub_string =  full_string[n..(n+99)] 

  # .. your code ..

  n += 1
end

你这样做

full_string.each_char.each_cons(100) do |sub_string|
  # .. your code ..
end

因为它只使用迭代器,所以它应该更有效,更快。

如果您需要索引(适用于error_index),则可以使用Enumerator课程中的with_index

这是您重写的代码

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建议