Reputation: 7830
I have a string with parameters inside braces, I set these parameters as objects and I want to evaluate them and replace them by their value in my string.
Here is what I did but I don't like the way I did it and maybe I can evaluate my parameters faster.
(I used ls() but I should create a data.frame for my values then I can use them easily).
region <- "france"
name <- "julien"
str <- "/test/{region}/v1.1/{name}/{test}"
df <- data.frame(object = gsub("[\\{\\}]", "", regmatches(str, gregexpr("\\{.*?\\}", str))[[1]]), string = unlist(regmatches(str, gregexpr("\\{.*?\\}", str))), stringsAsFactors = FALSE)
> df
object string
1 region {region}
2 name {name}
3 test {test}
for(i in 1:nrow(df)){
if (df$object[i] %in% ls()){
df$value[i] <- eval(as.name(df$object[i]))
} else {
df$value[i] <- ""
}
str <- gsub(df$string[i], df$value[i], str, fixed = TRUE)
}
> df
object string value
1 region {region} france
2 name {name} julien
3 test {test}
>
> str
[1] "/test/france/v1.1/julien/"
If someone have an idea to improve the code and make it more efficient and cleaner (or evaluate directly my parameters in the string), thank you for any help.
Upvotes: 0
Views: 129
Reputation: 4414
You could use gsubfn:
library(gsubfn)
region <- "france"
name <- "julien"
test <- 'toto'
str <- "/test/{region}/v1.1/{name}/{test}"
gsubfn('\\{(\\w+)\\}', get, str)
[1] "/test/france/v1.1/julien/toto"
If you want to pick your variables from a data frame:
df <- data.frame(region = 'France', name = 'Julien', test = 'Success',
stringsAsFactors = FALSE)
gsubfn('\\{(\\w+)\\}', function(x) get(x, df), str)
or
gsubfn('\\{(\\w+)\\}', x ~ get(x, df), str)
or even just:
gsubfn('\\{(\\w+)\\}', df, str)
It also works with a list:
L <- list(region = 'France', name = 'Julien', test = 'Success')
gsubfn('\\{(\\w+)\\}', L, str)
Upvotes: 1
Reputation: 121077
A good rule of thumb when dealing with strings is to never, ever use the builtin regular expression functions, if you can help it. Instead, use the stringr
package since it makes your code cleaner.
In this case, you can simplify the gregexpr
/regmatches
mess with a call to str_match_all
.
The parentheses, (
, show the region to be captured: "at least one alphabetic character", via [[:alpha:]]+
. This is returned in the second column.
The first column contains the full match, which also includes the curly braces, {
.
library(stringr)
matches <- str_match_all(str, "\\{([[:alpha:]]+)\\}")[[1]]
colnames(matches) <- c("string", "object")
matches
## string object
## [1,] "{region}" "region"
## [2,] "{name}" "name"
## [3,] "{test}" "test"
Then proceed as per Roland's answer, using a lookup data frame.
lookup <- data.frame(
object = c("region", "name"),
value = c("france", "julien")
)
(df <- merge(matches, lookup, all.x = TRUE))
## object string value
## 1 name {name} julien
## 2 region {region} france
## 3 test {test} <NA>
Update regarding replacing values:
Since the values need to be updated sequentially rather than all at once, a for
loop is as good as anything. There are a couple of minor improvements that you can make. 1:nrow(df)
is a bad idea if it is possible for df
to have zero rows, since 1:0
is not what you want. str_replace_all
is a little easier on the eye than gsub
.
First, a couple of changes to the data frame. The string column should be a charcter vector rather than a factor, and you want empty strings instead of missing values.
df <- within(
df,
{
string <- as.character(df$string)
value <- ifelse(is.na(value), "", value)
}
)
The updated loop looks like:
str <- "/test/{region}/v1.1/{name}/{test}"
for(i in seq_len(nrow(df)))
{
str <- with(df, str_replace_all(str, fixed(string[i]), value[i]))
}
str
## [1] "/test/france/v1.1/julien/"
Upvotes: 1
Reputation: 81693
An easier way to get the values can be achieved by get
and exists
:
df$value <- sapply(df$object, function(x) if (exists(x)) get(x) else "")
# object string value
# 1 region {region} france
# 2 name {name} julien
# 3 test {test}
An alternative way (without the data frame):
str <- "/test/{region}/v1.1/{name}/{test}"
matches <- regmatches(str,
gregexpr("(?<=\\{)\\w+(?=\\})", str, perl = TRUE))[[1]]
values <- sapply(matches, function(x) if (exists(x)) get(x) else "")
for (i in seq_along(matches)) {
str <- sub(paste0("\\{", matches[i], "\\}"), values[i], str)
}
str
# [1] "/test/france/v1.1/julien/"
Upvotes: 1
Reputation: 132706
You could use get
or mget
, since eval
is evil. However, a better strategy than having all those objects flying around in your global environment would be creating a look-up table:
df1 <- data.frame(object=c("region", "name"),
value=c("frace", "julien"))
Then you could use merge
:
merge(df, df1, all=TRUE)
Upvotes: 2