Laden...

Regex Intervall validieren und parsen

Erstellt von Palladin007 vor 10 Jahren Letzter Beitrag vor 10 Jahren 2.682 Views
Palladin007 Themenstarter:in
2.078 Beiträge seit 2012
vor 10 Jahren
Regex Intervall validieren und parsen

Moin,

ich habe ein Intervall von zwei reellen Zahlen. Beide können sowohl negativ als auch positiv sein und beide können weitere Kommastellen haben.

Mein Anfang:

var asdf = new List<string>();

// Hinter den Klammern steht, was es bedeuten soll

asdf.Add("-123--321"); // -321 bis -123
asdf.Add("123--321"); // 123 bis -321
asdf.Add("-123-321"); // -123 bis 321
asdf.Add("-321"); // minimum 321
asdf.Add("123-"); // maximum 321

var pattern = @"^(-{0,1}[0-9]+\.{0,1}[0-9]*)-{0,1}(\1)$";

var regex = new Regex(pattern);

foreach (var item in asdf)
    Console.WriteLine(item + "\t" + regex.IsMatch(item));

Console.ReadKey(true);

Leider bekomme ich immer false zurück gegeben.
An dem Teil für den reelen Zahlenwert kann es nicht liegen, dafür hab ich den Teil:

"^-{0,1}[0-9]+\.{0,1}[0-9]*$"

funktioniert. Einziges Manko ist, dass ein Komma erlaubt ist, selbst wenn keine Kommastellen vorhanden sind. Gibt es dafür eine einfache Lösung?

Bei dem "Haupt"-Ziel weiß ich aber nicht, warum es nie funktioniert.
Selbst wenn es funktioniert, brauche ich aber zusätzlich auch noch die Werte aus den Gruppen, kann ich da überhaupt über den Index auf die Gruppe für die erste Zahl zugreifen, oder habe ich am Ende dann nur eine Gruppe, die beide Zahlen validiert und ich bekomme nur die Zweite?

Ich hoffe, ihr versteht, was ich meine.

Gruß

49.485 Beiträge seit 2005
vor 10 Jahren

Hallo Palladin007,

mit \1 matchst du auf exakt die gleiche Zeichenfolge, auf die erste Gruppe gematcht hat. Das ist nicht, was du willst. Der Teilpattern der ersten Gruppe muss auch am Ende so dastehen.

Auf eine Zahl mit optionalen Nachkommateil kannst du per \d+(.\d+)? matchen.

Statt x{0,1} solltest du einfach x? benutzen.

herbivore

16.807 Beiträge seit 2008
vor 10 Jahren

Aus Deiner Erklärung und Deinen Beispielen werd ich nicht schlau.
Willst Du reelle Zahlen, oder ganze Zahlen?

Du solltest Dir nochmal anschauen, wie Regex funktioniert.
-{0,1} drückt man mit -? deutlich besser aus.

Alles fängst Du mit einem einzigen Pattern nicht ab - oder aber es wird undurchsichtig.
Im Endeffekt würd ich aufgrund der Wartbarkeit alle 3 Fälle (Range, Von, Bis) einzeln abdecken; gerade wenn Du Regex nicht wirklich verstehst.
Da Du nach dem Komma fragst bestätigt das meine Vermutung 😉

Tipp: mit ^(-?[0-9]+)-(-?[0-9]+)$deckst Du wenigstens den ersten Fall ab (ungetestet).

Schau Dir die Grundlagen von Regex an. Die fehlen hier offensichtlich.
[Artikel] Regex-Tutorial

S
417 Beiträge seit 2008
vor 10 Jahren

Selbst wenn es funktioniert, brauche ich aber zusätzlich auch noch die Werte aus den Gruppen, kann ich da überhaupt über den Index auf die Gruppe für die erste Zahl zugreifen, oder habe ich am Ende dann nur eine Gruppe, die beide Zahlen validiert und ich bekomme nur die Zweite?

Du kannst auch benannte Gruppen verwenden:

var pattern = @"^(?<min>(-?\d+(\.\d+)?)?)\-(?<max>(-?\d+(\.\d+)?)?)$";
var regex = new Regex(pattern);
Func<string, double?> toDoubleOrNull = f => string.IsNullOrEmpty(f) ? (double?)null : Convert.ToDouble(f);

foreach (var item in asdf)
{
	var m = regex.Match(item);

	if (m.Success)
	{
		var min = toDoubleOrNull(m.Groups["min"].Value);
		var max = toDoubleOrNull(m.Groups["max"].Value);
	}
}
Palladin007 Themenstarter:in
2.078 Beiträge seit 2012
vor 10 Jahren

