tovare
tovare

Reputation: 4087

Extract a regular expression match

I'm trying to extract a number from a string.

And do something like [0-9]+ on the string "aaa12xxx" and get "12".

I thought it would be something like:

> grep("[0-9]+", "aaa12xxx", value=TRUE)
[1] "aaa12xxx"

And then I figured...

> sub("[0-9]+", "\\1", "aaa12xxx")
[1] "aaaxxx"

But I got some form of response doing:

> sub("[0-9]+", "ARGH!", "aaa12xxx")
[1] "aaaARGH!xxx"

There's a small detail I'm missing.

Upvotes: 136

Views: 136657

Answers (13)

jan-glx
jan-glx

Reputation: 9486

While you said, you want to extract "12" from "aaa12xxx", it seems that you actually want 12. In such cases, strcapture from the preinstalled utils package is a very safe & powerful solution:

strcapture(pattern = "[^\\d]*(\\d+)[^\\d]*", x = "aaa12xxx", proto = list(my_val = integer()), perl = TRUE)
#>   my_val
#> 1     12

Created on 2023-07-12 by the reprex package (v2.0.1)

Upvotes: 0

Marek
Marek

Reputation: 50704

For your specific case you could remove all not numbers:

gsub("[^0-9]", "", "aaa12xxxx")
# [1] "12"

It won't work in more complex cases

gsub("[^0-9]", "", "aaa12xxxx34")
# [1] "1234"

Upvotes: 31

Mike V
Mike V

Reputation: 1354

A solution for this question

library(stringr)
str_extract_all("aaa12xxx", regex("[[:digit:]]{1,}"))
# [[1]]
# [1] "12"

[[:digit:]]: digit [0-9]

{1,}: Matches at least 1 times

Upvotes: 3

moodymudskipper
moodymudskipper

Reputation: 47300

Using the package unglue we would do the following:

# install.packages("unglue")
library(unglue)
unglue_vec(c("aaa12xxx", "aaaARGH!xxx"), "{prefix}{number=\\d+}{suffix}", var = "number")
#> [1] "12" NA

Created on 2019-11-06 by the reprex package (v0.3.0)

Use the convert argument to convert to a number automatically :

unglue_vec(
  c("aaa12xxx", "aaaARGH!xxx"), 
  "{prefix}{number=\\d+}{suffix}", 
  var = "number", 
  convert = TRUE)
#> [1] 12 NA

Upvotes: 1

user2074102
user2074102

Reputation:

You could write your regex functions with C++, compile them into a DLL and call them from R.

    #include <regex>

    extern "C" {
    __declspec(dllexport)
    void regex_match( const char **first, char **regexStr, int *_bool)
    {
        std::cmatch _cmatch;
        const char *last = *first + strlen(*first);
        std::regex rx(*regexStr);
        bool found = false;
        found = std::regex_match(*first,last,_cmatch, rx);
        *_bool = found;
    }

__declspec(dllexport)
void regex_search_results( const char **str, const char **regexStr, int *N, char **out )
{
    std::string s(*str);
    std::regex rgx(*regexStr);
    std::smatch m;

    int i=0;
    while(std::regex_search(s,m,rgx) && i < *N) {
        strcpy(out[i],m[0].str().c_str());
        i++;
        s = m.suffix().str();
    }
}
    };

call in R as

dyn.load("C:\\YourPath\\RegTest.dll")
regex_match <- function(str,regstr) {
.C("regex_match",x=as.character(str),y=as.character(regstr),z=as.logical(1))$z }

regex_match("abc","a(b)c")

regex_search_results <- function(x,y,n) {
.C("regex_search_results",x=as.character(x),y=as.character(y),i=as.integer(n),z=character(n))$z }

regex_search_results("aaa12aa34xxx", "[0-9]+", 5)

Upvotes: -3

andyyy
andyyy

Reputation: 1015

One important difference between these approaches the the behaviour with any non-matches. For example, the regmatches method may not return a string of the same length as the input if there is not a match in all positions

> txt <- c("aaa12xxx","xyz")

> regmatches(txt,regexpr("[0-9]+",txt)) # could cause problems

[1] "12"

> gsub("[^0-9]", "", txt)

[1] "12" ""  

> str_extract(txt, "[0-9]+")

[1] "12" NA  

Upvotes: 4

pari
pari

Reputation: 808

Another solution:

temp = regexpr('\\d', "aaa12xxx");
substr("aaa12xxx", temp[1], temp[1]+attr(temp,"match.length")[1])

Upvotes: 2

thelatemail
thelatemail

Reputation: 93813

It is probably a bit hasty to say 'ignore the standard functions' - the help file for ?gsub even specifically references in 'See also':

‘regmatches’ for extracting matched substrings based on the results of ‘regexpr’, ‘gregexpr’ and ‘regexec’.

So this will work, and is fairly simple:

txt <- "aaa12xxx"
regmatches(txt,regexpr("[0-9]+",txt))
#[1] "12"

Upvotes: 129

Ragy Isaac
Ragy Isaac

Reputation: 1458

Use capturing parentheses in the regular expression and group references in the replacement. Anything in parentheses gets remembered. Then they're accessed by \2, the first item. The first backslash escapes the backslash's interpretation in R so that it gets passed to the regular expression parser.

gsub('([[:alpha:]]+)([0-9]+)([[:alpha:]]+)', '\\2', "aaa12xxx")

Upvotes: 6

G. Grothendieck
G. Grothendieck

Reputation: 146

Using strapply in the gsubfn package. strapply is like apply in that the args are object, modifier and function except that the object is a vector of strings (rather than an array) and the modifier is a regular expression (rather than a margin):

library(gsubfn)
x <- c("xy13", "ab 12 cd 34 xy")
strapply(x, "\\d+", as.numeric)
# list(13, c(12, 34))

This says to match one or more digits (\d+) in each component of x passing each match through as.numeric. It returns a list whose components are vectors of matches of respective components of x. Looking the at output we see that the first component of x has one match which is 13 and the second component of x has two matches which are 12 and 34. See http://gsubfn.googlecode.com for more info.

Upvotes: 2

hadley
hadley

Reputation: 103898

Use the new stringr package which wraps all the existing regular expression operates in a consistent syntax and adds a few that are missing:

library(stringr)
str_locate("aaa12xxx", "[0-9]+")
#      start end
# [1,]     4   5
str_extract("aaa12xxx", "[0-9]+")
# [1] "12"

Upvotes: 196

Jyotirmoy Bhattacharya
Jyotirmoy Bhattacharya

Reputation: 9587

You can use PERL regexs' lazy matching:

> sub(".*?([0-9]+).*", "\\1", "aaa12xx99",perl=TRUE)
[1] "12"

Trying to substitute out non-digits will lead to an error in this case.

Upvotes: 17

Robert
Robert

Reputation: 848

One way would be this:

test <- regexpr("[0-9]+","aaa12456xxx")

Now, notice regexpr gives you the starting and ending indices of the string:

    > test
[1] 4
attr(,"match.length")
[1] 5

So you can use that info with substr function

substr("aaa12456xxx",test,test+attr(test,"match.length")-1)

I'm sure there is a more elegant way to do this, but this was the fastest way I could find. Alternatively, you can use sub/gsub to strip out what you don't want to leave what you do want.

Upvotes: 5

Related Questions