0
votes

Trouvez un modèle pour résoudre les regex en une seule étape

J'ai un problème pour trouver le modèle qui résout le problème en une seule étape. La chaîne ressemble à ceci:

Text1$Text2$Text3$Text4$56

Ce que je veux, c'est: prendre jusqu'à 4x texte. S'il y a plus de "4xText", ne prenez que le dernier signe.

Exemple:

([^\$])\b

Ma solution actuelle est:

Premier motif:

^([^\$]*)\$?([^\$]*)\$?([^\$]*)\$?([^\$]*)\$?

Après cela, je vais faire une substitution avec le premier modèle Nouvelle chaîne: Text5$Text6

le deuxième modèle est:

Text1$Text2$Text3$Text4$Text5$Text6 -> Text1$Text2$Text3$Text4&56

Résultat: 56

combinez les deux et obtenez le résultat:

Text1
Text1$Text2$Text3
Text1$Text2$Text3$Text4$Text5$Text6 etc.

Pour moi, on ne sait pas pourquoi je ne peux pas facilement mettre le deuxième motif après le premier motif en un seul motif. Y a-t-il quelque chose comme une ancre qui dit au moteur de démarrer le modèle à partir d'ici comme il le ferait si c'était le seul modèle?


5 commentaires

Cher Abra, j'utilise C #


Les expressions régulières permettent uniquement la sélection de texte contigu. Votre langue hôte peut offrir un moyen d'extraire des sous-chaînes en capturant des parenthèses, etc.


Est-ce que regex est réellement une exigence? Il existe de nombreuses façons simples de le faire


ok merci, donc ma solution n'est pas si mal? Aucun Regex ne serait pas une exigence. Mais j'aime ça. Que préféreriez-vous ?


en utilisant C #, il miserait sur un simple split sur $ puis prendrait les 4 en premier, puis prendrait le reste, ...


4 Réponses :


3
votes

Vous pouvez utiliser une alternative avec un regard positif en arrière, puis concaténer les matchs.

Text1
Text1$Text2$Text3
Text1$Text2$Text3$Text4$56

Explication

  • (?<= Regard positif derrière, affirmer que ce qui est à gauche est
    • ^(?:[^$]+\$){0,3} Correspond à 0 à 3 fois n'importe quel caractère sauf $ suivi d'un $ facultatif
  • ) Fermer regard en arrière
  • [^$]+\$? Correspond à 1+ fois n'importe quel caractère sauf $ , puis correspond à un $ facultatif
  • | Ou
  • [^$] Correspond à n'importe quel caractère sauf $
  • (?=\$|$) Recherche positive, affirmer que ce qui est directement à droite est $ ou la fin de la chaîne

Démo .NET regex | Démo C #

Exemple

string pattern = @"(?<=^(?:[^$]*\$){0,3})[^$]*\$?|[^$](?=\$|$)";
string[] strings = { 
    "Text1",
    "Text1$Text2$Text3",
    "Text1$Text2$Text3$Text4$Text5$Text6"
};            

Regex regex = new Regex(pattern);

foreach (String s in strings) {
    Console.WriteLine(string.Join("", from Match match in regex.Matches(s) select match.Value));
}

Production

(?<=^(?:[^$]+\$){0,3})[^$]+\$?|[^$](?=\$|$)


0 commentaires

2
votes

Je crois fermement que l'expression régulière n'est pas le moyen de le faire. Principalement à cause de la lisibilité.

Vous pouvez envisager d'utiliser un algorithme simple comme celui-ci pour atteindre votre objectif:

using System;
using System.Linq;

public class Program
{
    public static void Main()
    {
        var input = "Text1$Text2$Text3$Text4$Text5$Text6";
        var parts = input.Split('$');
                
        var first4 = parts.Take(4);
        var remainings = parts.Skip(4);
        
        var result2 = string.Join("$", first4) + "$" +  string.Join("", remainings.Select( r=>r.Substring(4)));
        
        Console.WriteLine(result2);
    }
}

Il existe également des alternatives linq:

using System;

