2009-11-16 9 views
2

J'ai quelques chaînes, saisies par les utilisateurs, qui peuvent ressembler à ceci:Comment diviser cette chaîne

  1. ++ 7
    • 7 ++
    • 1 ++ 7
    • 1 + 7
    • 1 ++ 7 ++ 10 + 15 + 20 + 30 ++

Ce sont à dire:

  1. Tout jusqu'au 7
    • Tout de 7 ans et plus
    • 1 et 7 et tout inbetween
    • 1 et 7 seulement
    • 1 à 7, 10 à 15, 20 et 30 et au-dessus

J'ai besoin d'analyser ces chaînes dans les plages réelles. C'est à dire que j'ai besoin de créer une liste d'objets de type Range qui ont un début et une fin. Pour les items simples, je mets simplement le début et la fin à la même chose, et pour ceux qui sont au-dessus ou au-dessous, je mets le début ou la fin à zéro. Par exemple, pour le premier, j'aurais une plage qui a été définie sur null et la fin définie sur 7.

J'ai actuellement une sorte de méthode désordonnée utilisant une expression régulière pour faire cette division et analyse et je veux simplifier il. Mon problème est que je dois diviser sur + d'abord, puis sur ++. Mais si je divise le + en premier, alors les instances ++ sont ruinées et je finis avec un désordre.

En regardant ces chaînes, il devrait être vraiment facile de les analyser, je ne peux pas trouver une façon intelligente de le faire. Il doit juste être un moyen plus facile (plus propre, plus facile à lire). Impliquant probablement un concept facile que je viens pas entendu parler avant: P


L'expression régulière ressemble à ceci:

private readonly Regex Pattern = new Regex(@" ([+]{2,})? 
      ([^+]+) 
      (?: 
      (?: [+]{2,} [^+]*)* 
      [+]{2,} ([^+]+) 
     )? 
     ([+]{2,})? ", RegexOptions.IgnorePatternWhitespace); 

qui est ensuite utilisé comme ceci:

public IEnumerable<Range<T>> Parse(string subject, TryParseDelegate<string, T> itemParser) 
{ 
    if (string.IsNullOrEmpty(subject)) 
     yield break; 


    for (var item = RangeStringConstants.Items.Match(subject); item.Success; item = item.NextMatch()) 
    { 
     var startIsOpen = item.Groups[1].Success; 
     var endIsOpen = item.Groups[4].Success; 
     var startItem = item.Groups[2].Value; 
     var endItem = item.Groups[3].Value; 

     if (endItem == string.Empty) 
      endItem = startItem; 

     T start, end; 

     if (!itemParser(startItem, out start) || !itemParser(endItem, out end)) 
      continue; 

     yield return Range.Create(startIsOpen ? default(T) : start, 
            endIsOpen ? default(T) : end); 
    } 
} 

Cela fonctionne, mais je ne pense pas que ce soit particulièrement lisible ou maintenable. Par exemple changer les '+' et '++' en ',' et '-' ne serait pas si trivial à faire.

+2

avec ',' et '-' vous diviser par', ', puis par' -' (en supposant pas de chiffres négatifs). Comme vous l'avez dit, un simple 'Replace (" ++ "," - ")' rendra le code trivial, même si c'est un peu hacky. – Kobi

+3

Donc 9 +++ 7 (de 9 et plus et 7) est invalide? –

+0

@Bart: Eh bien, le code que j'ai maintenant permettrait en fait n'importe quel nombre de ++ tant qu'il y en a 2 ou plus. @Kobi: Cela fonctionnerait avec les nombres, mais cela doit fonctionner pour "n'importe quoi". Plus important, cela doit fonctionner pour les dates et pour les chaînes qui peuvent inclure, et -. Donc je suppose que c'est pourquoi ils ont choisi d'utiliser + et ++ ici. – Svish

Répondre

4

Mon problème est que j'ai besoin de diviser sur + d'abord, puis sur ++. Mais si je divise le + en premier, alors les instances ++ sont ruinées et je finis avec un désordre.

Vous pouvez partager sur ce regex premier:

(?<!\+)\+(?!\+) 

De cette façon, seuls les 'simples' + 's sont en cours divisés sur, vous laissant pour analyser les ++' s.Notez que je suppose qu'il ne peut y avoir trois + successifs. La regex ci-dessus simple dit: "split sur le '+' seulement s'il n'y a pas de '+' en avant ou en arrière".

Edit:

Après avoir lu qu'il peut y avoir plus de 2 s + successives de », je vous conseille d'écrire une petite grammaire et de laisser un analyseur-générateur créer un lexer + analyseur pour votre petite langue. ANTLR peut également générer du code source C#.

Edit 2:

Mais avant la mise en œuvre que vous auriez d'abord une solution (analyseur ou regex) doivent définir ce est et ce n'est pas entrée valide. Si vous allez laisser plus de deux + successifs être valide, à savoir. 1+++++5, qui est [1++, +, ++5], j'écrirais un peu de grammaire. Voir ce tutoriel comment cela fonctionne: http://www.antlr.org/wiki/display/ANTLR3/Quick+Starter+on+Parser+Grammars+-+No+Past+Experience+Required

Et si vous rejetez l'entrée de plus de 2 + successifs, vous pouvez utiliser Lasse ou my (first) regex-suggestion.

+0

J'ai exactement la même regex sur mon presse-papiers ... +1. – Kobi

+0

Nice. Je vais essayer de lire sur ces choses :) Merci! – Svish

