测试字符串中是否包含特定字符

392

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

chars <- "test"
value <- "es"

如果字符串“chars”中包含“value”,我想返回TRUE。在以下情况下,我希望返回false:

chars <- "test"
value <- "et"

17
被选中的答案是错误的,你需要添加 fixed=TRUE,否则你会将其视为正则表达式而不是字符串。请参考我在2016年10月发表的回答。 - Joshua Cheek
@JoshuaCheek 除非您的模式中有特殊字符,否则正则表达式将返回与固定模式相同的结果。 - user3932000
3
可以,但是你只有在传递一个字面值时才能知道它的内容。否则,你将不知道模式中有哪些字符,因此你要么使用 fixed=TRUE,要么就会出现悄悄而微妙地破坏数据的错误。 - Joshua Cheek
9个回答

547

使用 grepl 函数。

grepl( needle, haystack, fixed = TRUE)

就像这样:

grepl(value, chars, fixed = TRUE)
# TRUE

使用?grepl来了解更多信息。


8
对于这个简单的案例,加上fixed=TRUE或许能提高性能(假设你会频繁进行这些计算)。 - Greg Snow
2
@Josh O'Brien,那篇文章比较了在单个长字符串中查找(计数)所有匹配项和在一堆短字符串中查找1个匹配项的情况:vec <- replicate(100000, paste( sample(letters, 10, replace=TRUE), collapse='') ) - Greg Snow
3
尝试了 system.time(a <- grepl("abc", vec))system.time(a <- grepl("abc", vec, fixed=TRUE)),即使使用 fixed=TRUE,速度仍然稍微慢一些。在这些短字符串中,差别不明显,但 fixed=TRUE 似乎仍然没有更快。感谢指出,在长字符串上才会真正影响 fixed=TRUE 的速度。 - Josh O'Brien
2
grepl(pattern, x)至少在2017年就存在了。 - JMR
3
这个答案不应该被接受,因为"value"将被解释为一个正则表达式模式。除非你知道你要搜索的字符串不会像正则表达式模式一样,否则应始终使用fixed=TRUE。Joshua Creek在下面的答案中对此有非常清晰的解释,并且应该被接受作为正确答案。 - bhaller
显示剩余4条评论

229

答案

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

# 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 可执行文件命名的,它本身是 "Global Regular Expression Print" 这个缩写,它将读取输入行,如果符合给定的参数,则会打印它们。"Global" 表示可以在输入行的任何位置进行匹配,下面我将解释 "正则表达式",但基本思想是它是一种更智能的匹配字符串的方法(R 称之为 "字符",例如 class("abc")),而 "Print" 是因为它是一个命令行程序,发出输出意味着它打印到其输出字符串中。

现在,grep 程序基本上是一个过滤器,从输入行到输出行。看起来 R 的 grep 函数也会像它一样接受一个输入数组。出于我完全不知道的原因(我只在大约一个小时前开始使用 R),它返回与匹配项相符的索引向量,而不是匹配项列表。

但是,回到您最初的问题,我们真正想知道的是在大海捞针中是否找到了针头,即一个 true/false 值。他们显然决定将这个函数命名为 grepl,就像 "grep" 一样,但具有 "Logical" 返回值(他们将 true 和 false 称为逻辑值,例如 class(TRUE))。

因此,现在我们知道了它的命名来自何处以及它应该做什么。让我们回到正则表达式。尽管参数是字符串,但它们用于构建正则表达式(以下简称 regex)。正则表达式是一种匹配字符串的方法(如果这个定义使您感到不悦,请放手)。例如,正则表达式 a 匹配字符 "a",正则表达式 a* 匹配字符 "a" 0 次或多次,而正则表达式 a+ 则匹配字符 "a" 1 次或多次。因此,在上面的示例中,我们正在搜索的针头是 1+2,当作为正则表达式处理时,它的意思是 "一个或多个 1 后跟一个 2"... 但我们的要求是它后面还要加上一个加号!

