C++ checking textbox value in application - c++

I'm working on an app with a map and do not want a person to be able to enter an invalid point on the map (think Latitude and longitude).
Say you have a panel that looks something like this. Note not codewise how it looks, just how it would look like if you looked at the application:
[My great application]
Textbox1 [XX:XX:XX]
Textbox2 [XX:XX:XX]
Textbox3 [XX:XX:XX]
Pushbutton [APPLY]
My main problem
I have no way of knowing if what someone enters is right in the text boxes. My TextBox is expecting 3 sets integers [XX:XX:XX]. Nothing below 00, and nothing above 99 (strictly 2 digits). For this example I will only show on int to keep it short:
int myInt = NULL; //Don't want garbage data here
bool isValid = false;
//Gets input and sets textbox to myInt
std::string myString = std::to_string(myInt); //Cast int to a string
If(myString.length() - 1 == 0) //NULL intToString.length() == 1?
{
std::cout << "Incorrect, please try again" << std::endl;
}
else
{
isValid = true;
}
if(isValid)
{
//applyChanges(); <-- MY INTS WOULD GO INTO THIS
}
then I quickly realized that 0-9 are totally valid. (00, 01, ..., 09). So this wouldn't work.
My question
How am I able to test if what a user inputs is correct? It's not quite making sense to me. I'm not sure what else I can do besides test the length.
All in all I want to find a way to get a condition so that when all is well THEN isValid = true;
Notes
I can directly access all the data in the textboxes
I have the ability to check for gaining or loss of focus

Here's a solution I came up with. Let me know if I misunderstood your problem.
The algorithm is simple:
Splits the input into 3 parts;
Make sure each is 2 digits long;
Convert to an integer and make sure its within range;
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
// Simple string split algorithm
std::vector<std::string> string_split(
std::string const& str,
char delimiter)
{
std::istringstream ss{str};
std::vector<std::string> result;
std::string line;
while(std::getline(ss, line, delimiter)) {
result.emplace_back(std::move(line));
}
return result;
}
// Validates the input
bool isValid(std::string const& input) {
// Split string on each ':'
// 00:00:00
// ^ ^
auto substr = string_split(input, ':');
// If there are not 3 parts, its malformed
if(substr.size() != 3)
return false;
// For reach result...
for(auto const& str : substr) {
// If it is not 2 digits long, error.
if(str.size() != 2)
return false;
try {
// Try converting it to an integer
// This throws on failure
auto value = std::stoi(str);
// make sure its within range
return value >= 0 && value <= 99;
} catch(...) {
// Failed to convert
return false;
}
}
return true;
}
int main()
{
// Some tests
std::vector<std::string> inputs{
"00:00:00",
"99:99:99",
"1:00:00",
"AA:00:00",
"0B:0000",
"0B:00c00",
};
// Run tests and print result
for(auto& input : inputs) {
if(isValid(input)) {
std::cout << input << " - Valid!"<< std::endl;
} else {
std::cout << input << " - Invalid!"<< std::endl;
}
}
}
Results:
$g++ -o main *.cpp
$main
00:00:00 - Valid!
99:99:99 - Valid!
1:00:00 - Invalid!
AA:00:00 - Invalid!
0B:0000 - Invalid!
0B:00c00 - Invalid!
Hope this helps,
Cheers.

I agree with markhc but you might also like using a regular expression way of doing things. The advantage is that the same code can be used to validate other type of input also and of course is much shorter.
Here is the code (parts stolen from markhc!):
#include <regex>
#include <string>
#include <iostream>
using namespace std;
int main()
{
string regx = R"([0-9]{2}:[0-9]{2}:[0-9]{2})";
smatch matches;
// some test data
std::vector<std::string> inputs{
"00:00:00",
"99:99:99",
"1:00:00",
"AA:00:00",
"0B:0000",
"0B:00c00",
};
// Run tests and print result
for (auto& input : inputs) {
if (regex_search(input, matches, regex(regx)))
{
std::cout << input << " - Valid!" << std::endl;
}
else {
std::cout << input << " - Invalid!" << std::endl;
}
}
}
This also produces as output:
00:00:00 - Valid!
99:99:99 - Valid!
1:00:00 - Invalid!
AA:00:00 - Invalid!
0B:0000 - Invalid!
0B:00c00 - Invalid!
The regular expression [0-9]{2}:[0-9]{2}:[0-9]{2} is explained below.
[0-9] means digit 0-9
{2} means 2 times
: means the separator you need
To show how flexible the regular expressions are:
..If you wanted a different format like 000:0000 for example you just have to change the regex to
[0-9]{3}:[0-9]{4} and the code remains the same.