public class Program
{
    public static void Main()
    {
        var input = "Text1$Text2$Text3$Text4$Text5$Text6";
        var parts = input.Split('$');
        
        var result = "";
        for(var i=0; i<parts.Length; i++){
            result += (i <= 4 ? parts[i] + "$" : parts[i].Substring(4));            
        }
        Console.WriteLine(result);
    }
}

Il faut l'ajuster aux besoins réels mais l'idée est là


0 commentaires

0
votes

Je ne sais pas si votre objectif peut être atteint en utilisant exclusivement des regex. Si rien d'autre, le fait que vous vouliez introduire un nouveau caractère '&' dans la sortie ajoute au défi, car une simple correspondance ne serait jamais en mesure d'accomplir cela. Peut-être en utilisant la méthode Replace() ? Je ne suis pas sûr que cela fonctionnerait cependant ... en utilisant uniquement un modèle de remplacement et non un MatchEvaluator , je ne vois pas de moyen de reconnaître mais d'exclure toujours la partie "$Text" de la cinquième instance et des MatchEvaluator ultérieures.

Mais, si vous êtes prêt à mélanger les regex avec une petite quantité de post-traitement, vous pouvez certainement le faire:

static readonly Regex regex1 = new Regex(@"(Text\d(?:\$Text\d){0,3})(?:\$Text(\d))*", RegexOptions.Compiled);

static void Main(string[] args)
{
    for (int i = 1; i <= 6; i++)
    {
        string text = string.Join("$", Enumerable.Range(1, i).Select(j => $"Text{j}"));

        WriteLine(KeepFour(text));
    }
}

private static string KeepFour(string text)
{
    Match match = regex1.Match(text);

    if (!match.Success)
    {
        return "[NO MATCH]";
    }

    StringBuilder result = new StringBuilder();

    result.Append(match.Groups[1].Value);

    if (match.Groups[2].Captures.Count > 0)
    {
        result.Append("&");
        // Have to iterate (join), because we don't want the whole match,
        // just the captured text.
        result.Append(JoinCaptures(match.Groups[2]));
    }

    return result.ToString();
}

private static string JoinCaptures(Group group)
{
    return string.Join("", group.Captures.Cast<Capture>().Select(c => c.Value));
}

Ce qui précède divise votre exigence en trois groupes de capture différents dans une expression régulière. Ensuite, il extrait le texte capturé, composant le résultat en fonction des résultats.


0 commentaires

1
votes

Essayez ce code:

(match) => match.Groups[1].Value +"$"+ match.Groups[2].Value.Replace("Text", "").Replace("$", "")

Explication:

La solution utilise un modèle d'expression régulière: (Text\d{1,3}(?:\$Text\d{1,3}){0,3})((?:\$Text\d{1,3})*)

(...) - premier groupe de capture

(?:...) - groupe non capturant

Text\d{1,3}(?:\$Text\d{1,3} - correspond littéralement au Text , puis correspond à \d{1,3} , qui comprend de 1 à trois chiffres, \$ correspond littéralement à $

Le repos n'est que la répétition. Fondamentalement, le premier groupe capture les quatre premières pièces, le deuxième groupe capture le reste, le cas échéant.

Nous utilisons également MatchEvaluator ici qui est le type de délégué défini comme:

public delegate string MatchEvaluator(Match match);

Nous définissons une telle méthode:

var texts = new string[] {"Text1", "Text1$Text2$Text3", "Text1$Text2$Text3$Text4$Text5$Text6" };

var parsed = texts
     .Select(s => Regex.Replace(s, 
         @"(Text\d{1,3}(?:\$Text\d{1,3}){0,3})((?:\$Text\d{1,3})*)", 
         (match) => match.Groups[1].Value +"$"+ match.Groups[2].Value.Replace("Text", "").Replace("$", "")
     )).ToArray();

// parsed is now: string[3] { "Text1$", "Text1$Text2$Text3$", "Text1$Text2$Text3$Text4$56" }

Nous l'utilisons pour évaluer la correspondance, alors prenez le premier groupe de capture et concaténez avec le second, en supprimant le texte inutile.


0 commentaires