3

I'm looking for a regex to find numbers in a string; if I have a string like:

li 12.12 si 43,23 45 31 uf 889 uf31 3.12345

I want to find only the numbers:

12.12 45 31 889 3.12345

I tried with the following pattern:

((\\+|-)?[[:digit:]]+)(\\.(([[:digit:]]+)?))?

but the output included uf31 and 43,23.

I tried with:

(?!([a-z]*((\\+|-)?[[:digit:]]+)(\\.(([[:digit:]]+)?))?[a-z]*))?((\\+|-)?[[:digit:]]+)(\\.(([[:digit:]]+)?))?

but this gave the same result.

What is the solution?

SOLUTION leave to posterity the solution:

13
  • 1
    Can you define what you want to use for delimiters? I understand that you do not want to include anything but whitespace delimited numbers. Is that correct? Commented Nov 4, 2015 at 11:56
  • 2
    Do you want to support .5? Commented Nov 4, 2015 at 12:05
  • How about scientific notation? You'll need to be specific about what you want to be able to capture. Your example string contains no + or - and you don't mention them in text, but your regexp attempts do. Commented Nov 4, 2015 at 12:14
  • @stribizhev I wouldn't mind offering an upvote if the OP made it clear what he was looking for. Commented Nov 4, 2015 at 12:29
  • 1
    @user3641602: Sry, I don't understand your answer. print '5'?? Commented Nov 4, 2015 at 15:19

7 Answers 7

5

Actually, C++ regex module supports look-aheads.

Here is my suggestion:

#include <iostream>
#include <regex>
using namespace std;

int main() {
    std::string buffer = " li 12.12 si 43,23 45 31 uf 889 uf31 3.12345";
    std::regex rx(R"((?:^|\s)([+-]?[[:digit:]]+(?:\.[[:digit:]]+)?)(?=$|\s))"); // Declare the regex with a raw string literal
    std::smatch m;
    std::string str = buffer;
    while (regex_search(str, m, rx)) {
        std::cout << "Number found: " << m[1] << std::endl; // Get Captured Group 1 text
        str = m.suffix().str(); // Proceed to the next match
    }  
    return 0;
}

See IDEONE demo

Due to the raw string literal declaration, there is no need using double backslashes with \s.

The lookahead (?=$|\s) checks the presence, but does not consume the whitespace and consecutive numbers can be extracted.

Note that if you need to extract decimal values like .5, you need

R"((?:^|\s)([+-]?[[:digit:]]*\.?[[:digit:]]+)(?=$|\s))"
Sign up to request clarification or add additional context in comments.

8 Comments

I stand corrected, I was using Visual Studio 2013 last I tested look arounds. It appears C++ now fully supports ECMAScript! However I'd still make the case that look arounds are the most expensive regex operation. They should be avoided unless absolutely necessary, which they aren't here.
In this case, following this logic, the look-ahead is a must. You can't match the numbers in <SPACE>41<SPACE>31<SPACE> without a look-ahead.
@JonathanMee: Please have a look at your results - your regex does not match the expected 31.
@user3641602: Glad it works for you, please consider accepting the answer.
It is a raw string literal. The notation is R"()". Inside the parentheses, \ symbol means a literal \ symbol, not a C-escaping symbol.
|
1

You need this regex:

(?<!,)\b([\d\.]+)\b(?!,)

14 Comments

thank! but with your regex i print token . token , token .
@user3641602 This will match 1.2.3... Do you want to enforce correct numbering on your number?
Then you need to just modification in current regex: \b([\d\.]+)\b
This has the bugs of your original regex: it captures 1.2.3... but now it's also picked up the need for Boost in the regex by @KarolyHorvath
@user2079303 Correction, I meant to type that this will pull from any symbol other than a comma: "12#3" for example will capture 12 and 3.
|
1

As is stated by stribizhev this can only be accomplished via look arrounds. Since a single whitespace separating numbers would otherwise be needed to be consumed in the search for the number before and after the whitespace.

user2079303 poses a viable option to regexes which could be simplified to the point where it rivaled the simplicity of a regexes:

for_each(istream_iterator<string>(istringstream(" li 12.12 si 43,23 45 31 uf 889 uf31 3.12345")),
         istream_iterator<string>(),
         [](const string& i) {
            char* it;
            double num = strtod(i.c_str(), &it);
            if (distance(i.c_str(), const_cast<const char*>(it)) == i.size()) cout << num << endl; });

However it is possible to accomplish this without the weight of an istringstream or a regex, by simply using strtok:

char buffer[] = " li 12.12 si 43,23 45 31 uf 889 uf31 3.12345";