4

Voici du code qui utilise des expressions régulières.

Notez que le problème soulevé par Bart dans les commentaires à votre question, à savoir. "Comment gérez-vous 1 +++ 5", n'est pas manipulé du tout.

Pour corriger cela, à moins que votre code est déjà dans la nature et non sujets à changement de comportement, je vous suggère de changer votre syntaxe à ce qui suit:

  • utilisation .. pour désigner les gammes
  • permettent à la fois + et - pour des chiffres, des numéros positifs et négatifs
  • virgule utilisation et/ou virgule pour séparer les plages de nombres distincts ou
  • permettent whitespace

regard sur la différence entre les deux chaînes suivantes:

  • 1 ++ 7 ++ 10 + 15 + 20 + 30 ++
  • 1..7, 10..15, 20, 30

La deuxième chaîne est beaucoup plus facile à analyser et beaucoup plus facile à lire.

Il supprimerait aussi toute ambiguïté:

  • 1 +++ 5 = 1 ++ + 5 = 1 .., 5
  • 1 +++ 5 = 1 + ++ 5 = 1 , ..5

Il est impossible d'analyser la deuxième syntaxe de manière incorrecte.


Quoi qu'il en soit, voici mon code. Fondamentalement, il fonctionne en ajoutant quatre modèles regex pour les quatre types de modèles:

  • num
  • num ++
  • ++ num num
  • ++ num

Pour "num", il traitera les nombres négatifs avec un signe moins et un ou plusieurs chiffres. Il ne gère pas, pour des raisons évidentes, le signe plus comme faisant partie du nombre.

Je l'ai interprété « et jusqu'à » signifie « jusqu'à Int32.MaxValue » et même pour descendre à Int32.MinValue.

public class Range 
{ 
    public readonly Int32 From; 
    public readonly Int32 To; 

    public Range(Int32 from, Int32 to) 
    { 
     From = from; 
     To = to; 
    } 

    public override string ToString() 
    { 
     if (From == To) 
      return From.ToString(); 
     else if (From == Int32.MinValue) 
      return String.Format("++{0}", To); 
     else if (To == Int32.MaxValue) 
      return String.Format("{0}++", From); 
     else 
      return String.Format("{0}++{1}", From, To); 
    } 
} 

public static class RangeSplitter 
{ 
    public static Range[] Split(String s) 
    { 
     if (s == null) 
      throw new ArgumentNullException("s"); 

     String[] parts = new Regex(@"(?<!\+)\+(?!\+)").Split(s); 
     List<Range> result = new List<Range>(); 

     var patterns = new Dictionary<Regex, Action<Int32[]>>(); 

     patterns.Add(new Regex(@"^(-?\d+)$"), 
      values => result.Add(new Range(values[0], values[0]))); 
     patterns.Add(new Regex(@"^(-?\d+)\+\+$"), 
      values => result.Add(new Range(values[0], Int32.MaxValue))); 
     patterns.Add(new Regex(@"^\+\+(-?\d+)$"), 
      values => result.Add(new Range(Int32.MinValue, values[0]))); 
     patterns.Add(new Regex(@"^(-?\d+)\+\+(-?\d+)$"), 
      values => result.Add(new Range(values[0], values[1]))); 

     foreach (String part in parts) 
     { 
      foreach (var kvp in patterns) 
      { 
       Match ma = kvp.Key.Match(part); 
       if (ma.Success) 
       { 
        Int32[] values = ma.Groups 
         .OfType<Group>() 
         .Skip(1) // group 0 is the entire match 
         .Select(g => Int32.Parse(g.Value)) 
         .ToArray(); 
        kvp.Value(values); 
       } 
      } 
     } 

     return result.ToArray(); 
    } 
} 

