Поскольку проблема с вашим кодом было разъяснено, я хотел бы предложить более "Рубин-подобный" подход:
TEST = ['G', 'A', 'T', 'C']
def scan_str(arr)
TEST.each_with_object({}) {|c,h| h[c] = arr.each_with_object(Hash.new(0)) {|line, hh| \
line.chars.each_with_index {|s,i| hh[i] += 1 if s==c}}}
end
arr = ["CTAGATA","CCCGAT","AAATT","TTCAAATGA"]
scan_str(arr)
# => {"G"=>{3=>2, 7=>1}, \
# => "A"=>{2=>2, 4=>3, 6=>1, 0=>1, 1=>1, 3=>1, 5=>1, 8=>1}, \
# => "T"=>{1=>2, 5=>2, 3=>1, 4=>1, 0=>1, 6=>1}, \
# => "C"=>{0=>2, 1=>1, 2=>2}}
Несколько пунктов:
- Вероятно, наиболее удобно помещать результаты в хэш. Здесь у меня есть
scan_str
, возвращающий хэш, ключи которого являются элементами TEST
. Значение каждой клавиши само по себе является хешем, причем каждая клавиша является позицией смещения линии, а связанное значение представляет собой количество раз, когда буква, указанная внешним ключом, находится в этом положении.
- Я сначала перебираю элементы TEST, используя
Enumerable#each_with_object
, причем объектом по умолчанию является пустой хеш {}
. Внутри блока хэш ссылается на h
. Альтернативой было бы определить пустой (h = {}
) в строке выше, а затем вместо этого использовать TEST.each {|c|...
. Если бы я сделал это, было бы также необходимо добавить строку h
в конце метода, чтобы хэш был возвращен.
- Для каждого элемента
c
из TEST
, я перебираю линии массива, снова используя each_with_object
. Однако на этот раз значением по умолчанию для объекта является Hash.new(0)
, который создает хэш со значениями по умолчанию, равными нулю. При этом, когда hh[i] += 1
выполняется во внутреннем цикле, нам не нужно проверять, имеет ли hh
ключ i
; если это не так, Ruby сначала выполняет hh[i] = 0
(ноль является значением по умолчанию), затем hh[i] += 1 => 1
.
- Для каждой строки
line.chars
преобразует строку в массив символов. Затем я повторяю с Enumerable#each_with_index
. Внутри блока символ (строка длины один) и смещение линии ссылаются на s
и i
соответственно.
Существует несколько способов получить желаемый результат. Первым и, вероятно, самым простым было бы просто изменить код, который я уже предложил. Я сделаю это позже сегодня. Во-вторых, использовать приведенный выше код как «вспомогательный метод».
Используйте вспомогательный метод»
Чтобы использовать код, который мы уже имеем, переименовать метод scan_str
выше scan_str_helper
и добавить:
def scan_str(arr)
h = scan_str_helper(arr)
posh = Hash[h.values.map(&:keys).flatten.uniq.map {|e| \
[e,Hash[TEST.zip([0]*TEST.size)]]}]
h.each {|k,v| v.each {|kk,vv| posh[kk][k] += vv}}
posh.each_with_object({}) {|(k,v),hp| tot = 1.0 * v.values.reduce(&:+); \
hp[k] = Hash[v.keys.zip(v.values.map {|e| e/tot})]}
end
scan_str(arr)
# {3=>{"G"=>0.5, "A"=>0.25, "T"=>0.25, "C"=>0.0}, 7=>{"G"=>1.0, "A"=>0.0, "T"=>0.0, "C"=>0.0},
# 2=>{"G"=>0.0, "A"=>0.5, "T"=>0.0, "C"=>0.5}, 4=>{"G"=>0.0, "A"=>0.75, "T"=>0.25, "C"=>0.0},
# 6=>{"G"=>0.0, "A"=>0.5, "T"=>0.5, "C"=>0.0}, 0=>{"G"=>0.0, "A"=>0.25, "T"=>0.25, "C"=>0.5},
# 1=>{"G"=>0.0, "A"=>0.25, "T"=>0.5, "C"=>0.25},
# 5=>{"G"=>0.0, "A"=>0.3333333333333333, "T"=>0.6666666666666666, "C"=>0.0},
# 8=>{"G"=>0.0, "A"=>1.0, "T"=>0.0, "C"=>0.0}}
Несколько дополнительных примечаний:
h.values.map(&:keys).flatten.uniq
=> [3, 7, 2, 4, 6, 0, 1, 5, 8] `просто создает массив смещений позиции, которые содержат один или несколько элементов TEST.
h.keys.zip([0]*TEST.size) => h.keys.zip([0, 0, 0, 0]) => Hash[["G",0], ["A",0], ["T",0], ["C",0]]] => {"G"=>0, "A"=>0, "T"=>0, "C"=>0}
, поэтому для e
= 3 (скажем), Hash[3, {"G"=>0, "A"=>0, "T"=>0, "C"=>0}] => {3=>{"G"=>0, "A"=>0, "T"=>0, "C"=>0}}
.
- Вместо
h.keys.zip([0]*TEST.size)
у вас может возникнуть соблазн написать a = [0]*TEST.size; TEST.zip(a)
. Это не работает. Я оставлю это вам, чтобы понять, почему.
h.each {|k,v| v.each {|kk,vv| posh[kk][k] += vv}}
заполняет хэш posh => # {3=>{"G"=>2, "A"=>1, "T"=>1, "C"=>0}, 7=>{"G"=>1, "A"=>0, "T"=>0, "C"=>0}, # 2=>{"G"=>0, "A"=>2, "T"=>0, "C"=>2}, 4=>{"G"=>0, "A"=>3, "T"=>1, "C"=>0}, # 6=>{"G"=>0, "A"=>1, "T"=>1, "C"=>0}, 0=>{"G"=>0, "A"=>1, "T"=>1, "C"=>2}, # 1=>{"G"=>0, "A"=>1, "T"=>2, "C"=>1}, 5=>{"G"=>0, "A"=>1, "T"=>2, "C"=>0}, # 8=>{"G"=>0, "A"=>1, "T"=>0, "C"=>0}}
.
- Последняя строка просто преобразует числа вхождений в дроби. Например,
3=>{"G"=>2, "A"=>1, "T"=>1, "C"=>0}
преобразуется в 3=>{"G"=>0.5, "A"=>0.25, "T"=>0.25, "C"=>0.0}
модификации исходного кода
def scan_str(arr)
a = Array.new(arr.map(&:size).max).map {|e| \
Hash[TEST.zip(Array.new(TEST.size,0))]}
arr.each {|s| s.chars.each_with_index {|c,i| TEST.each \
{|ss| a[i][ss] += 1 if c == ss}}}
Hash[a.map.with_index {|h,i| tot = 1.0 * h.values.reduce(&:+); tot > 0.0 ? \
[i, Hash[h.keys.zip(h.values.map {|e| e/tot})]] : nil}.compact]
end
- Первый оператор создает массив
a
, г-й элемент, соответствующий характеру смещения я в каждой строке. Значение i-го элемента - это хеш, о котором говорится в следующей заметке, при этом все значения равны нулю.
- Второе заявление заполняет массив
a
: # => [{"G"=>0, "A"=>1, "T"=>1, "C"=>2}, {"G"=>0, "A"=>1, "T"=>2, "C"=>1}, # => {"G"=>0, "A"=>2, "T"=>0, "C"=>2}, {"G"=>2, "A"=>1, "T"=>1, "C"=>0}, # => {"G"=>0, "A"=>3, "T"=>1, "C"=>0}, {"G"=>0, "A"=>1, "T"=>2, "C"=>0}, # => {"G"=>0, "A"=>1, "T"=>1, "C"=>0}, {"G"=>1, "A"=>0, "T"=>0, "C"=>0}, # => {"G"=>0, "A"=>1, "T"=>0, "C"=>0}]
.
- Последнее утверждение преобразует каждый элемент
a
в хэш, если сумма значений положительна; еще до nil
. compact
удаляет все элементы, которые являются nil
. Помещение Hash[
и начало и ]
в конце преобразует массив в хэш, который возвращается scan_str
.
- Обратите внимание, что этот подход дает тот же результат, что и метод, который используется методом «помощник», хотя порядок элементов хэша отличается.
можно и использовать хэш вместо этого? –
Вы можете разместить свой желаемый результат? – miah
Синтаксис 'position [x + 1,0]' не ссылается на массив массивов, как вы думаете. У вас нет «многомерного массива» в вашем вопросе, у вас есть «Array», где первым элементом является «Array» –