for (auto i = strtok(buffer, " \f\n\r\t\v"); i != nullptr; i = strtok(nullptr, " \f\n\r\t\v")) {
    char* it;
    double num = strtod(i, &it);

    if (*it == '\0') cout << num << endl;
}

Note that for my delimiter argument I'm simply using the default isspace values.

4 Comments

No need escaping if raw string literal is used. 31 is not matched, BTW.
@KarolyHorvath Wrong, notice those are non-capturing parenthesis.
+1 Thanks for the simplified use of second parameter of strtod. Took me a while to understand the documentation.
@user2079303 It seems we're the only ones on board with the strtod :( Ah well, if you're looking for a better explanation of how to use it you might want to check out: stackoverflow.com/q/32991193/2642059
1

Regexes are usually unreadable and hard to prove correct. Regexes matching only valid rational numbers need to be intricate and are easy to mess up. Therefore, I propose an alternative approach. Instead of regexes, tokenize your string with c++ and use std::strtod to test if input is a valid number. Here is example code:

std::vector<std::string> split(const std::string& str) {
    std::istringstream iss(str);
    return {
        std::istream_iterator<std::string>{iss},
        std::istream_iterator<std::string>{}
    };
}

bool isValidNumber(const std::string& str) {
    char* end;
    std::strtod(str.data(), &end);
    return *end == '\0';
}

// ...
auto tokens = split(" li 12.12 si 43,23 45 31 uf 889 uf31 3.12345");
std::vector<std::string> matches;
std::copy_if(tokens.begin(), tokens.end(), std::back_inserter(matches), isValidNumber);

3 Comments

You beat me to the use of strtod +1
yes, it's a possible way. But i have the solution of the problem. I would like to reduce my code via regex, because if you use regex then you have a powerful tool by hands!! :) But, such as you are mentioned before, "Regexes are usually unreadable and hard to prove correct." :)
@user3641602 His solution is I believe a simpler one than the regex solution in the first place. I've streamlined his code in one of the options I provide in my answer: stackoverflow.com/a/33521413/2642059
0

Use negative lookahead and lookbehind to assert that there are no funny characters on either side of the number:

(?<![^\\s])(\\+|-)?[0-9]+(\\.[0-9]*)?(?![^\\s])

Unfortunately you're going to need Boost.Regex for the task as the builtin one doesn't support these constructs.

You're probably better off splitting the input into words and then using a simple regex on each word.

8 Comments

C++ doesn't support look aheads or look behinds
ATM I don't really see another way of doing it.
Just a note: [^\\s] is looking for characters that are not '\\' or 's'. What you actually meant was \S
@JonathanMee cplusplus.com/reference/regex/ECMAScript c++ support lookahead
I refuse and have always refused to use Boost. I prefer to use standard, for compatibility in team.
|
0

You could play with a trick to consume stuff you don't want. Something like this.

(?:\d+,|[a-z]+)\d+|(\d+[.\d]*)

Modfiy to everything that should be excluded in pipes pre capture and grab captures of first group.

See demo at regex101. No idea if (: non capture group is ok for c++. Remove, if not.

2 Comments

Impressive way to think about it, but this will capture: "123abc" and "12#3" do you have a way to work around that?
@JonathanMee This approach only makes sense, if cases that could occur are known. For your samples have to add those cases like this.
0

Two attempts:

#include <string>
#include <iostream>
#include <regex>
#include <sstream>


int main()
{
    using namespace std;

    string buffer(" li 12.12 si 43,23 45 31 uf 889 uf31 3.12345 .5");

    regex num_regex("(^|\\s)([\\+-]?([0-9]+\\.?[0-9]*|\\.?[0-9]+))(\\s|$)");
    smatch num_match;
    while (regex_search(buffer, num_match, num_regex))
    {
        if (num_match.size() >= 4) //3 groups = 4 matches
        {
            //We only need the second group
            auto token = num_match[2].str();
            cout << token << endl;
        }

        buffer = num_match.suffix().str();
    }
    return 0;
}

#include <string>
#include <iostream>
#include <regex>
#include <sstream>


int main()
{
    using namespace std;

    string buffer(" li 12.12 si 43,23 45 31 uf 889 uf31 3.12345 .5");

    istringstream iss(buffer);
    vector<string> tokens{ istream_iterator<string>{iss}, istream_iterator<string>{} };

    regex num_regex("^[\\+-]?([0-9]+\\.?[0-9]*|\\.?[0-9]+)$");
    for(auto token : tokens)
    {
        if (regex_search(token, num_regex))
        {
            //Valid entry
            cout << token << endl;
        }
    }

    return 0;
}

2 Comments

first is correct, but ignore 31 and .5 second ignore always
31 is not ignored - I just tested both variants. You're right about .5 - I'll update my answer

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.