tests unitaires:-

[TestFixture] 
public class RangeSplitterTests 
{ 
    [Test] 
    public void Split_NullString_ThrowsArgumentNullException() 
    { 
     Assert.Throws<ArgumentNullException>(() => 
     { 
      var result = RangeSplitter.Split(null); 
     }); 
    } 

    [Test] 
    public void Split_EmptyString_ReturnsEmptyArray() 
    { 
     Range[] result = RangeSplitter.Split(String.Empty); 
     Assert.That(result.Length, Is.EqualTo(0)); 
    } 

    [TestCase(01, "++7", Int32.MinValue, 7)] 
    [TestCase(02, "7", 7, 7)] 
    [TestCase(03, "7++", 7, Int32.MaxValue)] 
    [TestCase(04, "1++7", 1, 7)] 
    public void Split_SinglePatterns_ProducesExpectedRangeBounds(
     Int32 testIndex, String input, Int32 expectedLower, 
     Int32 expectedUpper) 
    { 
     Range[] result = RangeSplitter.Split(input); 
     Assert.That(result.Length, Is.EqualTo(1)); 
     Assert.That(result[0].From, Is.EqualTo(expectedLower)); 
     Assert.That(result[0].To, Is.EqualTo(expectedUpper)); 
    } 

    [TestCase(01, "++7")] 
    [TestCase(02, "7++")] 
    [TestCase(03, "1++7")] 
    [TestCase(04, "1+7")] 
    [TestCase(05, "1++7+10++15+20+30++")] 
    public void Split_ExamplesFromQuestion_ProducesCorrectResults(
     Int32 testIndex, String input) 
    { 
     Range[] ranges = RangeSplitter.Split(input); 
     String rangesAsString = String.Join("+", 
      ranges.Select(r => r.ToString()).ToArray()); 

     Assert.That(rangesAsString, Is.EqualTo(input)); 
    } 

    [TestCase(01, 10, 10, "10")] 
    [TestCase(02, 1, 10, "1++10")] 
    [TestCase(03, Int32.MinValue, 10, "++10")] 
    [TestCase(04, 10, Int32.MaxValue, "10++")] 
    public void RangeToString_Patterns_ProducesCorrectResults(
     Int32 testIndex, Int32 lower, Int32 upper, String expected) 
    { 
     Range range = new Range(lower, upper); 
     Assert.That(range.ToString(), Is.EqualTo(expected)); 
    } 
} 
+0

D'abord, très impressionnant. Mais - j'ai déjà suggéré de remplacer les caractères (d'une certaine manière), et Svish a dit qu'il pourrait y avoir des dates et un mystérieux "Tout", pas seulement des chiffres. – Kobi

+0

Woah. Je vais devoir passer par là, hehe. Des choses intéressantes Je ne sais pas si je comprends bien ce qui se passe avec ces modèles et le dictionnaire ... mais je vais jeter un oeil: p (façon intéressante d'écrire ces tests aussi.) Peut-être que je vais réécrire le mien. TestCase comme ça, à quoi sert le testIndex? Jamais vu ça avant ...) – Svish

+0

Et, oh, ouais, ce mystérieux "Anything" n'est pas vraiment si mystérieux. Mais il peut à peu près tout contenir. Les exemples sont les nombres, les dates, les noms, les identifiants de produits, et cetera. Aucune idée si elles ont une règle empêchant les gens d'entrer un + dans l'un des noms par exemple. Mais ouais ... Pas quelque chose que j'ai décidé. Je dois juste travailler avec ça. C'est un de ceux-ci: "Ça a toujours été comme ça, donc on ne peut pas le changer" -les choses. Je voudrais le rendre plus restrictif et mieux défini ... – Svish

Questions connexes