Search code examples
c++stdvectorstdstring

Replace every occurrence with double in string


I'm trying to write a function whose first parameter is a string and the second parameter is vector of real numbers. The function should return as a result a new string in which each occurrence replaces the sequences "%d" or "%f" with one number each from the vector, in the order in which they appear. In doing so, if the sequence is "%d", any decimals in the number are truncated, while in the sequence "%f" they are retained.

For example, if the string reads “abc%dxx%fyy %d” and if the vector contains the numbers 12.25, 34.13, 25 and 47, the new string should read “abc12xx34.13yy 25” (data 47 which is “redundant” is simply ignored).

#include <iostream>
#include <string>
#include <vector>

std::string Replace(std::string s, std::vector < double > vek) {
  std::string str;
  int j = 0;
  for (int i = 0; i < s.length(); i++) {
    while (s[i] != '%' && i < s.length()) {
      if (s[i] != 'f' && s[i] != 'd')
        str += s[i];
      i++;
    }
    if (s[i] == '%' && (s[i + 1] == 'd' || s[i + 1] == 'f')) {
      if (s[i + 1] == 'd')
        str += (std::to_string(int(vek[j])));
      if (s[i + 1] == 'f') {
        std::string temp = std::to_string(vek[j]);
        int l = 0;
        while (temp[l] != '0') {
          str += temp[l];
          l++;
        }
      }
      j++;
      if (j > vek.size())
        throw std::range_error("Not enough elements");
      if (i == s.length()) break;
    }
  }
  return str;
}
int main() {
  try {
    std::cout<<Replace("abc%dxx%fyy %d",{12.25, 34.13, 25});
    std::cout << "\n" << "abc12xx34.13yy 25";
  } catch (std::range_error e) {
    std::cout << e.what();
  }

  return 0;
}

OUTPUT:

abc12xx34.13yy 25

abc12xx34.13yy 25

Output is correct. How could I modify this to work with less lines of code? Is there any way to make this more elegant and efficient?


Solution

  • You could use:

    1. regular expressions to search for the pattern (%d|%f), i.e., %d or %f, and
    2. a string stream to create the string to return.

    Going into some more detail:

    • The code is basically a while (std::regex_search).
    • std::regex_search will return whatever was in the input string before the matched pattern (what you want in the output string), the matched pattern (what you will need to check in order to decide if you want to write out an int or a double), and whatever is left to parse.
    • By using std::ostringstream, you can simply write out ints or doubles without converting them to strings yourself.
    • vek.at() will throw an std::out_of_range exception if you run out of data in the vector.
    • Notice as well that, whereas for this implementation it's good to pass the string s by value (since we are modifying it within the function), you should pass vek as a const reference to avoid a copy of the whole vector.

    [Demo]

    #include <iostream>
    #include <regex>
    #include <stdexcept>
    #include <sstream>
    #include <string>
    #include <vector>
    
    std::string Replace(std::string s, const std::vector<double>& vek) {
        std::regex pattern{"(%d|%f)"};
        std::smatch match{};
        std::ostringstream oss{};
        for (auto i{0}; std::regex_search(s, match, pattern); ++i) {
            oss << match.prefix();
            auto d{vek.at(i)};
            oss << ((match[0] == "%d") ? static_cast<int>(d) : d);
            s = match.suffix();
        }
        return oss.str();
    }
    
    int main() {
        try {
            std::cout << Replace("abc%dxx%fyy %d", {12.25, 34.13, 25});
            std::cout << "\n"
                      << "abc12xx34.13yy 25";
        } catch (std::out_of_range& e) {
            std::cout << e.what();
        }
    }
    
    // Outputs:
    //
    //   abc12xx34.13yy 25
    //   abc12xx34.13yy 25
    

    [EDIT] A possible way to do it without std::regex_search would be to search for the (%d|%f) pattern manually, using std::string::find in a loop until the end of the string is reached.

    The code below takes into account that:

    • the input string could not have that pattern, and that
    • it could have a % character followed by neither d nor f.

    [Demo]

    #include <iostream>
    #include <sstream>
    #include <stdexcept>
    #include <string>
    #include <vector>
    
    std::string Replace(std::string s, const std::vector<double>& vek) {
        std::ostringstream oss{};
        size_t previous_pos{0};
        size_t pos{0};
        auto i{0};
        while (previous_pos != s.size()) {
            if ((pos = s.find('%', previous_pos)) == std::string::npos) {
                oss << s.substr(previous_pos);
                break;
            }
            oss << s.substr(previous_pos, pos - previous_pos);
            bool pattern_found{false};
            if (s.size() > pos + 1) {
                auto c{s[pos + 1]};
                if (c == 'd') {
                    oss << static_cast<int>(vek.at(i));
                    pattern_found = true;
                } else if (c == 'f') {
                    oss << vek.at(i);
                    pattern_found = true;
                }
            }
            if (pattern_found) {
                ++i;
                previous_pos = pos + 2;
            } else {
                oss << s[pos];
                previous_pos = pos + 1;
            }
        }
        return oss.str();
    }
    
    int main() {
        try {
            std::cout << Replace("abc%%dx%x%fyy %d", {12.25, 34.13, 25}) << "\n";
            std::cout << "abc%12x%x34.13yy 25\n";
            std::cout << Replace("abcdxxfyy d", {12.25, 34.13, 25}) << "\n";
            std::cout << "abcdxxfyy d\n";
        } catch (std::out_of_range& e) {
            std::cout << e.what();
        }
    }
    
    // Outputs:
    //
    //   abc%12x%x34.13yy 25
    //   abc%12x%x34.13yy 25
    //   abcdxxfyy d
    //   abcdxxfyy d