关于r:测试字符是否在字符串中

Test if characters are in a string

我试图确定一个字符串是否是另一个字符串的子集。例如:

1
2
chars <-"test"
value <-"es"

如果"value"作为字符串"chars"的一部分出现,我想返回true。在下面的场景中,我希望返回false:

1
2
chars <-"test"
value <-"et"


使用grepl功能

1
2
grepl(value, chars)
# TRUE


回答

叹息,我花了45分钟才找到这个简单问题的答案。答案是:grepl(needle, haystack, fixed=TRUE)

1
2
3
4
5
6
7
8
9
10
11
# Correct
> grepl("1+2","1+2", fixed=TRUE)
[1] TRUE
> grepl("1+2","123+456", fixed=TRUE)
[1] FALSE

# Incorrect
> grepl("1+2","1+2")
[1] FALSE
> grepl("1+2","123+456")
[1] TRUE

解释

grep是以linux可执行文件命名的,linux可执行文件本身是"global regular expression print"的缩写,它将读取输入行,如果它们与您提供的参数匹配,则将其打印出来。全局"意味着匹配可能发生在输入行的任何地方,我将解释下面的"正则表达式",但我的想法是它是一种更聪明的匹配字符串的方法(r称此为"字符",例如class("abc")),而"打印"是因为它是一个命令行程序,发出输出意味着它打印到输出字符串。

现在,grep程序基本上是一个过滤器,从输入行到输出行。似乎R的grep函数也将接受一个输入数组。由于我完全不知道的原因(我大约一小时前才开始使用r),它返回匹配索引的向量,而不是匹配列表。

但是,回到你最初的问题,我们真正想要的是知道我们是否在干草堆中找到了针,一个正确/错误的值。他们显然决定将这个函数命名为grepl,就像在grep中一样,但使用"逻辑"返回值(他们称为真和假逻辑值,如class(TRUE))。

所以,现在我们知道这个名字是从哪里来的,应该怎么做。让我们回到正则表达式。这些参数,即使它们是字符串,也用于构建正则表达式(从此:regex)。regex是一种匹配字符串的方法(如果这个定义让您不快,就让它走吧)。例如,regex a匹配"a"字符,regex a*匹配"a"0次或更多次,regex a+匹配"a"1次或更多次。因此,在上面的例子中,我们正在搜索的1+2针,当作为regex处理时,表示"一个或多个1后跟一个2"…但我们的后面跟着一个加号!

1+2 as a regex

所以,如果你在没有设置fixed的情况下使用grepl,你的针会意外地变成干草堆,这会经常意外工作,我们可以看到它甚至适用于OP的例子。但那是一只潜伏的虫子!我们需要告诉它输入是一个字符串,而不是regex,这显然是fixed的作用。为什么要固定?不知道,把这个答案记在B/C上,你可能还要再查5遍才能记住。

最后的几点想法

你的代码越好,你就越不需要了解历史来理解它。每个参数至少可以有两个有趣的值(否则它不需要是一个参数),docs在这里列出了9个参数,这意味着至少有2^9=512种方法可以调用它,这是大量的编写、测试和记住工作…分离这样的函数(将它们分开,消除相互依赖关系,字符串的内容与regex不同,而向量的内容不同)。有些选项也是互斥的,不要给用户错误的代码使用方法,即有问题的调用在结构上应该是无意义的(例如传递一个不存在的选项),而不是逻辑上的无意义的(必须发出警告来解释它)。打个比方:把10楼的前门换成墙总比挂一个警告不要使用的标志要好,但两者都不如。在一个接口中,函数定义参数应该是什么样子的,而不是调用方(因为调用方依赖于函数,推断出每个人可能都想用它调用的所有内容,使得函数也依赖于调用方,并且这种类型的循环依赖性将很快阻塞系统,并且永远不会提供任何好处。你期待)。小心模棱两可的类型,像TRUE0"abc"这样的东西都是向量,这是一个设计缺陷。


你要的是grepl

1
2
3
4
5
6
7
8
> chars <-"test"
> value <-"es"
> grepl(value, chars)
[1] TRUE
> chars <-"test"
> value <-"et"
> grepl(value, chars)
[1] FALSE

使用stringi包中的此功能:

1
2
> stri_detect_fixed("test",c("et","es"))
[1] FALSE  TRUE

一些基准:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
library(stringi)
set.seed(123L)
value <- stri_rand_strings(10000, ceiling(runif(10000, 1, 100))) # 10000 random ASCII strings
head(value)

chars <-"es"
library(microbenchmark)
microbenchmark(
   grepl(chars, value),
   grepl(chars, value, fixed=TRUE),
   grepl(chars, value, perl=TRUE),
   stri_detect_fixed(value, chars),
   stri_detect_regex(value, chars)
)
## Unit: milliseconds
##                               expr       min        lq    median        uq       max neval
##                grepl(chars, value) 13.682876 13.943184 14.057991 14.295423 15.443530   100
##  grepl(chars, value, fixed = TRUE)  5.071617  5.110779  5.281498  5.523421 45.243791   100
##   grepl(chars, value, perl = TRUE)  1.835558  1.873280  1.956974  2.259203  3.506741   100
##    stri_detect_fixed(value, chars)  1.191403  1.233287  1.309720  1.510677  2.821284   100
##    stri_detect_regex(value, chars)  6.043537  6.154198  6.273506  6.447714  7.884380   100

为了防止您还要检查一个字符串(或一组字符串)是否包含多个子字符串,您还可以在两个子字符串之间使用""。

1
2
3
>substring="as|at"
>string_vector=c("ass","ear","eye","heat")
>grepl(substring,string_vector)

你会得到

1
[1]  TRUE FALSE FALSE  TRUE

因为第一个字有子串"as",最后一个字有子串"at"


此外,还可以使用"stringer"库:

1
2
3
4
5
6
7
8
9
10
> library(stringr)
> chars <-"test"
> value <-"es"
> str_detect(chars, value)
[1] TRUE

### For multiple value case:
> value <- c("es","l","est","a","test")
> str_detect(chars, value)
[1]  TRUE FALSE  TRUE FALSE  TRUE


使用grepgrepl,但要注意是否要使用正则表达式。

默认情况下,grep和relevant采用正则表达式进行匹配,而不是文字子字符串。如果您没有预料到这一点,并且您试图在一个无效的regex上进行匹配,那么它将不起作用:

1
2
3
> grep("[","abc[")
Error in grep("[","abc[") :
  invalid regular expression '[', reason 'Missing ']''

要进行真正的子串测试,请使用fixed = TRUE

1
2
> grep("[","abc[", fixed = TRUE)
[1] 1

如果你真的想要Regex,太好了,但这不是OP想要的。


您可以使用grep

1
2
3
4
grep("es","Test")
[1] 1
grep("et","Test")
integer(0)