Tester si les caractères sont dans une chaîne


279

J'essaie de déterminer si une chaîne est un sous-ensemble d'une autre chaîne. Par exemple:

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

Je veux retourner TRUE si "valeur" apparaît dans la chaîne "chars". Dans le scénario suivant, je voudrais retourner false:

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

12
La réponse acceptée est fausse, vous devez ajouter fixed=TRUE, sinon vous la traitez comme une expression régulière au lieu d'une chaîne. Voir ma réponse d'octobre 2016.
Joshua Cheek

@JoshuaCheek Sauf si vous avez des caractères spéciaux dans votre modèle, regex retournera le même résultat que fixe.
user3932000

1
Bien sûr, mais vous ne pouvez le savoir que si vous le passez littéralement. Sinon, vous ne saurez pas quels caractères se trouvent dans le modèle, vous utilisez donc fixed=TRUEou vous avez un bug qui gâchera discrètement et subtilement vos données.
Joshua Cheek

Réponses:


388

Utilisez la greplfonction

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

Utilisez ?greplpour en savoir plus.


8
Pour ce cas simple, l'ajout de fixed = TRUE peut améliorer les performances (en supposant que vous ferez beaucoup de ces calculs).
Greg Snow

1
@Josh O'brien, ce poste de conclusion par rapport (comptage) tous les matchs en une seule chaîne longue, essayez de trouver 1 correspondance dans un tas de chaînes plus courtes: vec <- replicate(100000, paste( sample(letters, 10, replace=TRUE), collapse='') ).
Greg Snow

2
@GregSnow - Essayé system.time(a <- grepl("abc", vec))et system.time(a <- grepl("abc", vec, fixed=TRUE)), et fixed=TRUEest toujours, si quelque chose légèrement plus lent. La différence n'est pas appréciable avec ces cordes courtes, mais fixed=TRUEne semble toujours pas être plus rapide. Merci d'avoir souligné, cependant, que c'est sur de longues cordes qui fixed=TRUEprend le vrai coup.
Josh O'Brien

2
grepl (motif, x) au moins en 2017
JMR

2
Cela ne devrait pas être la réponse acceptée, car la valeur sera interprétée comme un motif d'expression régulière. fixed = TRUE doit toujours être utilisé sauf si vous savez que la chaîne que vous recherchez ne ressemblera pas à un modèle d'expression régulière. La réponse de Joshua Creek ci-dessous a une explication très claire à ce sujet et devrait être la réponse acceptée.
bhaller le

159

Répondre

Soupir, il m'a fallu 45 minutes pour trouver la réponse à cette simple question. La réponse est: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

Interprétation

grepest nommé d' après l'exécutable linux, qui lui - même est un acronyme de « G lobal R REGULIERS E Xpression P Rint », on lirait les lignes d'entrée, puis les imprimer si elles correspondaient aux arguments que vous avez données. "Global" signifiait que la correspondance pouvait se produire n'importe où sur la ligne d'entrée, j'expliquerai "Expression régulière" ci-dessous, mais l'idée est que c'est une façon plus intelligente de faire correspondre la chaîne (R appelle ce "caractère", par exemple class("abc")), et "Imprimer" "car il s'agit d'un programme en ligne de commande, l'émission d'une sortie signifie qu'elle imprime sur sa chaîne de sortie.

Maintenant, le grepprogramme est essentiellement un filtre, des lignes d'entrée aux lignes de sortie. Et il semble que les Rgrep fonction prendra également un tableau d'entrées. Pour des raisons qui me sont totalement inconnues (je n'ai commencé à jouer avec R qu'il y a environ une heure), il renvoie un vecteur des index qui correspondent, plutôt qu'une liste de correspondances.

Mais, pour revenir à votre question initiale, ce que nous voulons vraiment, c'est savoir si nous avons trouvé l'aiguille dans la botte de foin, une valeur vraie / fausse. Ils ont apparemment décidé de nommer cette fonction grepl, comme dans « grep » mais avec un « L ogique » valeur de retour (ils appellent vraies et fausses valeurs logiques, par exemple class(TRUE)).

Donc, maintenant nous savons d'où vient le nom et ce qu'il est censé faire. Revenons aux expressions régulières. Les arguments, même s'ils sont des chaînes, sont utilisés pour construire des expressions régulières (désormais: regex). Une expression régulière est un moyen de faire correspondre une chaîne (si cette définition vous irrite, laissez-la aller). Par exemple, l'expression régulière acorrespond au caractère "a", l'expression régulière a*correspond au caractère "a"0 ou plusieurs fois, et l'expression régulière a+correspondrait au caractère "a"1 ou plusieurs fois. Par conséquent, dans l'exemple ci-dessus, l'aiguille que nous recherchons 1+2, lorsqu'elle est traitée comme une expression régulière, signifie "un ou plusieurs 1 suivi d'un 2" ... mais la nôtre est suivie d'un plus!

1 + 2 comme expression régulière

