【问题标题】:Extracting element from a string从字符串中提取元素
【发布时间】:2019-03-04 08:59:46
【问题描述】:

我试图在没有任何包的情况下提取两个 HTML 标记之间的元素。

假设我有一个字符串:

string <- c("<tb example", "<TEXT>", "some text", "<TEXT>", "<TEXT> some more text <TEXT>")

我想提取前两个 TEXT 元素之间的元素(一些文本)。

我曾尝试使用 grelp 和 gregexpr,但无法使其正常工作。

string[grepl("<TEXT>(.*?)<TEXT>", string, ignore.case = F)]
regmatches(string,gregexpr("<TEXT>", string, ignore.case = F))

谢谢

【问题讨论】:

  • 字符串的正确代码:string ", "some text", "", " some more text ")
  • 对于这些任务,我使用gsub 和标准正则表达式分组。类似gsub("(.*TEXT)(sometext)(TEXT.*)", \\2,string)
  • 如果您使用的是vector,那么您还可以通过索引string[3] 获取字符串(假设您的vector 结构是固定的)。

标签: r string text data-cleaning grepl


【解决方案1】:

正则表达式概念在这里不起作用,因为你有一个向量,而不是一个字符串。在向量上执行此操作的一个想法是识别前两个&lt;TEXT&gt;,使用seq 完成序列,然后使用setdiff 隔离所需的值,即

v1 <- which(string == '<TEXT>')[1]
v2 <- which(string == '<TEXT>')[2]

string[setdiff(seq(v1, v2), c(v1, v2))]
#[1] "some text"

【讨论】:

  • 如果 根本不出现会报错。
【解决方案2】:

首先将您的字符串向量连接成一个字符串,然后使用stringr 包进行正则表达式

library(stringr)

string <- c("<tb example", "<TEXT>", "some text", "<TEXT>", "<TEXT> some more text <TEXT>")
string_c <- paste(string, collapse = " ")

# Find the first occurrence
res <- str_match(string_c, "<TEXT> (.*?) <TEXT>")

res[2]
#[1] "some text"

【讨论】:

    【解决方案3】:

    除了 3a 之外,这些假设您正在寻找 string 的第一个长度为 3 的子序列,其形式为 &lt;TEXT&gt; something &lt;TEXT&gt;;但是,如果在连续的&lt;TEXT&gt; 分量之间允许有任意数量的string 向量分量,则使用 3a 或 4a。

    1) 如果 string 至少有 3 个元素,则创建一个三列移动窗口矩阵 m 并计算一个逻辑向量 ok,对于其中第 1 列和第3 等于&lt;TEXT&gt;。取m 的第二列的第一个元素,其中有一行ok 为TRUE。

    如果没有匹配项,则此代码返回零长度字符向量。没有使用任何包。

    m <- embed(string, 3)
    ok <- !rowSums(m[, -2, drop = FALSE] != "<TEXT>")
    head(m[ok, 2], 1)
    ## [1] "some text"
    

    2) 对于e 的每一行,如果第一个和最后一个元素是&lt;TEXT&gt;,则返回中间元素,否则返回NA。 然后移除 NA 并取出剩下的第一个元素。

    e <- embed(string, 3)
    Select <- function(x) ifelse(all(x[-2] == "<TEXT>"), x[2], NA)
    head(na.omit(apply(e, 1, Select)), 1)
    ## [1] "some text"
    

    3) 另一种基本方法是创建一个与string 长度相同的分组向量g,每次遇到&lt;TEXT&gt; 时都会增加1。然后使用tapply 提取每个组的第二个元素,长度为 2 或 NA 如果其他长度。删除 NA 元素并取第一个剩下的元素。如果没有匹配的子序列,则返回零长度字符向量。

    g <- cumsum(string == "<TEXT>")
    Select2 <- function(x) if (length(x) == 2) x[2] else NA
    ta <- tapply(string[g > 0], g[g > 0], Select2)
    as.character(head(na.omit(c(ta)), 1))
    ## [1] "some text"
    

    3a) 如果string 的多个组件在&lt;TEXT&gt; 组件之间是允许的,则进行以下修改。如果没有匹配的子序列,则返回 NULL。

    g <- cumsum(string == "<TEXT>")
    ta <- tapply(string[g > 0], g[g > 0], function(x) x[-1], simplify = FALSE)
    unname(unlist((head(ta, 1))))
    ## [1] "some text"
    

    4) 这会在每个 &lt;TEXT&gt; 元素之后查找元素并挑选出其中的第一个。如果只有一个 &lt;TEXT&gt; 则返回其后的元素,如果根本没有 &lt;TEXT&gt; 元素或唯一的 &lt;TEXT&gt; 元素出现在输入向量的末尾,则返回长度为 0 的字符向量。

    head(string[c(FALSE, head(string, -1) == "<TEXT>")], 1)
    ## [1] "some text"
    

    4a) 如果在&lt;TEXT&gt; 元素之间可以出现多个元素,请改用它。请注意,如果存在少于两个 &lt;TEXT&gt; 元素,则返回 NULL。如果我想在这种情况下返回其他内容,请在 if 中添加 else 分支。

    ix <- head(which(string == "<TEXT>"), 2)
    if (length(ix) == 2) string[seq(ix[1] + 1, ix[2] - 1)]
    ## [1] "some text"
    

    【讨论】:

    • 感谢您的精彩解释!但是,我找到了一种更简单的方法。 string ", string)[1:2] string[text.range[1]:text.range[2]]
    • 我添加了 4a,因为仍然没有说明是否允许多个中间元素。总的来说,我认为 4 或 4a 更可取。他们给出了正确的答案,即使没有 &lt;TEXT&gt; 元素也能正常工作。对于您评论中的代码,这些都不正确。
    猜你喜欢
    • 2013-12-10
    • 2018-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-18
    • 2018-06-22
    • 2018-06-09
    相关资源
    最近更新 更多