Replace a specific character only between parenthesis - r

Lest's say I have a string:
test <- "(pop+corn)-bread+salt"
I want to replace the plus sign that is only between parenthesis by '|', so I get:
"(pop|corn)-bread+salt"
I tried:
gsub("([+])","\\|",test)
But it replaces all the plus signs of the string (obviously)

If you want to replace all + symbols that are inside parentheses (if there may be 1 or more), you can use any of the following solutions:
gsub("\\+(?=[^()]*\\))", "|", x, perl=TRUE)
See the regex demo. Here, the + is only matched when it is followed with any 0+ chars other than ( and ) (with [^()]*) and then a ). It is only good if the input is well-formed and there is no nested parentheses as it does not check if there was a starting (.
gsub("(?:\\G(?!^)|\\()[^()]*?\\K\\+", "|", x, perl=TRUE)
This is a safer solution since it starts matching + only if there was a starting (. See the regex demo. In this pattern, (?:\G(?!^)|\() matches the end of the previous match (\G(?!^)) or (|) a (, then [^()]*? matches any 0+ chars other than ( and ) chars, and then \K discards all the matched text and \+ matches a + that will be consumed and replaced. It still does not handle nested parentheses.
Also, see an online R demo for the above two solutions.
library(gsubfn)
s <- "(pop(+corn)+unicorn)-bread+salt+malt"
gsubfn("\\((?:[^()]++|(?R))*\\)", ~ gsub("+", "|", m, fixed=TRUE), s, perl=TRUE, backref=0)
## => [1] "(pop(|corn)|unicorn)-bread+salt+malt"
This solves the problem of matching nested parentheses, but requires the gsubfn package. See another regex demo. See this regex description here.
Note that in case you do not have to match nested parentheses, you may use "\\([^()]*\\)" regex with the gsubfn code above. \([^()]*\) regex matches (, then any zero or more chars other than ( and ) (replace with [^)]* to match )) and then a ).

We can try
sub("(\\([^+]+)\\+","\\1|", test)
#[1] "(pop|corn)-bread+salt"

Related

Positive Lookbehind and Lookahead to the end of string

My string patterns looks like this:
UNB+UNOC:3+4399945681577+_GLN_Company__+180101:0050+10870 and I am trying to extract everything after the second last +, i.e. 180101:0050+10870.
Thus far, I managed to address the second last block 180101:0050 with this expression (?<=\+)[^\+]+(?=\+[^\+]*$) but fail to include the last block including the last +. Here is my sample: regex101
The expression is meant for R and I still need to escape the characters later on. This format it just for testing purposes in Regex101.
We could capture group based on the occurrence of + from the end ($) of the string.
sub(".*\\+([^+]+\\+[^+]+$)", "\\1", str1)
#[1] "180101:0050+10870"
data
str1 <- "UNB+UNOC:3+4399945681577+_GLN_Company__+180101:0050+10870"
You may use
\+\K[^+]+\+[^+]*$
Or, if you would like to use it with stringr::str_extract:
(?<=\+)[^+]+\+[^+]*$
See the regex demo. Details:
\+ - a + char
\K - match reset operator
(?<=\+) - location right after a + symbol
[^+]+ - one or more chars other than +
\+ - a +
[^+]+ - one or more chars other than +
$ - end of string.
See R demo online:
x <- "UNB+UNOC:3+4399945681577+_GLN_Company__+180101:0050+10870"
regmatches(x, regexpr("\\+\\K[^+]+\\+[^+]*$", x, perl=TRUE))
## => [1] "180101:0050+10870"
library(stringr)
str_extract(x, "(?<=\\+)[^+]+\\+[^+]*$")
## => [1] "180101:0050+10870"
Another way you can do in this case:
library(stringr)
str_extract("UNB+UNOC:3+4399945681577+_GLN_Company__+180101:0050+10870", "\\d+:\\d+\\+\\d+")
#"180101:0050+10870"

Extracting matches from strings with lookaround in R

I have textual data (storytellings) and my aim is to extract certain words that are defined by a co-occurrence pattern, namely that they occur immediately prior to overlap, which is indicated by square brackets. The data are like this:
who <- c("Sue:", NA, "Carl:", "Sue:", NA, NA, NA, "Carl:", "Sue:","Carl:", "Sue:","Carl:")
story <- c("That’s like your grand:ma. did that with::=erm ",
"with Ju:ne (.) once or [ twice.] ",
" [ Yeah. ] ",
"And June wanted to go out and yo- your granny said (0.8)",
"“make sure you're ba(hh)ck before midni(hh)ght.” ",
"[Mm.] ",
"[There] she was (.) a ma(h)rried woman with a(h)- ",
"She’s a right wally. ",
"mm [kids as well ] ",
" [They assume] an awful lot man¿ ",
"°°ye:ah,°° ",
"°°the elderly do.°° ")
CAt <- data.frame(who, story)
Now, defining the pattern:
pattern <- "\\w.*\\s\\[[^]].*]"
and using grep():
grep(pattern, CAt$story, value = T)
[1] "with Ju:ne (.) once or [ twice.] "
[2] "mm [kids as well ] "
I get the two strings that contain the target matches but what I'm really after are the target words only, in this case the words "or" and "mm". This, to me, seems to call for positive lookahead. So I redefined the pattern thus:
pattern <- "\\w.*(?=\\s\\[[^]].*])"
which says something along the lines: "match the word iff you see a space followed by square brackets with some content on the right of that word". Now to extract only the exact matches, I normally use this code, which works fine as long as no lookaround is involved, but here it throws an error:
unlist(regmatches(CAt$story, gregexpr(pattern, CAt$story)))
Error in gregexpr(pattern, CAt$story) :
invalid regular expression, reason 'Invalid regexp'
Why is this? And how can the exact matches be extracted?
In your code, you could add perl=TRUE to gregexpr.
In your pattern \w.* will match a single word char followed by matching any char 0+ times.
This part \[[^]].*] will match [, then 1 char which is not ] and then .* which will match any char 0+ times followed by ].
You could update your pattern to repeating the word char and the character class itself instead.
\w+(?=\s\[[^]]*])
Explanation
\w+ Match 1+ word chars
(?= Positive lookahead, assert what is directly to the right is
\s Match single whitespace char
\[[^]]*] Match from opening[ to closing ] using a negated character class
) Close positive lookahead
Regex demo
Using doubled backslashes:
\\w+(?=\\s\\[[^]]*])
As an alternative you could use a capturing group instead of using a lookahead
(\w+)\s\[[^]]*]
Regex demo

