【问题标题】:How to efficiently determine percentage of particular character in each block of 100 characters in a string?如何有效地确定字符串中每个 100 个字符块中特定字符的百分比?
【发布时间】:2014-06-24 15:32:57
【问题描述】:

我正在尝试计算任意长度的任意给定字符串的每 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

【问题讨论】:

  • @IvayloPetrov:这应该是一个答案,而不是评论!

标签: ruby algorithm


【解决方案1】:

使用前一个块的计数。它最多更改为 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 

【讨论】:

  • 好建议,Ivaylo,但在您提供代码之前不要 +1。
【解决方案2】:

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

【讨论】:

  • 这与我的答案基本相同,尽管您的实现可能看起来更好:)
【解决方案3】:

您不必检查每个索引位置。

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

i , ................... ............... (1)

[j, 100]A 的最大计数为 m + (j - i)(当[i...j] 中没有字符为A 并且[i + 100...j + 100] 中的所有字符时会发生这种情况是A)。从(1)开始,

m + (j - i) ,

我们知道[j, 100]A 的计数小于n


考虑到这一事实,该算法可以改进如下:
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

【讨论】:

  • 与其他答案相同的问题:full_string[i, 100].count 完全取消了滑动块的效率,因为它重新访问了前一个块中的字符。
  • 啊,我刚刚注意到您的算法步骤不同。但是,我无法让您的代码在 0 以外的任何百分比限制下工作。
【解决方案4】:

请将此视为扩展评论。 (请不要投票;不情愿地接受反对票。)这只是实现@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"]}

【讨论】:

    【解决方案5】:

    如果您必须在同一块 100 个字符中搜索多个(不同的)字符,您可能想一口气完成:

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

    这将为您返回一个哈希,然后可以根据您的规则进行过滤。它将保证您只通过一次。

    【讨论】:

    • 这将访问每个字符 100 次,因为块重叠。
    【解决方案6】:

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

    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 建议的那样

    【讨论】:

    • each_cons 是获取连续块的一种有效方法,但随着块的移动,您仍然需要对每个字符进行 100 次计数。
    • @Max 你是对的,可以避免计数。由于 OP 正在努力阅读“100 个字符块”,我发现这是一个使用 each_cons 的好机会。
    • @Max 更新了答案。我坚持使用each_cons 进行演示,但我更喜欢您的解决方案:更具可读性和更简单。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-12
    • 1970-01-01
    • 2022-01-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多