Calculating protein mass

This question is part of a series solving the Rosalind challenges. For the previous question in this series, see A sequence of mistakes. The repository with all my up-to-date solutions so far can be found here.

Problem: PRTM

In a weighted alphabet, every symbol is assigned a positive real number called a weight. A string formed from a weighted alphabet is called a weighted string, and its weight is equal to the sum of the weights of its symbols.

The standard weight assigned to each member of the 20-symbol amino acid alphabet is the monoisotopic mass of the corresponding amino acid.

Given:

A protein string $P$ of length at most 1000 aa.

Return:

The total weight of $P$. Consult the monoisotopic mass table.

Sample Output:

821.392

My solution solves the sample dataset and the actual dataset given.

97283.202

PRTM.rb:

module Rosalind
RESIDUE_BY_MONOISOTOPIC_MASS = {
71.03711  => ["A"],
103.00919 => ["C"],
115.02694 => ["D"],
129.04259 => ["E"],
147.06841 => ["F"],
57.02146  => ["G"],
137.05891 => ["H"],
113.08406 => ["I", "L"],
128.09496 => ["K"],
131.04049 => ["M"],
114.04293 => ["N"],
97.05276  => ["P"],
128.05858 => ["Q"],
156.10111 => ["R"],
87.03203  => ["S"],
101.04768 => ["T"],
99.06841  => ["V"],
186.07931 => ["W"],
163.06333 => ["Y"]
}

MONOISOTOPIC_MASS_BY_RESIDUE = RESIDUE_BY_MONOISOTOPIC_MASS.
flat_map { |c, as| as.map { |a| [a, c] } }.to_h

def self.problem_prtm(aminoacids_string)
aminoacids_string.
scan(/./).
map { |residue| MONOISOTOPIC_MASS_BY_RESIDUE[residue] }.
take_while { |aminoacid| aminoacid != "" }.
inject(0){ |sum, x| sum + x }
end
end

user_input = gets.chomp
puts '%.3f' % Rosalind.problem_prtm(user_input)

The problem statement is quite similar to PROT, so the code looks quite similar to the answers given there.

Basically, I declare a bunch of value-key pairs which get inverted. This saves me from having the same value multiple times in the hash (when multiple residue weigh the same amount).

I'm mainly looking for idiomaticness and re-usability. I'm pretty sure my take_while could be written better and my naming is always open for critique.

• I am glad you liked the idea of building the inverse structure programmatically, but here I don't think it's necessary :) Only two aminoacids have the same weight, I'd write the reversed hash.

• Why residue instead of aminoacid?

• I don't see why you need take_while here.

• xs.reduce(0) { |acc, x| acc.method(x) } -> xs.reduce(0, :method).

• Minor detail: personally I prefer explicit uses of $stdin.readline instead of gets. I'd write: module Rosalind MONOISOTOPIC_MASS_BY_AMINOACID = { "A" => 71.03711, "C" => 103.00919, "D" => 115.02694, "E" => 129.04259, "F" => 147.06841, "G" => 57.02146, "H" => 137.05891, "I" => 113.08406, "L" => 113.08406, "K" => 128.09496, "M" => 131.04049, "N" => 114.04293, "P" => 97.05276, "Q" => 128.05858, "R" => 156.10111, "S" => 87.03203, "T" => 101.04768, "V" => 99.06841, "W" => 186.07931, "Y" => 163.06333, } def self.problem_prtm(aminoacids_string) aminoacids_string. chars. map { |aminoacid| MONOISOTOPIC_MASS_BY_AMINOACID[aminoacid] }. reduce(0, :+) end end protein =$stdin.readline.rstrip
weight = '%.3f' % Rosalind.problem_prtm(protein)
puts(weight)

Same idea, slightly different approach:

module Rosalind
...
def self.problem_prtm(aminoacids_string)
aminoacids = aminoacids_string.chars
masses = MONOISOTOPIC_MASS_BY_AMINOACID.values_at(*aminoacids)
masses.reduce(0, :+)
end
end
• Hmmm, yes, I was wondering how to properly solve the take_while. Turns out I could've snipped the entire thing without a problem. It wasn't exactly necessary to do the hash programatically here, but since constructs like this will likely pop-up more often during the challenges I thought it was a good thing to practice. Thanks for writing another very helpful answer :-) – Mast Jun 23 '16 at 22:55
• +1. Reversing the hash to avoid repeating weight is a perfect example of premature optimization. In this case, the cure is far worse than disease. – Jonah Jun 25 '16 at 18:37