查找包含字符序列的字符串,而不考虑 r 中的顺序



我有一个数据帧(df(

V1    V2
1 "BCC"  Yes
2 "ABB"  Yes

我想找到包含特定字符序列的所有字符串,无论顺序如何。 例如,如果我有字符串"CBC"或"CCB",我想得到

V1    V2
1 "BCC"  Yes

我尝试过使用 grep,但它只找到匹配的模式

>df[grep("CBC", df$V1),]
1  V1   V2
<0 rows> (or 0-length row.names)
>df[grep("BCC", df$V1),]
V1   V2
1 "BCC" Yes

我们可以通过拆分列来创建逻辑索引

i1 <- sapply(strsplit(df$V1, ""), function(x) all(c("B", "C") %in% x))
df[i1, , drop = FALSE]
#   V1  V2
#1 BCC Yes

如果我们有两个数据集,其中一个是查找表('DF2'(,则将列拆分为字符,pastesorted 元素,并使用%in%创建用于过滤行的逻辑vector

v1n <- sapply(strsplit(df1$v1, ""), function(x) paste(sort(x), collapse=""))
v1l <- sapply(strsplit(df2$v1, ""), function(x) paste(sort(x), collapse=""))
df1[v1n %in% v1l, , drop = FALSE]

数据

df1 <- data.frame(v1 = c("BCC", "CAB" , "ABB", "CBC", "CCB", "BAB", "CDB"),
stringsAsFactors = FALSE)
df2 <- data.frame(v1 = c("CBC", "ABB"), stringsAsFactors = FALSE)

在评论中,您提到了查找表。如果是这种情况,一种方法可能是将两个集合连接在一起,然后使用 Wiktor Stribiżew 的正则表达式来指示哪些是有效的

当我加入数据集时,我将使用data.table

方法 1:加入所有内容

library(data.table)
## dummy data, and a lookup table
dt <- data.frame(V1 = c("BCC", "ABB"))
dt_lookup <- data.frame(V1 = c("CBC","BAB", "CCB"))
## convert to data.table
setDT(dt); setDT(dt_lookup)
## add some indexes to keep track of rows from each dt
dt[, idx := .I]
dt_lookup[, l_idx := .I]
## create a column to join on
dt[, key := 1L]
dt_lookup[, key := 1L]
## join EVERYTHING
dt <- dt[
dt_lookup
, on = "key"
, allow.cartesian = T
]
#regex
dt[
, valid := grepl(paste0("^[",i.V1,"]+$"), V1)
, by = 1:nrow(dt)
]
#     V1 idx key i.V1 l_idx valid
# 1: BCC   1   1  CBC     1  TRUE
# 2: ABB   2   1  CBC     1 FALSE
# 3: BCC   1   1  BAB     2 FALSE
# 4: ABB   2   1  BAB     2  TRUE
# 5: BCC   1   1  CCB     3  TRUE
# 6: ABB   2   1  CCB     3 FALSE

方法二:易极联接

一种稍微节省内存的方法可能是使用 Jaap 的这种技术,因为它避免了"连接所有内容"步骤,而是一次"按每个 i"(行(连接它。

dt_lookup[
dt, 
{
valid = grepl(paste0("^[",i.V1,"]+$"), V1)
.(
V1 = V1[valid]
, idx = i.idx
, match = i.V1
, l_idx = l_idx[valid]
)
}
, on = "key"
, by = .EACHI
]
#    key  V1 idx match l_idx
# 1:   1 CBC   1   BCC     1
# 2:   1 CCB   1   BCC     3
# 3:   1 BAB   2   ABB     2

这是一个使用sapplytableidentical的方法。

# construct a named vector of integers with names in 
# alphabetical order: your match
myVal <- c("B"=1L, "C"=2L)
# run through character variable, perform check
sapply(strsplit(dat$V1, ""), function(x) identical(c(table(x)), myVal))
[1]  TRUE FALSE

identical的使用和table的输出相关的两个关键点:

  1. 匹配向量,myVal 必须是整数。
  2. 你想按字母顺序对匹配向量进行排序,你可以提前这样做,你也可以在事后使用ordernames[来做。

另外,并不是说我将table的输出包装在c中以去除不需要的属性,同时保留名称。

您可以使用stringi::stri_count_regex来查看字符串中的出现次数是否与strsplit(str_to_find, '')table匹配。最后一个reduce("|")意味着它正在检查是否有任何匹配项,因此如果要检查它是否与to.find中的所有字符串匹配,请将|更改为&

set.seed(0)
df <- data.frame(a = replicate(20, paste0(sample(LETTERS[1:3], 3, T), collapse = ''))
, stringsAsFactors = F)
to.find <- c("CBB", "CCB")
to.find <- strsplit(to.find, '')
library(tidyverse)
library(stringi)
df$b <- 
sapply(df$a, function(x){
lapply(to.find, function(y){
imap(table(y), ~ .x == stri_count_regex(x, .y)) %>% 
reduce(`&`)}) %>% 
reduce(`|`)})
df
# a     b
# 1  CAB FALSE
# 2  BCA FALSE
# 3  CCB  TRUE
# 4  BAA FALSE
# 5  ACB FALSE
# 6  CBC  TRUE
# 7  CBC  TRUE
# 8  CAB FALSE
# 9  AAB FALSE
# 10 ABC FALSE
# 11 BBB FALSE
# 12 BAC FALSE
# 13 CCA FALSE
# 14 CBC  TRUE
# 15 BCB  TRUE
# 16 BCA FALSE
# 17 BCC  TRUE
# 18 BCB  TRUE
# 19 AAA FALSE
# 20 ABB FALSE
# 19 AAA FALSE
# 20 ABB FALSE

你也可以用map来完成这一切,但这更难阅读

df$b <- 
df$a %>% 
map(~{x <- .x
map(to.find, 
~imap(table(.x), ~ .x == stri_count_regex(x, .y)) %>% 
reduce(`&`)) %>% 
reduce(`|`)})

最新更新