Related

How to calculate the smallest number possible based on digits (0-9) not used in a user inputted integer?

I am trying to write a code that will accept an integer input and then calculate the smallest number possible using digits not found in the inputted integer. The possible digits would be 0-9, however, 0 can not be the leading value in the output.
For example, if the user enters:
6789
the the program would output:
102345
How can I solve this?
The lowest number possible from any set of digits (ignoring, for now, the issue of the zero) comprises those digits in order; thus, from the digits 2, 1, 6 and 3, the lowest number is 1236.
So, we can start of with a list of all digits, in order, then run through the digits in the given input number (after we have converted that to a string), removing each of those digits from our list (if it's still in it). If we end up with a list whose first element is zero, we simply swap that with the second digit.
Here's a possible implementation:
#include <iostream>
#include <string>
#include <algorithm>
int main()
{
std::string numbs = "0123456789";
int input;
std::cout << "Enter a number: ";
std::cin >> input;
std::string check = std::to_string(input); // Convert our input to a string
for (auto digit : check) { // Remove each digit in that from our list...
size_t p;
if ((p = numbs.find(digit)) != std::string::npos) numbs.erase(p, 1);
}
// A basic error check that at least one digit remains ...
if (numbs.length() == 0) {
std::cout << "No digit left with which to make a number!\n";
return 1;
}
// Swap first two digits if first is zero and there is at least one other ...
if (numbs[0] == '0' && numbs.length() > 1) std::swap(numbs[0], numbs[1]);
int answer = std::stoi(numbs);
std::cout << answer << std::endl;
return 0;
}
In this example, I have used the std::string container class from the Standard Library; in many ways, that acts like an array of characters; however, if you want to use actual arrays, you could readily adapt the shown code to use them.
Yet another implementation. Same algorithm as from Adrian . . .
#include <iostream>
#include <string>
#include <algorithm>
#include <cctype>
int main() {
// Basic string
std::string allDigits{ "0123456789" };
// Get input. Digits only
if (std::string input{}; std::getline(std::cin, input) and std::all_of(input.begin(), input.end(), std::isdigit)) {
// Erase from the allDigits string the characters that are in the input string
std::erase_if(allDigits, [&](const char d) { return std::any_of(input.begin(), input.end(), [d](const char c) { return c == d; }); });
// Take care of leading 0
if ((allDigits.length() > 1) and allDigits.front() == '0') std::swap(allDigits[0], allDigits[1]);
// Show result
std::cout << allDigits << '\n';
}
else std::cerr << "\n*** Error: Invalid input\n\n";
}

Parsing doubles and words in a string

I was working on the following exercise from The C++ Programming Language:
Read a sequence of possibly whitespace-separated (name,value) pairs,
where the name is a single whitespace-separated word and the value is
an integer or a floating-point value. Compute and print the sum and
mean for each name and the sum and mean for all names.
For example, given:
hello world5.678popcorn 8.123 rock 123 hello world 8.761 popcorn 98 rock 1.9rock2.3
The output of my implementation is:
rock: Sum (127.2), Mean (42.4)
hello world: Sum (14.439), Mean (7.2195)
popcorn: Sum (106.123), Mean (53.0615)
My implementation:
#include <iostream>
#include <string>
#include <unordered_map>
std::unordered_map<std::string, double> pairs;
std::unordered_map<std::string, int> occurences;
void save(const std::string& name, const std::string& value);
void trim(std::string& s, const std::string& chars = " ");
int main() {
std::string line;
getline(std::cin, line);
std::string name;
std::string value;
bool name_saved;
for(char c : line) {
if(!name_saved && isdigit(c)) { // reached end of name
name_saved = true;
trim(name);
value += c;
} else if(!name_saved) { // add char to name
name += c;
} else if(name_saved) {
if(isdigit(c) || (c == '.' && (value.find_first_of(".") == std::string::npos))) { // add char to value
value += c;
} else { // reached end of value
trim(value);
save(name, value);
name = "";
value = "";
name_saved = false;
if(isalpha(c)) {
name += c;
}
}
}
}
if(value != "") {
save(name, value);
}
std::cout << "\n";
for(auto pair : pairs) {
std::cout << pair.first << ": " << "Sum (" << pair.second << "), Mean (" << (pair.second / occurences[pair.first]) << ")\n";
}
return 0;
}
void save(const std::string& name, const std::string& value) {
pairs[name] += std::stod(value);
occurences[name]++;
}
void trim(std::string& s, const std::string& chars) {
s.erase(0, s.find_first_not_of(chars));
s.erase(s.find_last_not_of(chars) + 1);
}
I was wondering what would a more efficient approach to this exercise be? I feel that my code is quite messy and I would like to get some input on what I could use to clean it up and make it more compact.
There are many many different soultions. It depends a little bit on your personal programming style and what you have learned already or not.
The above example cries for "regular expressions". You can read here in the Cpp- Reference about them. And especially the function std::regex_search will be your friend.
First to the regurlar expression. You are looking for a "text" with embedded spaces ("single whitespace-separated word "), followed by a int or float. This can be expressed easily with a "regex":
([a-zA-Z]+ ?[a-zA-Z]+) ?(\d+\.?\d*)
So, first we have 1 or more alpha-characters, then an optional space and then again 1 or more alpha-characters. This makes up the name.
For the value, we have 1 or more digits, maybe followed by a "." and maybe more digits.
You get a better understanding, if you paste the regex and the test string in some online regex-tester like this.
It will give you some more detailed explanation. Espcially for the brackets "()", which form groups.
An the groups can be extracted after a std::regex_search has found a match. Meaning: If std::regex_search will find a match for the given regex, it will return true and the resulting groups can be found in std::smatch, see here for a description.
And with all this, we can define a simple for loop to get all names and values from the test string.
for (std::string s{ test }; std::regex_search(s, sm, re); s = sm.suffix())
First we will initialize the "loop-run" variable, in this case a std::string and initialize it with the given test-string. Then we will search for a match in the test-string. If there was a match, then we will find the result in sm[1] and sm[2]. After we did all operations inside the loop body, we set the "loop-run" variable to the not yet matched rest of the test string. The suffix.
To goup, calculate and aggregate the values, we use a std::map. The key is the "name" and the value is a std::pair consisting of the count of names and the sum of the associated values. So, in the loop:
for (std::string s{ test }; std::regex_search(s, sm, re); s = sm.suffix()) {
// Count the occurences of a text
aggregator[sm[1]].first++;
// Sum up the values for a text
aggregator[sm[1]].second += std::stod(sm[2]);
}
we use aggregator[sm[1]] to create the name in or retrieve the name from map. In any case, we have then a reference to the current name entry, and we can increment the count and build the running sum.
This is a very simple 3 line approach, which does already nearly all the expected work.
The rest is simple calculation of expected sum and mean values and showing everything on the screen.
Please see the full code below:
#include <iostream>
#include <string>
#include <regex>
#include <vector>
#include <iterator>
#include <map>
// The regex for words with embedded space and floats/ints
const std::regex re{ R"(([a-zA-Z]+ ?[a-zA-Z]+) ?(\d+\.?\d*))" };
int main() {
// Definition Section --------------------------------------------------------------------------------
// The input test string
std::string test{"hello world5.678popcorn 8.123 rock 123 hello world 8.761 popcorn 98 rock 1.9rock2.3"};
// Here we will store the result. The text and the associated "count" and "sum"
std::map<std::string, std::pair<unsigned int, double>> aggregator{};
std::smatch sm;
// Find, store and calculate data -------------------------------------------------------------------
// Iterate though the string and get the text and the float value
for (std::string s{ test }; std::regex_search(s, sm, re); s = sm.suffix()) {
// Count the occurences of a text
aggregator[sm[1]].first++;
// Sum up the values for a text
aggregator[sm[1]].second += std::stod(sm[2]);
}
// Output data ----------------------------------------------------------------------------------------
// Since the task is to calculate also the overall results, we will do
unsigned int countOverall{};
double sumOverall{};
// Iterate over the "text" data and output sum and mean value per text and aggregate the overall values
for (const auto& [text, agg] : aggregator) {
// Output sum and mean per text
std::cout << "\n" << text << ": Sum (" << agg.second << "), Mean (" << agg.second / agg.first << ")";
// Aggregate overall values
countOverall += agg.first;
sumOverall += agg.second;
}
// Show overall result to the user.
std::cout << "\n\nSum overall: (" << sumOverall << "), Mean overall: (" << sumOverall / countOverall << ")\n\n";
return 0;
}
If this solution is "better" or not? Please decide yourself . . .

How to fix iteration without a cause (with an input); in c++?

Just on a starting note, Please excuse the lack of c++ conventions and logic shown in this code. I'm still trying to get around formatting syntax for c++..
With this small part of a larger application building project, I am trying to create a 'data validation' type subroutine with integers and strings being the main test cases, the input coming from the intended user.
The problem is that each character of the variable input is being iterated over, even if the 'if statements' are outside of the 'for loop' (or in some other cases while loops).
An example of the error being:
Enter a value:f1o2o3
Your value is a string
Your value is a integer
Your value is a string
Your value is a integer
Your value is a string
Your value is a integer
The extra lines after the first Your value is a string and
Your value is a integer are unwanted.
I know that I could just instead return the boolean variable letexists or intexsits, with them both being iterated without a problem.
However this 'character iterating' problem has been a nuisance in other parts too and I cannot seem to find a definite, understandable fix anywhere.
I am used to using python so this 'iterating over every character while passing parameters' is quite new to me.
I have tried looking into what it could be for multiple days now. Both: searching online and asking others (in person showing them code that has problems) about the 'iteration over each character' problem, however, none seem to know why this happens.
I have solved this problem in the past by breaking a while(true) loop after the intended data value has been received, however I understand this is really bad practice and therefore want to figure out how I can improve data validation.
#include <iostream> // for std::cin, std::cout
#include <string> // for string datatype
#include <algorithm> // for std::find
#include <iterator> // for std::begin, std::end
using namespace std;
// If the 'cout's are changed to returning functions I will change this to a function itself (using int datacheck(...){...})
void datacheck(string &i) {
const char nums[] = { '0','1','2','3','4','5','6','7','8','9','\0' };
const char alph[] = { 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','\0' };
for (char& c : i) {
bool intexists = find(begin(nums), end(nums), c) != end(nums);
bool letexists = find(begin(alph), end(alph), c) != end(alph);
if (letexists || (letexists && intexists))
cout << "Your value is a string" << endl;
// This will be changed for a return value
else if (!letexists && intexists)
cout << "Your value is a integer" << endl;
// This will be changed for a return value
else
cout << "Your value has an erogenous input (Special characters or non-letter/ number related stuff.";
}
}
int main(void) {
string checkedvalue;
cout << "Enter a value: ";
cin >> checkedvalue;
cin.clear();
cin.ignore(512, '\n');
datacheck(checkedvalue);
return 0;
}
I have realised that it may be because each character is being sent through individually into the subroutine, however, if this is the case I am still unsure how to fix this.
Just to end off...
Overall, an explained solution for why the iterating happens and how it can be fixed will be greatly appreciated. If possible for general cases, however, just this case will be extremely helpful nonetheless.
Thank you.
I have to admit, i dont understand all of what you write. What I did understnad is
The problem is that each character of the variable input is being
iterated over, even if the 'if statements' are outside of the 'for
loop'
And that is wrong!
for (char& c : i) {
bool intexists = find(begin(nums), end(nums), c) != end(nums);
bool letexists = find(begin(alph), end(alph), c) != end(alph);
if (letexists || (letexists && intexists))
cout << "Your value is a string" << endl;
// This will be changed for a return value
else if (!letexists && intexists)
cout << "Your value is a integer" << endl;
// This will be changed for a return value
else
cout << "Your value has an erogenous input (Special characters or non-letter/ number related stuff.";
}
All this above is "the for loop". In simple terms the syntax for a (ranged based) for loop is
for (char& c : i) {
// body of the loop
}
Your code will be more clear and readable if you use {} also for the if, even if not needed and proper intendation:
for (char& c : i) {
bool intexists = find(begin(nums), end(nums), c) != end(nums);
bool letexists = find(begin(alph), end(alph), c) != end(alph);
if (letexists || (letexists && intexists)) {
cout << "Your value is a string" << endl;
} else if (!letexists && intexists) {
cout << "Your value is a integer" << endl;
} else {
cout << "Your value has an erogenous input (Special characters or non-letter/ number related stuff.";
}
}
In C++, the standard provides different ways to checks if an string contains alpha/numeric values:
Check if all characters are numeric: std::isalnum
Check if all characters are alpha: std::isalpha
So you can replace part of your code with:
const all_numeric = std::all_of(std::begin(str), std::end(str), [](const auto c) {
return std::isalnum(c);
})
const all_alpha = std::all_of(std::begin(str), std::end(str), [](const auto c) {
return std::isalpha(c);
})
So to solve your problem, you can start by creating a helper function that checks if a string is a number by using standard functions like strtool or recycle what we did later:
bool is_number(const std::string& s) {
return !s.empty() && std::find_if(s.begin(),
s.end(), [](char c) { return !std::isdigit(c); }) == s.end();
}
Now that you know if the string is a number or not, you can check if your string contains any weird character, and discard it.
Updates:
void datacheck(string &i) {
const auto intexists = is_number(i);
if (intexists) {
// do whatever
return;
}
const all_alpha = std::all_of(std::begin(str), std::end(str), [](const auto c) {
return std::isalpha(c);
});
if (all_alpha) {
// do whatever
return;
}
}

how to find a substring or string literal

I am trying to write a code that will search userInput for the word "darn" and if it is found, print out "Censored". if it is not found, it will just print out the userInput. It works in some cases, but not others. If the userInput is "That darn cat!", it will print out "Censored". However, if the userInput is "Dang, that was scary!", it also prints out "Censored". I am trying to use find() to search for the string literal "darn " (the space is because it should be able to determine between the word "darn" and words like "darning". I am not worrying about punctuation after "darn"). However, it seems as though find() is not doing what I would like. Is there another way I could search for a string literal? I tried using substr() but I couldn't figure out what the index and the len should be.
#include <iostream>
#include <string>
using namespace std;
int main() {
string userInput;
userInput = "That darn cat.";
if (userInput.find("darn ") > 0){
cout << "Censored" << endl;
}
else {
cout << userInput << endl;
} //userText.substr(0, 7)
return 0;
}
The problem here is your condition. std::string::find returns a object of std::string::size_type which is an unsigned integer type. That means it can never be less than 0 which means
if (userInput.find("darn ") > 0)
will always be true unless userInput starts with "darn ". Because of this if find doesn't find anything then it returns std::string::npos. What you need to do is compare against that like
if (userInput.find("darn ") != std::string::npos)
Do note that userInput.find("darn ") will not work in all cases. If userInput is just "darn" or "Darn" then it won't match. The space needs to be handled as a separate element. For example:
std::string::size_type position = userInput.find("darn");
if (position != std::string::npos) {
// now you can check which character is at userInput[position + 4]
}
std::search and std::string::replace were made for this:
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main() {
string userInput;
userInput = "That darn cat is a varmint.";
static const string bad_words [] = {
"darn",
"varmint"
};
for(auto&& bad : bad_words)
{
const auto size = distance(bad.begin(), bad.end());
auto i = userInput.begin();
while ((i = std::search(i, userInput.end(), bad.begin(), bad.end())) != userInput.end())
{
// modify this part to allow more or fewer leading letters from the offending words
constexpr std::size_t leading_letters = 1;
// never allow the whole word to appear - hide at least the last letter
auto leading = std::min(leading_letters, std::size_t(size - 1));
auto replacement = std::string(i, i + leading) + std::string(size - leading, '*');
userInput.replace(i, i + size, replacement.begin(), replacement.end());
i += size;
}
}
cout << userInput << endl;
return 0;
}
expected output:
That d*** cat is a v******.

C++ code for taking substrings

I have a string as "1.0.0" and I want to extract the "1", "0", and "0". If the last zero is not present, the string must store 0 by default:
verstr.substr(0,verstr.find(".");
The above statement can find the first digit that is "1", however, I am not able to think of a solution for extracting the remainder of the string.
After this i convert it to a long as:
va = atol(verstr.substr(0,verstr.find(".")).c_str());
so i want the "1" in va , 0 in "vb" and so on
Thanks.
C++11 solution:
#include <iostream>
#include <string>
#include <regex>
using namespace std;
int main(int, char **) {
string version("1.2.3");
match_results<string::const_iterator> m;
regex re("([0-9]+)\\.([0-9]+)(\\.([0-9]+))?");
if (regex_match(version, m, re)) {
int major = stoi(m[1].str()),
minor = stoi(m[2].str()),
rev = stoi(m[4].str().length() == 0 ? 0 : m[4].str());
cout << "major: " << major << endl;
cout << "minor: " << minor << endl;
cout << "rev: " << rev << endl;
} else {
cout << "no match\n";
}
}
The regular expression used is ([0-9]+)\.([0-9]+)(\.([0-9]+))? and breaks down as follows:
[0-9]+ matches one or more digits
\. matches a literal dot.
? following the last expression indicates that it is optional
Expressions wrapped in ( and ) are capture groups. There are five capture groups in this expression:
0 - always matches the entire string - we don't use this.
1 - matches the major version number.
2 - matches the minor version number.
3 - matches a dot followed by the revision number - we don't use this but it is necessary because we use the parentheses followed by a ? to make this whole group optional.
4 - matches the revision number.
Not sure if I understand what you need, if you want to retrieve the digits as strings, with a minimum of x digits, you can do something like this.
vector<string> GetVersion(const string &strInput, int iMinSize)
{
vector<string> vRetValue;
std::stringstream ss(strInput);
string strItem;
while(std::getline(ss, strItem, '.'))
vRetValue.push_back(strItem);
while(vRetValue.size() < iMinSize)
vRetValue.push_back("0");
return vRetValue;
}
int _tmain(int argc, _TCHAR* argv[])
{
vector<string> vRetValue = GetVersion("1.0", 3);
return 0;
}
A possibility would to use std::sscanf(). It is simple to use and provides a level of error checking with relatively few lines of code:
#include <iostream>
#include <string>
#include <cstdio>
int main()
{
std::string input[] = { "1.0.7", "1.0.", "1.0", "1.", "1" };
for (size_t i = 0; i < sizeof(input)/sizeof(input[0]); i++)
{
std::cout << input[i] << ": ";
// Init to zero.
int parts[3] = { 0 };
// sscanf() returns number of assignments made.
if (std::sscanf(input[i].c_str(),
"%d.%d.%d",
&parts[0],
&parts[1],
&parts[2]) >= 2)
{
// OK, the string contained at least two digits.
std::cout << parts[0]
<< ","
<< parts[1]
<< ","
<< parts[2]
<< "\n";
}
else
{
std::cout << "bad format\n";
}
}
return 0;
}
Output:
1.0.7: 1,0,7
1.0.: 1,0,0
1.0: 1,0,0
1.: bad format
1: bad format
See online demo: http://ideone.com/0Ox9b .
find and substr are two really nice family of function overloads that are pretty well suited to many simple parsing problems, especially when your syntax checking only needs to be loose.
To extract multiple scalars out of your version vector, store the found index somewhere:
const auto a = verstr.find('.');
const std::string major = verstr.substr(0, a);
Then re-use it with one of the overloads of string::find, saying start searching at one after a:
const auto b = verstr.find ('.', a+1);
const std::string minor = verstr.substr(a+1, b);
And so forth.
If you need a syntax check, compare the returned indices against string::npos:
const auto a = verstr.find('.');
if (std::string::npos == a)
.... bad syntax ....
Pastebin style version of this answer:
#include <string>
#include <stdexcept>
#include <iostream>
struct Version
{
std::string Major, Minor, Patch;
Version(std::string const &Major)
: Major(Major), Minor("0"), Patch("0")
{}
Version(std::string const &Major, std::string const &Minor)
: Major(Major), Minor(Minor), Patch("0")
{}
Version(std::string const &Major, std::string const &Minor, std::string const &Patch)
: Major(Major), Minor(Minor), Patch(Patch)
{}
};
std::ostream& operator<< (std::ostream &os, Version const &v)
{
return os << v.Major << '.' << v.Minor << '.' << v.Patch;
}
Version parse (std::string const &verstr) {
if (verstr.empty()) throw std::invalid_argument("bad syntax");
const auto first_dot = verstr.find('.');
if (first_dot == std::string::npos)
return Version(verstr);
const auto second_dot = verstr.find('.', first_dot+1);
if (second_dot == std::string::npos)
return Version(verstr.substr(0, first_dot),
verstr.substr(first_dot+1, second_dot));
return Version(verstr.substr(0, first_dot),
verstr.substr(first_dot+1, second_dot),
verstr.substr(second_dot+1));
}
and then
int main () {
std::cout << parse("1.0") << '\n'
<< parse("1.0.4+Patches(55,322)") << '\n'
<< parse("1") << '\n';
parse(""); // expected to throw
}
try something like this instead of solution below the line
string s = "1.0.0";
string delimiters = ".";
size_t current;
size_t next = -1;
do
{
current = next + 1;
next = s.find_first_of( delimiters, current );
string current_substring = s.substr( current, next - current ); // here you have the substring
}
while (next != string::npos);
Ok, please don't use this solution below, if you really don't know what you're doing, according to discussion below this answer with #DavidSchwartz
Take a look at function strtok http://www.cplusplus.com/reference/clibrary/cstring/strtok/
char str[] = "1.0.0";
char * pch;
pch = strtok (str,".");
while (pch != NULL)
{
printf ("%s\n",pch);
pch = strtok (NULL, ".");
}
Take a look at Boost libraries, specifically String Algo.
Standard library support for string manipulation is somewhat limited in C++. And reinventing the wheel is just plain bad.
Update:
I was asked in comments why I consider all find/substr based solutions bad style.
I'll try my best.
As questions does not states otherwise, performance is not a question here. Maintainability and readability are much more important. All solutions proposed here tightly tie split algorithm semantics with a specific version parsing algorithm semantics. This hurts both.
This hurts maintainability, because when you will need to change version format, it will involve changing the very same block of code that implements splitting, making it more error-prone. Same applies to unit-tests.
This hurts readability, because due to mixed semantics I can't at once guess an intent behind this block of code. For example, when I am looking up parse algorithm to check how missing 3d version argument is handled, I'd better not waste my time digging through split implementation details.
If parsing pattern would have been slightly more difficult, I'd have advised regular expressions. But in this case splitting string by a delimiter is an action generic and often used enough to justify having it as a separate function.
if it's only simple char comparison in a small string...
char[] should not be so bad... and c functions should work... (EDIT: for some, its a blasphemy... a lot of C++ method use char* whether it's const or not).
why use an object if it's to has the same functionality with more memory to be used, and more time for the process to spend?
EDIT:
I saw that some answer suppose to create a lot of string object... i don't khnow if it's really the best way...
a little 2 line recursive C like function can do that without gasping a lot.
In c++ code I probably would do that with string object, as it's negligible gasp... but just to say it so.
In string object i would use the length property to get the last char first (with [] operator, or appropriate method).
then just need to get the two elements (in a loop, or with 2 back reference in an object accepting regex (which is less efficient))