3
\$\begingroup\$

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?

\$\endgroup\$

3 Answers 3

6
\$\begingroup\$

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.

\$\endgroup\$
2
  • \$\begingroup\$ Does this work for three different things, such as {"H", 1, 1.008}? \$\endgroup\$
    – esote
    Dec 10, 2016 at 17:23
  • 1
    \$\begingroup\$ @Idempotence Not directly, but yes: you can wrap 1 and 1.008 into a pair and use it like a value for "H". \$\endgroup\$
    – coderodde
    Dec 10, 2016 at 17:24
2
\$\begingroup\$

Use unordered_map

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
    }
}
\$\endgroup\$
1
  • \$\begingroup\$ That table needs to be static const unless you want to rebuild it for each conversion. \$\endgroup\$ Dec 11, 2016 at 9:27
1
\$\begingroup\$

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

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.