matching start of a string but not end in R

How can I match all words starting with plan_ and not ending with template without using invert = TRUE? In the below example, I'd like to match only the second string. I tried with negative lookahead but it does not work, maybe because of greediness?
names <- c("plan_x_template", "plan_x")
grep("^plan.*(?!template)$",
names,
value = TRUE, perl = TRUE
)
#> [1] "plan_x_template" "plan_x"
I mean one can also solve the problem with two regex calls but I'd like to see how it works the other way :-)
is_plan <- grepl("^plan_", names)
is_template <- grepl("_template$", names)
names[is_plan & !is_template]
#> [1] "plan_x"
You may use
names <- c("plan_x_template", "plan_x")
grep("^plan(?!.*template)",
names,
value = TRUE, perl = TRUE
)
See the R online demo
The ^plan(?!.*template) pattern matches:
^ - a start of string
plan - a plan substring
(?!.*template) - a negative lookahead that fails the match if, immediately to the left of the current location, there are 0+ chars other than line break chars (since perl = TRUE is used and the pattern is processed with a PCRE engine, the . does not match all possible chars as opposed to the default grep TRE regex engine), as many as possible, followed with template substring.
NOTE: In case of multiline strings, you need to use a DOTALL modifier in the regex, "(?s)^plan(?!.*template)".

