基因组范围查询ruby实现

时间:2013-11-21 15:28:56

标签: ruby algorithm rmq

我进行了一次演示测试并获得了62分的结果。我猜我的代码效率不高,无法达到最高分100分。那么如何有效地找到子字符串中的最低字符代码?例如,字符串为s="ACGTTAGTAC"。从子串s[p,q]中有效地找出最小字符是什么 - 有许多重复查询具有相同的s但不同的[p,q]。实际上,这个问题被称为范围最小查询(RMQ),并且有多种算法可以解决问题。但是我很难理解并将它们应用于这个特定的实例。任何人都可以建议如何修复代码?

# s is a string, p and q are arrays of integers with p[i] <= q[i]
def solution (s, p, q)
  len = s.length
  a = Array.new(len,0)
  for k in 0..len-1
    case s[k]
    when 'A'
      a[k] = 1
    when 'C'
      a[k] = 2
    when 'G'
      a[k] = 3
    when 'T'
      a[k] = 4
    end
  end
  s = []
  m = p.size
  for i in 0..m-1
    s << a[p[i]..q[i]].min
  end
  s
end

由于版权问题,完整的问题不会复制到此处。您可以通过此链接https://codility.com/demo/results/demoHSB3XQ-R24/阅读完整的详细信息。

1 个答案:

答案 0 :(得分:2)

扩展解决方案的问题是因为您反复扫描每个查询的输入,生成子数组并直接查找最小值。当您需要处理大量查询时,这效率很低。例如,如果任何子字符串包含“A”,则包含该子字符串的字符串也包含“A”,但您的解决方案会抛弃该先验知识并重新计算。最终结果是您的解决方案不仅可以按输入字符串的大小进行扩展,还可以将其乘以查询数。当s很长且[p,q]时,这会导致效果不佳。

您可以通过将s预处理到旨在最有效地回答查询的索引结构来改进代码的扩展。发现正确使用的结构是编码问题中的重要挑战。获得纯粹的“正确输出”代码只有一半,因此62/100的分数度量似乎是有效的。

这是一个索引结构,可以从固定字符串中有效地找到给定索引范围内的最小字符。

首先将字符串分析为两部分索引

s = "AGTCTTCGATGAAGCACATG"
len = s.length

# Index to answer "what count of each character type comes next in s"
# E.g.  next_char_instance["A"][7]  returns the instance number of "A" that is
# at or after position 7  ( == 1 )
next_char_instance = Hash[ "A" => Array.new(len), "C" => Array.new(len), 
  "G" => Array.new(len), "T" => Array.new(len) ]

# Index to answer "where does count value n of this character appear in s"
# E.g.  pos_of_char_instance["A"][1]  returns the index position of 
# the second "A" ( == 8 )
pos_of_char_instance = Hash[ "A" => Array.new, "C" => Array.new, 
  "G" => Array.new, "T" => Array.new ]

# Basic building block during iteration
next_instance_ids = Hash[ "A" => 0, "C" => 0, "G" => 0, "T" => 0 ]

# Build the two indexes - O( N )
(0...len).each do |i|
   next_instance_ids.each do | letter, next_instance_id |
     next_char_instance[letter][i] = next_instance_id
   end
   this_letter = s[i]
   pos_of_char_instance[ this_letter ] << i
   next_instance_ids[ this_letter ] += 1
end

所以那是O( N ),因为你已经迭代了一次字符串,所有其他效果都是(有效)不变的;好吧,创建数组也是O( N ),但可能快10倍,如果你发现自己在考虑O( 1.4 * N ),那么没有恐慌,在考虑纯缩放问题时,你会抛弃常数1.4。

现在你有了这个索引,可以依次询问“这个位置或之后的下一个A(或C或G)在哪里”非常有效,你可以用它快速找到一个里面的最小字符特殊范围。事实上,由于它将是固定成本查询和一些比较,因此每个查询都会O( 1 ),因此整体O( M )

# Example queries
p = [ 0, 3, 2, 7 ]    
q = [ 6, 4, 2, 9 ]    

# Search for each query - O( M )
p.zip(q).map do | a, b |
  # We need to find lowest character possible that fits into the range
  "ACGT".chars.find do | letter |
     next_instance_id = next_char_instance[ letter ][ a ]
     pos_next_instance = pos_of_char_instance[ letter ][ next_instance_id ]
     true if pos_next_instance && pos_next_instance <= b
  end
end
# => ["A", "C", "T", "A"] is output for example data

我已将此映射到字母,希望您可以看到输出1,2,3,4对此非常简单。事实上,基因组风格字母的编号和使用是谜题中的红色鲱鱼,它们对解决方案没有实际影响(除了生成仅4个字母的结构更容易编码为固定值)。

上述解决方案可能很多。您应该注意它依赖于允许修改的字母数量,并且不能作为在单个条目整数或浮点数的范围内查找最小值的答案。