web-dev-qa-db-fra.com

Java regex: remplacer tous les caractères par `+` sauf les instances d'une chaîne donnée

J'ai le problème suivant qui déclare

Remplacez tous les caractères d'une chaîne par + symbole sauf les instances de la chaîne donnée dans la méthode

par exemple, si la chaîne donnée était abc123efg et ils veulent que je remplace chaque caractère sauf chaque occurrence de 123 alors il deviendrait +++123+++.

J'ai pensé qu'une expression régulière est probablement la meilleure pour cela et j'ai trouvé cela.

str.replaceAll("[^str]","+") 

où str est une variable, mais cela ne me permet pas d'utiliser la méthode sans la mettre entre guillemets. Si je veux juste remplacer la chaîne de variable str, comment faire? Je l'ai exécuté avec la chaîne tapée manuellement et cela a fonctionné sur la méthode, mais puis-je simplement entrer une variable?

en ce moment je crois que sa recherche de la chaîne "str" ​​et non de la chaîne variable.

Voici la sortie de son droit pour de nombreux cas, sauf pour deux :(

enter image description here

Liste des cas de test ouverts:

plusOut("12xy34", "xy") → "++xy++"
plusOut("12xy34", "1") → "1+++++"
plusOut("12xy34xyabcxy", "xy") → "++xy++xy+++xy"
plusOut("abXYabcXYZ", "ab") → "ab++ab++++"
plusOut("abXYabcXYZ", "abc") → "++++abc+++"
plusOut("abXYabcXYZ", "XY") → "++XY+++XY+"
plusOut("abXYxyzXYZ", "XYZ") → "+++++++XYZ"
plusOut("--++ab", "++") → "++++++"
plusOut("aaxxxxbb", "xx") → "++xxxx++"
plusOut("123123", "3") → "++3++3"
23
fsdff

Il semble que ce soit le problème de plusOut sur CodingBat.

J'ai eu 3 solutions à ce problème et j'ai écrit une nouvelle solution de streaming juste pour le plaisir.

Solution 1: boucle et vérification

Créez un StringBuilder à partir de la chaîne d'entrée et recherchez le mot à chaque position. Remplacez le caractère s'il ne correspond pas et ignorez la longueur du mot s'il est trouvé.

public String plusOut(String str, String Word) {
  StringBuilder out = new StringBuilder(str);

  for (int i = 0; i < out.length(); ) {
    if (!str.startsWith(Word, i))
      out.setCharAt(i++, '+');
    else
      i += Word.length();
  }

  return out.toString();
}

C'est probablement la réponse attendue pour un programmeur débutant, bien que l'on suppose que la chaîne ne contient aucun caractère de plan astral, qui serait représenté par 2 caractères au lieu de 1.

Solution 2: remplacez le mot par un marqueur, remplacez le reste, puis restaurez le mot

public String plusOut(String str, String Word) {
    return str.replaceAll(Java.util.regex.Pattern.quote(Word), "@").replaceAll("[^@]", "+").replaceAll("@", Word);
}

Pas une bonne solution car elle suppose qu'un certain caractère ou séquence de caractères n'apparaît pas dans la chaîne.

Notez l'utilisation de Pattern.quote Pour éviter que le Word soit interprété comme syntaxe regex par la méthode replaceAll.

Solution 3: Regex avec \G

public String plusOut(String str, String Word) {
  Word = Java.util.regex.Pattern.quote(Word);
  return str.replaceAll("\\G((?:" + Word + ")*+).", "$1+");
}

Construisez regex \G((?:Word)*+)., qui fait plus ou moins ce que fait la solution 1:

  • \G S'assure que le match commence là où le match précédent s'est arrêté
  • ((?:Word)*+) Sélectionne 0 ou plusieurs instances de Word - le cas échéant, afin que nous puissions les conserver dans le remplacement avec $1. La clé ici est le quantificateur possessif *+, Qui force l'expression régulière à conserver n'importe quelle instance du Word qu'il trouve. Sinon, l'expression régulière ne fonctionnera pas correctement lorsque le Word apparaîtra à la fin de la chaîne, car les expressions régulières reviennent sur .
  • . Ne fera partie d'aucun Word, car la partie précédente récupère déjà toutes les apparitions consécutives de Word et interdit la marche arrière. Nous allons remplacer cela par +

Solution 4: Streaming

public String plusOut(String str, String Word) {
  return String.join(Word, 
    Arrays.stream(str.split(Java.util.regex.Pattern.quote(Word), -1))
      .map((String s) -> s.replaceAll("(?s:.)", "+"))
      .collect(Collectors.toList()));
}

L'idée est de diviser la chaîne par Word, de faire le remplacement sur le reste et de les joindre à nouveau avec Word en utilisant la méthode String.join.

  • Comme ci-dessus, nous avons besoin de Pattern.quote Pour éviter que split interprète le Word comme une expression régulière. Puisque split supprime par défaut la chaîne vide à la fin du tableau, nous devons utiliser -1 Dans le deuxième paramètre pour que split laisse ces chaînes vides seules.
  • Ensuite, nous créons un flux à partir du tableau et remplaçons le reste sous forme de chaînes de +. Dans Java 11, nous pouvons utiliser s -> String.repeat(s.length()) à la place.
  • Le reste est juste en train de convertir le Stream en un Iterable (List dans ce cas) et de les rejoindre pour le résultat
14
nhahtdh

C'est un peu plus compliqué que vous ne le pensez au départ car vous n'avez pas seulement besoin de faire correspondre caractères , mais le absence de phrase spécifique - un jeu de caractères nié ne suffit pas. Si la chaîne est 123, vous aurez besoin de:

(?<=^|123)(?!123).*?(?=123|$)

https://regex101.com/r/EZWMqM/1/

Autrement dit, recherchez le début de la chaîne ou "123", assurez-vous que la position actuelle n'est pas suivie de 123, puis répétez paresseusement n'importe quel caractère jusqu'à ce que la recherche de correspondance corresponde à "123" ou à la fin de la chaîne. Cela correspondra à tous les caractères qui ne sont pas dans une sous-chaîne "123". Ensuite, vous devez remplacer chaque caractère par un +, après quoi vous pouvez utiliser appendReplacement et un StringBuffer pour créer la chaîne de résultat:

String inputPhrase = "123";
String inputStr = "abc123efg123123hij";
StringBuffer resultString = new StringBuffer();
Pattern regex = Pattern.compile("(?<=^|" + inputPhrase + ")(?!" + inputPhrase + ").*?(?=" + inputPhrase + "|$)");
Matcher m = regex.matcher(inputStr);
while (m.find()) {
    String replacement = m.group(0).replaceAll(".", "+");
    m.appendReplacement(resultString, replacement);
}
m.appendTail(resultString);
System.out.println(resultString.toString());

Production:

+++123+++123123+++

Notez que si le inputPhrase peut contenir un caractère avec une signification spéciale dans une expression régulière, vous devrez d'abord les échapper avant de concaténer dans le motif.

6
CertainPerformance

Vous pouvez le faire en une seule ligne:

input = input.replaceAll("((?:" + str + ")+)?(?!" + str + ").((?:" + str + ")+)?", "$1+$2");

Cela capture facultativement "123" de chaque côté de chaque caractère et les remet en place (un blanc s'il n'y a pas de "123"):

2
Bohemian

Pour que cela fonctionne, vous avez besoin d'une bête d'un modèle. Supposons que vous utilisiez le cas de test suivant à titre d'exemple:

plusOut("abXYxyzXYZ", "XYZ") → "+++++++XYZ"

Ce que vous devez faire est de créer une série de clauses dans votre modèle pour correspondre à un seul caractère à la fois:

  • Tout caractère qui n'est PAS "X", "Y" ou "Z" - [^XYZ]
  • Tout "X" non suivi de "YZ" - X(?!YZ)
  • Tout "Y" non précédé de "X" - (?<!X)Y
  • Tout "Y" non suivi de "Z" - Y(?!Z)
  • Tout "Z" non précédé de "XY" - (?<!XY)Z

Un exemple de ce remplacement peut être trouvé ici: https://regex101.com/r/jK5wU3/4

Voici un exemple de la façon dont cela pourrait fonctionner (certainement pas optimisé, mais cela fonctionne):

import Java.util.regex.Pattern;

public class Test {

    public static void plusOut(String text, String exclude) {

        StringBuilder pattern = new StringBuilder("");
        for (int i=0; i<exclude.length(); i++) {

            Character target    = exclude.charAt(i);
            String prefix       = (i > 0) ? exclude.substring(0, i) : "";
            String postfix      = (i < exclude.length() - 1) ? exclude.substring(i+1) : "";

            // add the look-behind (?<!X)Y
            if (!prefix.isEmpty()) {
                pattern.append("(?<!").append(Pattern.quote(prefix)).append(")")
                        .append(Pattern.quote(target.toString())).append("|");
            }

            // add the look-ahead X(?!YZ)
            if (!postfix.isEmpty()) {
                pattern.append(Pattern.quote(target.toString()))
                        .append("(?!").append(Pattern.quote(postfix)).append(")|");
            }

        }

        // add in the other character exclusion
        pattern.append("[^" + Pattern.quote(exclude) + "]");

        System.out.println(text.replaceAll(pattern.toString(), "+"));

    }

    public static void main(String  [] args) {

        plusOut("12xy34", "xy");
        plusOut("12xy34", "1");
        plusOut("12xy34xyabcxy", "xy");
        plusOut("abXYabcXYZ", "ab");
        plusOut("abXYabcXYZ", "abc");
        plusOut("abXYabcXYZ", "XY");
        plusOut("abXYxyzXYZ", "XYZ");
        plusOut("--++ab", "++");
        plusOut("aaxxxxbb", "xx");
        plusOut("123123", "3");

    }

}

MISE À JOUR: Même cela ne fonctionne pas tout à fait car il ne peut pas gérer les exclusions qui ne sont que des caractères répétés, comme "xx". Les expressions régulières ne sont certainement pas le bon outil pour cela, mais j'ai pensé que cela pourrait être possible. Après avoir fouillé, je ne suis pas sûr qu'un modèle existe même qui pourrait faire fonctionner cela.

1
Daedalus

Le problème dans votre solution que vous mettez un ensemble de chaîne d'instance str.replaceAll("[^str]","+") qui exclura tout caractère de la variable str et qui ne résoudra pas votre problème

[~ # ~] ex [~ # ~] : lorsque vous essayez str.replaceAll("[^XYZ]","+") il exclura toute combinaison de caractères X, caractère Y et caractère Z de votre méthode de remplacement pour obtenir "++XY+++XYZ".

En fait, vous devez exclure une séquence de caractères à la place dans str.replaceAll.

Vous pouvez le faire en utilisant un groupe de capture de caractères comme (XYZ) Puis utilisez un lookahead négatif pour faire correspondre une chaîne qui ne contient pas de séquence de caractères: ^((?!XYZ).)*$

Vérifiez ceci solution pour plus d'informations sur ce problème, mais vous devez savoir qu'il peut être compliqué de trouver une expression régulière pour le faire directement.

J'ai trouvé deux solutions simples à ce problème:

Solution 1 :

Vous pouvez implémenter une méthode pour remplacer tous les caractères par '+' À l'exception de l'instance de la chaîne donnée:

String exWord = "XYZ";
String str = "abXYxyzXYZ";

for(int i = 0; i < str.length(); i++){
    // exclude any instance string of exWord from replacing process in str
    if(str.substring(i, str.length()).indexOf(exWord) + i == i){
        i = i + exWord.length()-1;
    }
    else{
        str = str.substring(0,i) + "+" + str.substring(i+1);//replace each character with '+' symbol
    }
}             

Remarque : str.substring(i, str.length()).indexOf(exWord) + i cette instruction if exclura toute chaîne d'instance de exWord du remplacement du processus dans str.

Sortie :

+++++++XYZ

Solution 2 :

Vous pouvez essayer cette approche en utilisant la méthode ReplaceAll et elle ne nécessite aucune expression régulière complexe:

String exWord = "XYZ";
String str = "abXYxyzXYZ";

str = str.replaceAll(exWord,"*"); // replace instance string with * symbol
str = str.replaceAll("[^*]","+"); // replace all characters with + symbol except * 
str = str.replaceAll("\\*",exWord); // replace * symbol with instance string

Remarque : Cette solution ne fonctionnera que si votre chaîne d'entrée str ne contient aucun symbole *.

Vous devez également échapper tout caractère ayant une signification spéciale dans une expression régulière dans la chaîne d'instance de phrase exWord comme: exWord = "++".

1
Oghli

Absolument juste pour le plaisir, une solution utilisant CharBuffer (de manière inattendue, il en a fallu beaucoup plus que ce que j'espérais initialement):

private static String plusOutCharBuffer(String input, String match) {
    int size = match.length();
    CharBuffer cb = CharBuffer.wrap(input.toCharArray());
    CharBuffer Word = CharBuffer.wrap(match);

    int x = 0;
    for (; cb.remaining() > 0;) {
        if (!cb.subSequence(0, size < cb.remaining() ? size : cb.remaining()).equals(Word)) {
            cb.put(x, '+');
            cb.clear().position(++x);
        } else {
            cb.clear().position(x = x + size);
        }
    }

    return cb.clear().toString();
}
1
Eugene

Donc, au lieu de trouver une expression régulière qui correspond à l'absence de chaîne. Nous pourrions tout aussi bien faire correspondre la phrase sélectionnée et ajouter + le nombre de caractères ignorés.

StringBuilder sb = new StringBuilder();
Matcher m = Pattern.compile(Pattern.quote(str)).matcher(input);
while (m.find()) {
    for (int i = 0; i < m.start(); i++) sb.append('+');
    sb.append(str);
}
int remaining = input.length() - sb.length();
for (int i = 0; i < remaining; i++) {
    sb.append('+');
}
1
xiaofeng.li