Extract digits after matching the certain string second time

I want to extract the digits after second occurance of under score _ from a pattern.
by following the similar posts here
Matching different digits after a lookahead
regex - return all before the second occurrence
I tried
library(stringr)
pattern <- c("1/2/3_500k/855kk_1400k/AVBB")
str_extract(pattern, "(^_){2}(\\d+\\.*\\d*)")
which outputs
[1] NA
instead of 1400. Could you help?
You may use a base R solution with regexpr/regmatches:
regmatches(x, regexpr("^(?:[^_]*_){2}[^_0-9]*\\K\\d*\\.?\\d+", x, perl=TRUE))
Or, with sub:
sub("^(?:[^_]*_){2}[^_0-9]*(\\d*\\.?\\d+).*", "\\1", x)
See the R demo online.
The regex is
^(?:[^_]*_){2}[^_0-9]*\K\d*\.?\d+
See the online regex demo.
Details
^ - start of string
(?:[^_]*_){2} - 2 repetitions of
[^_]* - any 0+ chars other than _
_ - an underscore
[^_0-9]* - any 0+ chars other than _ and digits
\K - match reset operator discarding all text matched so far
\d*\.?\d+ - a float or integer number pattern (0+ digits, an optional . and then 1+ digits).
In the sub regex variation, the \K is not necessary, the number pattern is captured into a capturing group and the rest of string is matched with .* pattern. The result is the contents of Group 1, referred to with the \1 placeholder.
One option could be as:
pattern <- c("1/2/3_500k/855kk_1400k/AVBB")
sub(".*_*_(\\d+).*","\\1", pattern, perl = TRUE)
[1] "1400"
The regex is:
".*_*_(\\d+).*"
Details:
.*_ anything before first _
.*_ anything after first _ and before 2nd _
\\d+ look for digits and take those as selection.
.* anything afterwards.
\\1 replaces matching strings with values found for 1st group.

Regex - Substitute character in a matching substring

Let's say I have the following string:
input = "askl jmsp wiqp;THIS IS A MATCH; dlkasl das, fm"
I need to replace the white-spaces with underscores, but only in the substrings that match a pattern. (In this case the pattern would be a semi-colon before and after.)
The expected output should be:
output = "askl jmsp wiqp;THIS_IS_A_MATCH; dlkasl das, fm"
Any ideas how to achieve that, preferably using regular expressions, and without splitting the string?
I tried:
gsub("(.*);(.*);(.*)", "\\2", input) # Pattern matching and
gsub(" ", "_", input) # Naive gsub
Couldn't put them both together though.
Regarding the original question:
Substitute character in a matching substring
You may do it easily with gsubfn:
> library(gsubfn)
> input = "askl jmsp wiqp;THIS IS A MATCH; dlkasl das, fm"
> gsubfn(";([^;]+);", function(g1) paste0(";",gsub(" ", "-", g1, fixed=TRUE),";"), input)
[1] "askl jmsp wiqp;THIS-IS-A-MATCH; dlkasl das, fm"
The ;([^;]+); matches any string starting with ; and up to the next ; capturing the text in-between and then replacing the whitespaces with hyphens only inside the captured part.
Another approach is to use a PCRE regex with a \G based regex with gsub:
p = "(?:\\G(?!\\A)|;)(?=[^;]*;)[^;\\s]*\\K\\s"
> gsub(p, "-", input, perl=TRUE)
[1] "askl jmsp wiqp;THIS-IS-A-MATCH; dlkasl das, fm"
See the online regex demo
Pattern details:
(?:\\G(?!\\A)|;) - a custom boundary: either the end of the previous successful match (\\G(?!\\A)) or (|) a semicolon
(?=[^;]*;) - a lookahead check: there must be a ; after 0+ chars other than ;
[^;\\s]* - 0+ chars other than ; and whitespaces
\\K - omitting the text matched so far
\\s - 1 single whitespace character (if multiple whitespaces are to be replaced with 1 hyphen, add + after it).

Resources