# Assigning atomic number based on atomic symbol

I currently have the following code:

unsigned int symToZ(const std::string & sym){
unsigned int atomicNum;
if(sym == "H"){atomicNum = 1;}
else if(sym == "He"){atomicNum = 2;}
else if(sym == "Li"){atomicNum = 3;}
else if(sym == "Be"){atomicNum = 4;}
else if(sym == "B"){atomicNum = 5;}
else if(sym == "C"){atomicNum = 6;}
else if(sym == "N"){atomicNum = 7;}
else if(sym == "O"){atomicNum = 8;}
else if(sym == "F"){atomicNum = 9;}
...

return atomicNum;
}


The code simply takes in a string sym (which is the atomic symbol, such as He, F, Au, and so on), and converts it to the respective atomic number. As you can see, it involves a lot of repeated code. For such simple code, I feel it is needlessly complex.

How can this be done more effectively?

The most efficient way is to use unordered_map since C++11:

int main(int argc, const char * argv[]) {
std::unordered_map<std::string, unsigned int> symbol_to_number = {
{"H",  1},
{"He", 2},
{"Li", 3},
// And so on..
};

// Usage:
std::cout << symbol_to_number["H"] << std::endl;
return 0;
}


Above, you get the number out of a symbol in constant time which improves on going through a lot of if statements.

• Does this work for three different things, such as {"H", 1, 1.008}? Dec 10, 2016 at 17:23
• @Idempotence Not directly, but yes: you can wrap 1 and 1.008 into a pair and use it like a value for "H". Dec 10, 2016 at 17:24
unsigned int symToZ(const std::string& sym)
{
std::unordered_map<std::string, unsigned int> table = {
{"He", 2}
, {"Li", 3}
, {"Be", 4}
, {"B", 5}
, {"C", 6}
, {"N", 7}
, {"O", 8}
, {"F", 9}
};

return table[sym];
}


Notice that operator[] inserts elements if they do not already exist in the map. If you do not want this behavior or you want to handle the error differently than returning a value-initialized unsigned int then do the following:

unsigned int symToZ(const std::string& sym)
{
const std::unordered_map<std::string, unsigned int> table = {
{"He", 2}
, {"Li", 3}
, {"Be", 4}
, {"B", 5}
, {"C", 6}
, {"N", 7}
, {"O", 8}
, {"F", 9}
};

auto it = table.find(sym);
if (it != std::end(table))
{
return *it;
}
else
{
// some error
}
}

• That table needs to be static const unless you want to rebuild it for each conversion. Dec 11, 2016 at 9:27

This look like a typical case of mapping values to a key.

Enter std::map:

std::map<std::string,long double> atomic_num_map;
void init_map()
{
atomic_num_map["He"] = 2;
//and so on for all symbols
}
long double symToZ(const std::string & sym){
auto i = atomic_num_map.find(sym);
if(i != atomic_num_map.end())
return *i.second;
else
{
//handle error
}
}


In most cases this will work but if you are really serious about performance there is another version (std::vector with std::lower_bound)

std::vector<std::pair<std::string,long double>> atomic_num_map;
void init_map()
{
atomic_num_map.push_back(std::pair<std::string,long double>("He",2));
//and so on for all symbols

std::sort(atomic_num_map.begin(),atomic_num_map.end(), [](const auto & a,const auto & b){
return a.first < b.first;
});
}
long double symToZ(const std::string & sym){
auto i = std::lower_bound(atomic_num_map.begin(),atomic_num_map.end(), sym,[](const auto & a,const auto & b){
return a.first < b;
});
if(i != atomic_num_map.end() && *i.first == sym)
return *i.second;
else
{
//return an error
}
}


now this a lot faster, but if you need to go even faster there is one last thing that you can do, avoid using std::string.

option one, if your key string is not longer that 4 character, you can use ints as a key

atomic_num_map.push_back(std::pair<int,long double>('He',2));
atomic_num_map.push_back(std::pair<int,long double>('B', /*whatever value*/));
atomic_num_map.push_back(std::pair<int,long double>('O', /*whatever value*/)); //and so on


option two is to use a hash function

Important node: don't forget to handle the case where you have invalid strings. You might also want to consider converting the string to a lower case to avoid case mismatches