moodymudskipper
moodymudskipper

Reputation: 47340

extract function instances and parameters from a string

I would like a function that will find function instances in a string, extract the raw parameters,and replace them with placeholders. Unfortunately my regex skills don't lead me far...

I would like the folowing behavior:

extract_fun("max(7*xy,b=z)+maximum+max(j)",fun="max")
# $modified_string
# [1] "{F[[1]]}+maximum+{F[[2]]}"
# 
# $params
# $params[[1]]
# [1] "7*xy" "b=z" 
# 
# $params[[2]]
# [1] "j"

Edit:

A more complicated use case:

extract_fun("max(7*xy,b=min(1,3))+maximum+max(j)",fun="max")
    # $modified_string
    # [1] "{F[[1]]}+maximum+{F[[2]]}"
    # 
    # $params
    # $params[[1]]
    # [1] "7*xy" "b=min(1,3)" 
    # 
    # $params[[2]]
    # [1] "j"

Upvotes: 0

Views: 98

Answers (1)

missuse
missuse

Reputation: 19736

Here is something to get you started:

Your function should have two arguments:

 fun = "max"
 string = "max(7*xy,b=z)+maximum+max(j)"

the regex captures anything in (, ), preceded by fun and it is lazy ?

regex = paste0(fun, "\\((.*?)\\)")
regex
#output
"max\\((.*?)\\)"

matcher = stringr::str_match_all(string, regex)
matcher = do.call(rbind, matcher)
matcher
#output
     [,1]         [,2]      
[1,] "(7*xy,b=z)" "7*xy,b=z"
[2,] "(j)"        "j"       

#extract arguments from captured groups in matcher[,2]
params = strsplit(matcher[,2], " {0,}, {0,}" ) #, with possible white spaces before and after
#output
[[1]]
[1] "7*xy" "b=z" 

[[2]]
[1] "j"

#generate a modified_string
Fs = 1:nrow(matcher)
replacer = paste0("{F[[", Fs, "]]}")
regex2 = paste(matcher[,1])
out =  string
for (i in 1:length(replacer)){
  out= gsub(regex2[i], replacer[i], out , fixed = TRUE)
}
out
#output
"{F[[1]]}+maximum+{F[[2]]}"

EDIT: here is what I have so far on the updated question:

My idea is to isolate the part of the string with the function of interest and than manipulate this part only.

string = "max(7*xy,b=min(1,3))+maximum+max(j)"

Split string just behind max(

fun = "max"
regex_for_split = paste0("(?<=.)(?=", fun, "\\()")
fun_char = nchar(fun)    

spliter_begin = unlist(strsplit(string, regex_for_split, perl = TRUE))

Locate opening and ending parentheses

opening = stringr::str_locate_all(spliter_begin, "\\(")
ending = stringr::str_locate_all(spliter_begin, "\\)")

clean it up a bit

opening = lapply(opening, function(x){
  return((x[,1]))
})
ending = lapply(ending, function(x){
  return((x[,1]))
})

find at what position is the amount of ending parentheses equal to the amount of opened parentheses. We are interested in the first match.

out = list()
for (i in 1: length(ending)){
  end = ending[[i]]
  open = opening[[i]]
  sumer = vector()
  for(z in end){
    sumi=  sum(open < z) == sum(end<=z)
    sumer = c(sumer, sumi)
  }
  out[[i]] = sumer
  }

spliter_end = purrr::map2(ending, out, function(x, y){
  return(x[y])
})

isolate the sub string

fun_isolate = purrr::map2(as.list(spliter_begin), spliter_end, function(x,y){
  substr(x, start = fun_char+2, stop = y[1]-1)
})

fun_isolate
#output
[[1]]
[1] "7*xy,b=min(1,3)"

[[2]]
[1] "j"

lets try with a meaner example

string2 = "max(7*xy,b=min(1,3),z=sum(x*y)),mean(x+y)+maximum+max(j)"
  #copy above code with `string2` instead of `string`
fun_isolate
[[1]]
[1] "7*xy,b=min(1,3),z=sum(x*y)"

[[2]]
[1] "j"

Or even tougher:

string3 = "max(7*xy,b=min(1,3, head(z)),z=sum(x*y+mean(x+y))),mean(x+y)+maximum+max(j)"

#output
[[1]]
[1] "7*xy,b=min(1,3, head(z)),z=sum(x*y+mean(x+y))"

[[2]]
[1] "j"

Now its just a matter of splitting at , not surrounded by ( ).

#locate strings in parenthesis
locate_exclude = stringr::str_locate_all(unlist(fun_isolate), "\\(.*?\\)")
#locate all comas
locate_comma = stringr::str_locate_all(unlist(fun_isolate), ",")

#leave the difference
splt_locate  = purrr::map2(locate_exclude, locate_comma, function(x, y){
  if(length(x)==0) x = matrix(data=c(0,0), nrow=1)
  offbounds = vector()
  for (i in 1 : nrow(x)){
    z = x[i,1]:x[i,2]
    offbounds = c(offbounds, z)
  }
   comas = y[,1]
  return(comas[!comas%in%offbounds])
})
#function to split string at indexes
split_str_by_index <- function(target, index) {
  index <- sort(index)
  substr(rep(target, length(index) + 1),
         start = c(1, index),
         stop = c(index -1, nchar(target)))
}

close_but_not_yet = purrr::map2(fun_isolate, splt_locate, function(x, y){
   split_str_by_index(x, y)
  })
close_but_not_yet
#output
[[1]]
[1] "7*xy"                  ",b=min(1,3, head(z))"  ",z=sum(x*y+mean(x+y))"

[[2]]
[1] "j"

And just remove , on the start of the string if there is one. Example:

lapply(close_but_not_yet , function(x) gsub("^, {0,}", "",x))
#output
[[1]]
[1] "7*xy"                 "b=min(1,3, head(z))"  "z=sum(x*y+mean(x+y))"

[[2]]
[1] "j"

it won't work if the same function is called within itself like:

"max(7*xy,b=min(1,3),z=max(x*y)),mean(x+y)+maximum+max(j)"

but even that could be manageable if you exclude all from ( ) prior the first strsplit like in the , example.

tested with:

"max(7*xy,b=min(1,3, head(z)),z=sum(x*y+mean(x+y))),mean(x+y)+maximum+max(j)"
"max(7*xy,b=min(1,3, head(z)),z=sum(x*y+mean(x+y))),mean(x+y)+maximum+max(j*z+sum(a*b^sum(z)), drop = 72)"
"max(7*xy,b=min(1,3, head(z)),z=sum(x*y, mean(x+y))),mean(x+y)+maximum+max(j*z+sum(a*b^sum(z)), drop = 72)"

Upvotes: 2

Related Questions