Donc, si vous utilisiez le greplparamètre sans fixed, vos aiguilles seraient accidentellement des meules de foin, et cela fonctionnerait accidentellement assez souvent, nous pouvons voir que cela fonctionne même pour l'exemple de l'OP. Mais c'est un bug latent! Nous devons lui dire que l'entrée est une chaîne, pas une expression régulière, ce qui est apparemment à cela fixed. Pourquoi réparé? Aucune idée, mettez cette réponse dans vos favoris b / c, vous devrez probablement la rechercher 5 fois de plus avant de la mémoriser.

Quelques dernières réflexions

Plus votre code est bon, moins vous devez connaître l'historique pour le comprendre. Chaque argument peut avoir au moins deux valeurs intéressantes (sinon il n'aurait pas besoin d'être un argument), les documents listent 9 arguments ici, ce qui signifie qu'il y a au moins 2 ^ 9 = 512 façons de l'invoquer, c'est beaucoup de travail écrire, tester et mémoriser ... découpler de telles fonctions (les séparer, supprimer les dépendances les unes des autres, les choses de chaîne sont différentes de celles de regex sont différentes de celles de vecteur). Certaines options sont également mutuellement exclusives, ne donnent pas aux utilisateurs des façons incorrectes d'utiliser le code, c'est-à-dire que l'invocation problématique doit être structurellement absurde (comme passer une option qui n'existe pas), pas logiquement absurde (où vous devez émettre un avertissement pour l'expliquer). Mettez métaphoriquement: il est préférable de remplacer la porte d'entrée sur le côté du 10e étage par un mur que de suspendre une pancarte qui met en garde contre son utilisation, mais l'une ou l'autre vaut mieux que ni l'une ni l'autre. Dans une interface, la fonction définit à quoi doivent ressembler les arguments, pas l'appelant (parce que l'appelant dépend de la fonction, inférer tout ce que tout le monde voudra l'appeler avec fait que la fonction dépend aussi des appelants et de ce type de dépendance cyclique obstrueront rapidement un système et ne fourniront jamais les avantages escomptés). Méfiez-vous des types équivoques, c'est un défaut de conception que des choses comme inférer tout ce que tout le monde voudrait l'appeler rend également la fonction dépendante des appelants, et ce type de dépendance cyclique obstruera rapidement un système et ne fournira jamais les avantages escomptés). Méfiez-vous des types équivoques, c'est un défaut de conception que des choses comme inférer tout ce que tout le monde voudrait l'appeler rend également la fonction dépendante des appelants, et ce type de dépendance cyclique obstruera rapidement un système et ne fournira jamais les avantages escomptés). Méfiez-vous des types équivoques, c'est un défaut de conception que des choses commeTRUEet 0et "abc"sont tous des vecteurs.


6
Bravo pour votre explication! Il semble que R ait évolué sur une longue période de temps et est coincé avec des choix de conception étranges (voir par exemple les réponses à cette question sur les types de valeur ). Cependant, le retour d'un vecteur d'indices de correspondance semble approprié dans ce cas, tout comme le grepfiltrage des lignes, pas des cellules.
krevelen

4
"fixe" fait référence aux caractères correspondant à une séquence "fixe".
Will Beason

32

Vous voulez grepl:

> chars <- "test"
> value <- "es"
> grepl(value, chars)
[1] TRUE
> chars <- "test"
> value <- "et"
> grepl(value, chars)
[1] FALSE

27

Utilisez cette fonction du stringipackage:

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

Quelques repères:

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

22

Aussi, peut être fait en utilisant la bibliothèque "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

20

Juste au cas où vous souhaiteriez également vérifier si une chaîne (ou un ensemble de chaînes) contient plusieurs sous-chaînes, vous pouvez également utiliser le '|' entre deux sous-chaînes.

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

Tu auras

[1]  TRUE FALSE FALSE  TRUE

puisque le premier mot a une sous-chaîne "as", et le dernier mot contient une sous-chaîne "at"


L'opérateur OR était exactement ce dont j'avais besoin! +1
Sam

10

Utilisez grepou grepl mais sachez si vous souhaitez ou non utiliser des expressions régulières .

Par défaut, grepet apparenté prennent une expression régulière pour correspondre, pas une sous-chaîne littérale. Si vous ne vous attendez pas à cela et que vous essayez de faire correspondre une expression rationnelle non valide, cela ne fonctionne pas:

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

Pour effectuer un véritable test de sous-chaîne, utilisez fixed = TRUE.

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

Si vous voulez des regex, tant mieux, mais ce n'est pas ce que l'OP semble demander.


7

Vous pouvez utiliser grep

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

0

Problème similaire ici: étant donné une chaîne et une liste de mots clés, détectez lesquels, le cas échéant, des mots clés sont contenus dans la chaîne.

Les recommandations de ce fil suggèrent stringr's str_detectet grepl. Voici les repères du microbenchmarkpackage:

En utilisant

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))
}

puis

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

nous trouvons

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

Comme vous pouvez le voir, plus de 5 000 itérations de la recherche par mot-clé en utilisant str_detectet greplsur une chaîne et un vecteur de mots-clés greplpratiques , fonctionnent bien mieux que str_detect.

Le résultat est le vecteur booléen rqui identifie les éventuels mots clés contenus dans la chaîne.

Par conséquent, je recommande d'utiliser greplpour déterminer si des mots clés se trouvent dans une chaîne.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.