web-dev-qa-db-fra.com

Remplacer des mots dans un fichier texte non structuré à l'aide d'une boucle for

J'ai un fichier texte TRÈS non structuré que j'ai lu avec readLines. Je veux changer certaines chaînes en une autre chaîne qui se trouve dans une variable (appelée "nouvelle" ci-dessous). 

Ci-dessous, je souhaite que le texte manipulé comprenne tous les termes: "un", "deux", "trois" et "quatre" une fois, au lieu des chaînes "de modification". Cependant, comme vous pouvez le voir, le premier motif de chaque élément change, mais j'ai besoin du code pour ignorer qu'il y a de nouvelles chaînes avec des guillemets. 

Voir exemple de code et de données ci-dessous. 

 #text to be changed
 text <- c("TEXT TEXT TEXT TEXT TEXT TEXT TEXT TEXT TEXT change",
        "TEXT TEXT TEXT change TEXT TEXT TEXT TEXT TEXT change", 
        "TEXT TEXT TEXT change TEXT TEXT TEXT TEXT")

 #Variable containing input for text
 new <- c("one", "two", "three", "four")
 #For loop that I want to include 
 for (i in 1:length(new)) {

   text  <- sub(pattern = "change", replace = new[i], x = text)

 }
 text
9
Gorp

Que dis-tu de ça? La logique est la suivante: tuez une chaîne jusqu'à ce qu'elle n'ait plus change. Sur chaque "hit" (où change est trouvé), déplacez-vous le long du vecteur new.

text <- c("TEXT TEXT TEXT TEXT TEXT TEXT TEXT TEXT TEXT change",
          "TEXT TEXT TEXT change TEXT TEXT TEXT TEXT TEXT change", 
          "TEXT TEXT TEXT change TEXT TEXT TEXT TEXT")

#Variable containing input for text
new <- c("one", "two", "three", "four")
new.i <- 1

for (i in 1:length(text)) {
  while (grepl(pattern = "change", text[i])) {
    text[i] <- sub(pattern = "change", replacement = new[new.i], x = text[i])
    new.i <- new.i + 1
  }
}
text

[1] "TEXT TEXT TEXT TEXT TEXT TEXT TEXT TEXT TEXT one" 
[2] "TEXT TEXT TEXT two TEXT TEXT TEXT TEXT TEXT three"
[3] "TEXT TEXT TEXT four TEXT TEXT TEXT TEXT" 
8
Roman Luštrik

Voici une autre solution utilisant gregexpr() et regmatches():

#text to be changed
text <- c("TEXT TEXT TEXT TEXT TEXT TEXT TEXT TEXT TEXT change",
          "TEXT TEXT TEXT change TEXT TEXT TEXT TEXT TEXT change",
          "TEXT TEXT TEXT change TEXT TEXT TEXT TEXT")

#Variable containing input for text
new <- c("one", "two", "three", "four")

# Alter the structure of text
altered_text <- paste(text, collapse = "\n")

# So we can use gregexpr and regmatches to get what you want
matches <- gregexpr("change", altered_text)
regmatches(altered_text, matches) <- list(new)

# And here's the result
cat(altered_text)
#> TEXT TEXT TEXT TEXT TEXT TEXT TEXT TEXT TEXT one
#> TEXT TEXT TEXT two TEXT TEXT TEXT TEXT TEXT three
#> TEXT TEXT TEXT four TEXT TEXT TEXT TEXT

# Or, putting the text back to its old structure
# (one element for each line)
unlist(strsplit(altered_text, "\n"))
#> [1] "TEXT TEXT TEXT TEXT TEXT TEXT TEXT TEXT TEXT one" 
#> [2] "TEXT TEXT TEXT two TEXT TEXT TEXT TEXT TEXT three"
#> [3] "TEXT TEXT TEXT four TEXT TEXT TEXT TEXT"

Créé le 2018-10-16 par le paquet reprex (v0.2.1)

Nous pouvons le faire puisque gregexpr() peut trouver toutes les correspondances dans le texte pour "changer"; à partir de help("gregexpr"):

regexpr renvoie un vecteur entier de la même longueur que le texte donnant la position de départ du premier match ....

gregexpr retourne une liste de la même longueur que text chaque élément de qui a la même forme que la valeur de retour pour regexpr, sauf que les positions de départ de tous les matchs (disjoints) sont indiquées.

(emphase ajoutée).

Ensuite, regmatches() peut être utilisé pour extraire les correspondances trouvées par gregexpr()ou les remplacer; à partir de help("regmatches"):

Usage

regmatches (x, m, invert = FALSE)
regmatches (x, m, invert = FALSE) <- valeur

...

valeur
un objet avec des valeurs de remplacement appropriées pour la correspondance ou sous-chaînes non appariées (voir Détails).

...

Détails

La fonction de remplacement peut être utilisée pour remplacer le correspondant ou sous-chaînes non appariées. Pour les données de correspondance vectorielle, si inverser est égal à FALSE, La valeur doit être un vecteur de caractères dont la longueur correspond au nombre d'appariés éléments en m. Sinon, il devrait s'agir d'une liste de vecteurs de caractères avec la même longueur que m, chacun aussi longtemps que le nombre de remplacements nécessaire.

3
duckmayr

Une autre approche utilisant strsplit:

tl <- lapply(text, function(s) strsplit(s, split = " ")[[1]])
df <- stack(setNames(tl, seq_along(tl)))

ix <- df$values == "change"
df[ix, "values"] <- new
tapply(df$values, df$ind, paste, collapse = " ")

qui donne:

                                                  1 
 "TEXT TEXT TEXT TEXT TEXT TEXT TEXT TEXT TEXT one" 
                                                  2 
"TEXT TEXT TEXT two TEXT TEXT TEXT TEXT TEXT three" 
                                                  3 
          "TEXT TEXT TEXT four TEXT TEXT TEXT TEXT"

De plus, vous pouvez envelopper l'appel tapply dans unname:

 unname(tapply(df$values, df$ind, paste, collapse = " "))

qui donne:

[1] "TEXT TEXT TEXT TEXT TEXT TEXT TEXT TEXT TEXT one" 
[2] "TEXT TEXT TEXT two TEXT TEXT TEXT TEXT TEXT three"
[3] "TEXT TEXT TEXT four TEXT TEXT TEXT TEXT"

Si vous souhaitez utiliser les éléments de new une seule fois, vous pouvez mettre à jour le code pour:

newnew <- new[1:3]

ix <- df$values == "change"
df[ix, "values"][1:length(newnew)] <- newnew
unname(tapply(df$values, df$ind, paste, collapse = " "))

Vous pouvez modifier cela davantage pour prendre également en compte la situation où il y a plus de remplacements que de postes (occurrences du modèle, change dans l'exemple) qui doivent être remplacés:

newnew2 <- c(new, "five")

tl <- lapply(text, function(s) strsplit(s, split = " ")[[1]])
df <- stack(setNames(tl, seq_along(tl)))

ix <- df$values == "change"
df[ix, "values"][1:pmin(sum(ix),length(newnew2))] <- newnew2[1:pmin(sum(ix),length(newnew2))]
unname(tapply(df$values, df$ind, paste, collapse = " "))
1
Jaap