Das Tutorial kenne ich bereits und ich habe es auch immer griffbereit, wenn es an Regex geht, doch mittlwerweile weiß ich die einen oder anderen Sachen und schau daher auch immer weniger da rein.
Da vergesse ich dann manche Vereinfachungen.

Aus Deiner Erklärung und Deinen Beispielen werd ich nicht schlau.
Willst Du reelle Zahlen, oder ganze Zahlen?

Ich möchte ein Intervall aus einem String parsen, was entweder den klasseischen Bereich (x bis y) darstellen kann, einen Minimalwert (x bis, ohne y), einen Maximalwert (bis y, ohne x) und einen festen Wert (x bis x).

All das kann rein theoretisch bei einem Intervall eingegeben werden und sinnvoll aussehen.
Den letzten Fall würde ich aber gerne vereinfachen, es soll also nicht mehr x bis x eingegeben werden müssen, sondern nur noch x.

Für x oder y kann jede Zahl eingegeben werden, sowohl negativ, als auch positiv, mit Kommastellen oder ohne.

Diese vier Fälle habe ich oben aufgeschrieben, um sie zu testen.

Jetzt funktioniert es auch, ich glaube, der Inweis von herbivore, dass das Teilpattern für die zweite Zahl auch am Ende stehen muss, war der gröbste Fehler am Anfang.

Ich habe jetzt testweise eine kleine Klasse geschrieben:

class Interval
{
    public static Interval ParseInterval(string input)
    {
        var numberPattern = @"-?\d+(\,\d+)?";
        var intervalPattern = string.Format(@"^\ *(?<min>{0})?\ *-\ *(?<max>{0})?\ *$", numberPattern);

        var regex = new Regex(intervalPattern);

        Func<string, decimal?> toDecimalOrNull = f => string.IsNullOrEmpty(f) ? (decimal?)null : Convert.ToDecimal(f);

        var match = regex.Match(input);

        if (!match.Success)
            throw new ArgumentException();

        var min = toDecimalOrNull(match.Groups["min"].Value);
        var max = toDecimalOrNull(match.Groups["max"].Value);

        return new Interval(min, max);
    }

    private readonly decimal? _min = null;
    private readonly decimal? _max = null;

    public decimal? Min
    {
        get { return _min; }
    }
    public decimal? Max
    {
        get { return _max; }
    }

    private Interval(decimal? min, decimal? max)
    {
        _min = min;
        _max = max;
    }

    public override string ToString()
    {
        var result = "";

        if (Min == null && Max == null)
            result = "Kein Wert";
        else if (Min == null)
            result = "maximal " + Max.Value.ToString();
        else if (Max == null)
            result = "mindestens " + Min.Value.ToString();
        else
            result = Min.Value.ToString() + " bis " + Max.Value.ToString();

        return result;
    }
}

Getestet so:

var asdf = new List<string>();

asdf.Add("-123,376--321,547"); // -123,376 bis -321,547
asdf.Add("123--321,356656"); // 123 bis -321,356656
asdf.Add("-123,6576-321"); // -123,6576 bis 321
asdf.Add("--321,356"); // maximum -321,356
asdf.Add("123,64545-"); // minimum 123,64545
asdf.Add("-"); // kein Wert

foreach (var item in asdf)
    Console.WriteLine("{0,20} ˆ {1}\r\n", item, Interval.ParseInterval(item).ToString());

Console.ReadKey(true);

Danke für die gute Hilfe, ich hab auch das Gefühl, dass ich bei Regex jetzt ein ganzes Stück weiter bin. ^^

16.807 Beiträge seit 2008
vor 10 Jahren

Vermeide Convert-Methoden und nimm Parse-Methoden + eine neutrale Culture.
Ansonsten kann/wird Dir das beim Umstellen der Systemsprache um die Ohren fliegen.

Palladin007 Themenstarter:in
2.078 Beiträge seit 2012
vor 10 Jahren

Ich hatte vorher die Parse-Methode, allerdings hat die gekonnt die Kommata ignoriert, was aber an der Culture gelegen haben kann, da hab ich nicht dran gedacht.

Danke für den Hinweis ^^

49.485 Beiträge seit 2005
vor 10 Jahren

Hallo Palladin007,

wie willst du unterscheiden, ob -321 ein Maximum von 321 (also y ohne x) oder ein Minimum von -321 (also x ohne y) ist?

EDIT: Ah, ich jetzt verstehe ich es wohl. Für ein x ohne y müsste man wohl -321- schreiben. Dann sind aber die Kommentare in deinen Beispielen nicht korrekt:

asdf.Add("-321"); // minimum 321  
asdf.Add("123-"); // maximum 321  

herbivore