如何在字符向量中删除重复元素

3
s <- "height(female), weight, BMI, and BMI."

在上面的字符串中,“BMI”这个单词被重复了两次。我想把这个字符串变成:
"height (female), weight, and BMI."

我已经尝试以下方法将字符串拆分为唯一部分:
> unique(strsplit(s, " ")[[1]])
[1] "height"      "(female),"   "weight,"    "BMI," "and"         "BMI."

但是由于"BMI"和"BMI."不是相同的字符串,使用unique并不能消除其中一个。

编辑:我该如何处理重复短语?(例如使用“身体质量指数”代替BMI)

s <- "height (female), weight, weight, body mass index, body mass index." 
s <- stringr::str_replace(s, "(?<=, |^)\\b([()\\w\\s]+),\\s(.*?)((?: and)?(?=\\1))", "\\2") 
> stringr::str_replace(s, "(\\w+)(\\(.*?\\))", "\\1 \\2")
[1] "height (female), weight, body mass index, body mass index."

规则对我来说不是很清楚。s的结构是否总是"<keyword>,<keyword>,...,and <keyword>"?一个关键字可以包含逗号、空格、单词“and”等吗? - Maurits Evers
s 的结构始终为“<关键词>,<关键词>,...,和<关键词>。” 一些“<关键词>”可能包含“(男)”或“(女)”。 每个“<关键词>”后面都跟着“,”,而最后一个<关键词>前面则是“和”。 - Adrian
你能提供一个覆盖更多边缘情况的例子吗?例如,如果有两个height(female)会发生什么?或者这种情况不会发生吗? - andrew_reece
@andrew_reece 那是不会发生的。 - Adrian
那么,你想删除的唯一子字符串是“ BMI,”吗? 像 stringr::str_replace(s, " BMI,", "")这样的操作? - andrew_reece
你想要删除单词的第一个实例还是第二个/第n个实例? - Chris
3个回答

1

使用类似于这样的正则表达式先替换掉不需要的重复内容可能会有所帮助:

(?<=,|^)([()\w\s]+),\s(.*?)((?: and)?(?=\1))

演示

解释

  • (?<=, |^)\b 前边界。(\b 也可以工作,但是没有正确地锚定)
  • ([()\w\s]+), 块元素
  • \s(.*?)((?: and)? 中间的所有内容
  • (?=\1)) 重复元素

代码示例:

#install.packages("stringr")
library(stringr)
s <- "height(female), weight, BMI, and BMI."
stringr::str_replace(s, "(?<=, |^)\\b([()\\w\\s]+),\\s(.*?)((?: and)?(?=\\1))", "\\2")

输出:

[1] "height(female), weight, and BMI."

关于括号中部分的分隔,请使用另一个类似的替换:

stringr::str_replace(s, "(\\w+)(\\(.*?\\))", "\\1 \\2")

输出:

[1] "height (female), weight, and BMI."

测试和组合事物:

s <- c("height(female), weight, BMI, and BMI."
       ,"height(female), weight, whatever it is, and whatever it is."
       ,"height(female), weight, age, height(female), and BMI."
       ,"weight, weight.")
s <- stringr::str_replace(s, "(?<=, |^)\\b([()\\w\\s]+),\\s(.*?)((?: and)?(?=\\1))", "\\2")
stringr::str_replace(s, "(\\w+)(\\(.*?\\))", "\\1 \\2")

输出:

[1] "height (female), weight, and BMI."      "height (female), weight, and whatever it is."
[3] "weight, age, height (female), and BMI." "weight."    

谢谢。当我运行代码时,我得到的是“height(female), weight,和BMI.” 也就是说,“height”和“(female)”之间少了一个空格,“weight,”和“and”之间也少了一个空格。 - Adrian
@Adrian 你是对的;我在发布代码时做了最后一分钟的更改,把它搞砸了。已更新。 - wp78de
再次感谢您的更新。我目前遇到了一个问题,当我使用s <- "height (female), body mass index, body mass index."时,即重复的部分不再是单词(BMI),而是一个短语(体重指数)。我该如何处理这种情况? - Adrian
@Adrian 当我运行那行代码时,我得到了“身高(女性),体重指数。”这是怎么回事? - wp78de
我已经更新了原始帖子,添加了一个新的字符串。我正在运行s <- "height (female), weight, weight, body mass index, body mass index."。然后我使用stringr::str_replace(s, "(?<=, |^)\\b([()\\w\\s]+),\\s(.*?)((?: and)?(?=\\1))", "\\2")进行替换。最后我再次使用stringr::str_replace(s, "(\\w+)(\\(.*?\\))", "\\1 \\2")进行替换。 - Adrian

1
你可以尝试使用这个正则表达式:

(\b\w+\b)[^\w\r\n]+(?=.*\1)

并用空字符串替换每个匹配项

点击查看演示

查看Ruby代码

输入

height(female), weight, BMI, BMI, BMI, BMI, BMI, BMI, BMI, BMI, BMI, BMI, and BMI.
height(female), weight, BMI, age, and BMI.

输出

height(female), weight, and BMI.
height(female), weight, age, and BMI.

解释:

  • (\b\w+\b) - 匹配由单词边界包围的1个或多个单词字符,并在组1中捕获它
  • [^\w\r\n]+ - 匹配1个或多个既不是单词字符也不是换行符的任意字符。因此,这将匹配,.或空格。
  • (?=.*\1) - 正向先行断言,验证组1中匹配的任何内容必须稍后再次出现在字符串中。只有在这种情况下才会进行替换。

注意:这将保留重复单词的最后一次出现。

或者,如果重复单词中还包含空格,则可以使用 (\b[^,]+)[, ]+(?=.*\1)


我喜欢它,但如果存在相似的子字符串,则会失败:height(female), weight, whatever this is, and whatever that is. - wp78de
是的,因为在第一组中,我只允许连续的单词字符\w+,这将不允许空格。无论如何,这会给OP一个想法 :) - Gurmanjot Singh
1
这个问题的提问者并没有表述得很清楚。如果不是这种情况,你的回答基本上就符合要求了。 - wp78de
@Gurman 谢谢。请问实现这个的确切 R 代码是什么?stringr::str_replace(s, "(\b\w+\b)[^\w\r\n]+(?=.*\1)", "") 运行时出现错误:Error: '\w' is an unrecognized escape in character string starting ""(\b\w" - Adrian

0
library(stringr)

s <- "height(female), weight, BMI, and BMI, and more even more BMI."
pieces <- unlist(str_split(s, "\\b"))
non_word <- !grepl("\\w", pieces)

# if you want to keep just the last instance of a duplicated word
non_duped <- !duplicated(pieces, fromLast = TRUE)
paste0(pieces[non_word | non_duped], collapse = "")
#> [1] "height(female), weight, ,  , and  even more BMI."

# if you want to keep just the first instance of a duplicated word
non_duped <- !duplicated(pieces, fromLast = FALSE)
paste0(pieces[non_word | non_duped], collapse = "")
#> [1] "height(female), weight, BMI, and ,  more even  ."

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