1+2 as a regex

因此,如果您在没有设置 fixed 的情况下使用了 grepl,则您的针头将意外地成为大海捞针中的一部分,这通常会意外地起作用,我们可以看到它甚至对 OP 的示例也有效。但这是一个隐藏的错误!我们需要告诉它输入是字符串而不是正则表达式,这似乎就是 fixed 的作用。为什么是 fixed?我不知道,记住这个答案,因为您可能需要查阅它 5 次才能记住它。

最后几点思考

你的代码越好,你就需要更少的历史知识以便理解它。每个参数都可以有至少两个有趣的值(否则它不需要成为参数),文档


9
感谢您的解释!看起来 R 语言经历了漫长的发展过程,并且在一些奇怪的设计选择上被困住(例如参见对此值类型问题的回答)。然而,在这种情况下返回匹配索引向量似乎是合适的,因为 grep 是在过滤行而不是单元格。 - krevelen
5
“fixed”指的是与一个“固定”的序列匹配的字符。 - Will Beason
3
TL;DR: “fixed” 意味着搜索模式不应被视为正则表达式。 - Josiah Yoder
2
如果你阅读了?grep页面,你会发现value=TRUE会使它表现得像你最初期望的那样。一直抱怨它不按照你的预期行事只意味着在开始抱怨之前你没有阅读文档。 - IRTFM

43
你需要使用grepl函数:
> chars <- "test"
> value <- "es"
> grepl(value, chars)
[1] TRUE
> chars <- "test"
> value <- "et"
> grepl(value, chars)
[1] FALSE

39

同时,也可以使用"stringr" 库来完成:

> 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

What's the difference between the str_detect function in stringer and grepl and grep?中,对stringr的优点进行了很好的讨论。 - sfuqua

33

使用来自stringi包的此函数:

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

一些基准测试:

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

23

如果您想检查一个字符串(或一组字符串)是否包含多个子字符串,您也可以在两个子字符串之间使用 '|'。

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

您将获得

[1]  TRUE FALSE FALSE  TRUE

由于第一个单词包含子字符串 "as",且最后一个单词包含子字符串 "at"


OR运算符正是我所需要的!+1 - sam

11

使用 grepgrepl 但要注意你是否想使用正则表达式

默认情况下,grep 和相关函数需要使用 正则表达式 进行匹配,而不是普通的字面上的子字符串。如果您没有意识到这一点,并尝试在无效的正则表达式上进行匹配,它将无法工作:

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

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

> grep("[", "abc[", fixed = TRUE)
[1] 1
如果您确实需要正则表达式,那很好,但是这似乎不是OP所要求的。

7
您可以使用grep
grep("es", "Test")
[1] 1
grep("et", "Test")
integer(0)

1
这里有一个类似的问题:给定一个字符串和一个关键词列表,检测字符串中是否包含其中任何一个关键词。

本线程的建议是使用stringr库的str_detect函数和grepl函数。以下是microbenchmark包的基准测试结果:

使用

map_keywords = c("once", "twice", "few")
t = "yes but only a few times"

mapper1 <- function (x) {
  r = str_detect(x, map_keywords)
}

mapper2 <- function (x) {
  r = sapply(map_keywords, function (k) grepl(k, x, fixed = T))
}

然后

microbenchmark(mapper1(t), mapper2(t), times = 5000)

我们发现
Unit: microseconds
       expr    min     lq     mean  median      uq      max neval
 mapper1(t) 26.401 27.988 31.32951 28.8430 29.5225 2091.476  5000
 mapper2(t) 19.289 20.767 24.94484 23.7725 24.6220 1011.837  5000

如您所见,在一个实际的字符串和关键字向量上使用 str_detectgrepl 进行超过 5,000 次迭代的关键字搜索后,grepl 的表现比 str_detect 好得多。
结果是布尔向量 r,它标识了哪些关键字(如果有)包含在字符串中。
因此,我建议使用 grepl 来确定字符串中是否存在任何关键字。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接