Parser mit Klammern

Peter86

Erfahrenes Mitglied
Hallo,
ich brauche einen Parser weiß aber nicht ganz recht wie ich das anstellen soll, unzwar folgendes:

Code:
(values (value1 50) (value2 0) (value3 6) (value4 80) 
(value5 70) (value6 10 20 30 40))

Ich möchte die werte folgendermaßen abrufen, z.b.
Code:
Value("value1").Item(0)
was ja über ein dictionary möglich ist, als wert müsste ich
Code:
50
erhalten. Mach ihn nun
Code:
Value("value6").Item(2)
müsste ich als wert
Code:
30
erhalten. Ich hoffe ihr versteht wie ich das meine.

Ich hoffe ihr könnt mir weiterhelfen, das ding muss auch über mehrere zeilen arbeiten können und die anzahl von values ist dynamisch. :confused:

Peter86
 
Zuletzt bearbeitet:
Mir ist nicht ganz klar was du mit "das ding muss auch über mehrere zeilen arbeiten können und die anzahl von values ist dynamisch." meinst, aber vielleicht hilft dir der Denkanstoss ja weiter:

Code:
  Dictionary<string, List<int>> values = new Dictionary<string, List<int>>();

            List<int> value1List = new List<int>(new int[] { 50 });
            //...
            List<int> value6List = new List<int>(new int[]{10, 20, 30, 40});

            values.Add("value1", value1List);
            values.Add("value6", value6List);


            int v1Item0 = values["value1"][0];
            int v6Item2 = values["value6"][2];
 
Ich glaube er will einen Parser schreiben, der einen String in ein Dictionary verwandelt. Mir ist noch nicht ganz klar, wie der String aufgebaut ist, aber:
@Peter86:
Kennst du die IndexOf-Methode und Substring-Methode der String-Klasse? Damit kannst du nach öffnenden und schließenden Klammern suchen und dann den String so weit lesen, bis eine Ziffer kommt.
 
Hi.

Regex böte sich auch an um an die Daten zu kommen.

Das folgende Beispiel prüft zuerst ob der gesamte Text dem Muster entspricht, und holt sich dann die Werte raus. Deswegen sind 2 reguläre Ausdrücke..

Mit meinen Regexes hab ich mich an dein Beispiel gehalten. Das gilt vorallem für "values1", "values2" etc.
"values" kann zwra anders lauten, aber die Zahl dahinter muss sein, ansonsten findet keine übereinstimmung statt.

C#:
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;


class Program
{
	static void Main(string[] args)
	{
		var result = Parse(
			"(values (value1 50) (value2 0) (value3 6) (value4 80) " +
			"(value5 70) (value6 10 20 30 40))");

		foreach (string key in result.Keys)
		{
			Console.WriteLine("Name: {0}", key);
			for (int i = 0; i < result[key].Length; i++)
				Console.Write("{0}, ", result[key][i]);
			Console.WriteLine();
		}

		Console.ReadLine();
	}

	private static Regex validateRegex = new Regex(
			@"^\(values\ (?<valgroup>\((?<name>[a-zA-Z]+?\d+?\ )(?<values>(\d+\ )*)+?\)\ ?)+?\)$",
			RegexOptions.ExplicitCapture | RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline);

	private static Regex matchRegex = new Regex(
			@"(?<valgroup>\((?<name>[a-zA-Z]+?\d+?\ )(?<values>(\d+\ )*)+?\)\ ?)+?",
			RegexOptions.ExplicitCapture | RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline);

	private static Dictionary<string, int[]> Parse(string text)
	{
		var result = new Dictionary<string, int[]>();

		if (!validateRegex.IsMatch(text))
			return result;

		foreach (Match m in matchRegex.Matches(text))
		{
			if (!m.Success)
				continue;

			string name = m.Groups["name"].Value;
			string[] stringvalues = m.Groups["values"].Value.Split(' ');

			int[] values = new int[stringvalues.Length];
			for (int i = 0; i < stringvalues.Length; i++)
				values[i] = int.Parse(stringvalues[i]);

			result.Add(name, values);
		}

		return result;
	}
}

lg,..
 
Die sache liegt darin, der Parser soll nicht beschrängt sein sondern alles was in 2 klammern () ist herrausfiltern.
Um an die Klammern zu kommen hab ich schonmal was zusammen gewürfelt, Code ist zwar nicht so der Burner, aber es tut seinen dienst, vielleicht weiß ja wer wie mans besser macht. :)

Code:
Module Module1
    Sub main()
        Dim Line As String = "values (value1 50) (value2 0) (value3 6) (value4 80) " & _
        "(value5 70) (value6 10 20 30 40)"


        Dim test As Dictionary(Of String, String()) = ParseLine(Line)

        For Each value In test
            Dim sb As New System.Text.StringBuilder
            For Each V In value.Value
                sb.Append(" " & V)
            Next

            Console.WriteLine("Beschreibung: {0} | Werte: {1}", value.Key, sb.ToString)
        Next
        Console.ReadLine()
    End Sub

    Function ParseLine(ByVal Line As String)
        Dim test As New List(Of Integer)
        Dim test1 As New List(Of Integer)
        Dim test2 As New List(Of Integer())

        For i = 0 To Line.Length - 1
            Select Case Line(i)
                Case "("
                    test.Add(i)
                Case ")"
                    test1.Add(i)
            End Select
        Next
        For i = 0 To test.Count - 1
            test2.Add(New Integer() {test.Item(i), test1.Item(i)})
        Next

        Dim test3 As New Dictionary(Of String, String())
        For Each c In test2
            Dim s() As String = Line.Substring(c(0), c(1) - c(0) + 1).Replace("(", "").Replace(")", "").Split(" ")
            Dim werte(UBound(s) - 1) As String
            For i = 0 To UBound(s) - 1

                werte(i) = New String(s(i + 1))

            Next
            test3.Add(s(0), werte)
        Next

        Return test3
    End Function

End Module

Ich habe bei text die ( ganz vorne und die ) ganz hinten gelöscht weils sonst nicht richtig funktioniert hätte, normale müsste text so aussehen:
Dim text As String = "(values (value1 50) (value2 0) (value3 6) (value4 80) " & _
"(value5 70) (value6 10 20 30 40))"



Ich hab eine txt-Datei voller solcher zeilen wie oben, und die will ich komplett einlesen, manchmal gehören mehrere Zeilen, welche das sind sieht man wenn ganz am Anfang ein ( und ganz am ende ein ) ist und mein problem liegt jetzt darin rauszufinden welche Zeilen zusammen gehören, und dann jede zeile so einlesen wie der code es oben tut.
 
Zuletzt bearbeitet:
Mhmm.. nungut..

Oder hol dir zuerst aus deinem Text die Tokens raus, und die gehst du dann nach einander durch, merkst dir wo du gerade bist (Klammern zählen ^^), und behandelst die Daten dann entsprechend.

Hier (wieder) ein C# Beispiel:

C#:
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;


class Program
{
	static void Main(string[] args)
	{
		string text =
			"(values (value1 50) (value2 0) (value3 6) (value4 80) " +
			"(value5 70) (value6 10 20 30 40))";


		var result = Parse(text);

		foreach (string key in result.Keys)
		{
			Console.WriteLine("Name: {0}", key);
			for (int i = 0; i < result[key].Length; i++)
				Console.Write("{0}, ", result[key][i]);
			Console.WriteLine();
		}

		Console.ReadLine();

	}

	private static Dictionary<string, int[]> Parse(string text)
	{
		var data = new Dictionary<string, int[]>();
		
		bool outer = false;
		bool inner = false;
		bool first = false;

		string key = null;
		List<string> values = null;

		foreach (string token in Tokenize(text))
		{
			switch (token)
			{
				case "(":
					if (outer)
					{
						inner = true;
						first = true;
						values = new List<string>();
					}
					else
						outer = true;
					break;
				case ")":
					if (inner)
					{
						inner = false;
						int[] vals = new int[values.Count];
						int t;
						for (int i = 0; i < vals.Length; i++)
						{
							if (int.TryParse(values[i], out t))
								vals[i] = t;
							else
								throw new Exception("Token not a number");
						}
						data.Add(key, vals);
					}
					else if (outer)
						outer = false;
					else
						throw new Exception("Unexpected token ')'");
					break;
				default:
					if (!inner)
						break;
					if (first)
					{
						key = token;
						first = false;
					}
					else
					{
						values.Add(token);
					}

					break;
			}
		}

		return data;
	}

	private static IEnumerable<string> Tokenize(string text)
	{
		string token = "";

		foreach (char c in text)
		{
			switch (c)
			{
				case ' ':
				case '\n':
				case '\r':
					if (token.Length > 0)
						yield return token;
					token = "";
					break;
				case '(':
				case ')':
					if (token.Length > 0)
						yield return token;
					yield return c.ToString();
					token = "";
					break;
				default:
					token += c;
					break;
			}
		}
	}
}
 
Danke, das funktioniert soweit ganz gut bis auf das ich kaum c# kann und es über Snippet Converter for .NET 2.0 convertiert vb.net nich ganz anehmen will, hab ich es einfach mal in c# eingefügt, doch wie mache ich es wenn ich nun mehrere zeilen einlesen will, z.b. will ich jetz mal folgendes einlesen:


Code:
(values (value1 40) (value2 0) (value3 6) (value4 80) 
(value5 70) (value6 10 20))
(values (value1 70) (value2 0) (value3 6) (value4 80) 
(value5 70) (value6 10 20 30 40))
(values (value1 80) (value2 0) (value3 6) (value4 80) 
(value5 70) (value6 10 20 30))
(values (value1 90) (value2 0) (value3 6) (value4 40) 
(value5 70) (value6 10 20 30 20))
(values (value1 10) (value2 0) (value3 6) (value4 80) 
(value5 70) (value6 10 20 30 50))

Da gibt er mir nen fehler aus.
Ein Element mit dem gleichen Schlüssel wurde bereits hinzugefügt.

Wo der fehler liegt ist mir klar, doch bleiben die namen in jeder zeile gleich, nur die werte ändern sich. :confused:

Peter86
 
Zuletzt bearbeitet:
Tja, dann musst schon sagen, dass diese Namen mehrfach vorkommen können...
Da value1, value2 etc. als Schlüssel im Dictionary verwendet werden, darf es die nicht doppelt geben.

Also müssten die Daten jetzt anders gespeichert werden, dann geht aber..

[...]Ich möchte die werte folgendermaßen abrufen, z.b.
Code:
Value("value1").Item(0)

was ja über ein dictionary möglich ist, als wert müsste ich[...]

..nicht mehr. (value1 ist ja nicht eindeutig)

Wie willst denn die Daten weiter verwenden? Pro Datensatz könnte man ein eigenes Dictionary zurückliefern. Möglich ist vieles, hängt halt ab wie du mit den Daten arbeiten willst/musst.

Mh ja, konvertieren kann man meinen Code nicht 1:1. "yield return" gibts nicht, da müsste man zuerst die Werte in eine Liste speichern, und dann die gesamte zurückliefern.

lg,..
 
... Pro Datensatz könnte man ein eigenes Dictionary zurückliefern...

Ich brauch die werte in den Klammern nur einmal kurz auslesen und dann können sie schon wieder verworfen werden, also die werte in den klammern einmal einlesen, ausgeben und dann die nächste klammer das selbe tun. Also sozugsagen wie du schon meintes, jedes mal nen neues Dictionary, wobei das alte dann auch verworfen werden kann da es nicht mehr gebraucht wird.
 
C#:
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;


class Program
{
	static void Main(string[] args)
	{
		string text =
			"(values (value1 40) (value2 0) (value3 6) (value4 80) " +
			"(value5 70) (value6 10 20))" +
			"(values (value1 70) (value2 0) (value3 6) (value4 80) " +
			"(value5 70) (value6 10 20 30 40))" +
			"(values (value1 80) (value2 0) (value3 6) (value4 80) " +
			"(value5 70) (value6 10 20 30))" +
			"(values (value1 90) (value2 0) (value3 6) (value4 40) " +
			"(value5 70) (value6 10 20 30 20))" +
			"(values (value1 10) (value2 0) (value3 6) (value4 80) " +
			"(value5 70) (value6 10 20 30 50))";


		var result = Parse(text);

		for (int i = 0; i < result.Count; i++)
		{
			Console.WriteLine("\r\n -- Zeile {0}", i);
			foreach (string key in result[i].Keys)
			{
				Console.WriteLine("Name: {0}", key);

				for (int j = 0; j < result[i][key].Length; j++)
					Console.Write("{0}, ", result[i][key][j]);

				Console.WriteLine();
			}
		}

		Console.ReadLine();

	}

	private static List<Dictionary<string, int[]>> Parse(string text)
	{
		var data = new List<Dictionary<string, int[]>>();
		
		bool outer = false;
		bool inner = false;
		bool first = false;

		string key = null;
		List<string> values = null;
		Dictionary<string, int[]> currentData = new Dictionary<string, int[]>();

		foreach (string token in Tokenize(text))
		{

			switch (token)
			{
				case "(":
					if (outer)
					{
						inner = true;
						first = true;
						values = new List<string>();
					}
					else
						outer = true;

					break;
				case ")":
					if (inner)
					{
						inner = false;
						int[] vals = new int[values.Count];
						int t;
						for (int i = 0; i < vals.Length; i++)
						{
							if (int.TryParse(values[i], out t))
								vals[i] = t;
							else
								throw new Exception("Token not a number");
						}
						currentData.Add(key, vals);
					}
					else if (outer)
					{
						outer = false;

						if (currentData != null)
							data.Add(currentData);

						currentData = new Dictionary<string, int[]>();
					}
					else
						throw new Exception("Unexpected token ')'");
					break;
				default:
					if (!inner)
						break;
					if (first)
					{
						key = token;
						first = false;
					}
					else
					{
						values.Add(token);
					}

					break;
			}
		}

		return data;
	}

	private static IEnumerable<string> Tokenize(string text)
	{
		string token = "";

		foreach (char c in text)
		{
			switch (c)
			{
				case ' ':
				case '\n':
				case '\r':
					if (token.Length > 0)
						yield return token;
					token = "";
					break;
				case '(':
				case ')':
					if (token.Length > 0)
						yield return token;
					yield return c.ToString();
					token = "";
					break;
				default:
					token += c;
					break;
			}
		}
	}
}

Jetzt kommt eine Liste von Dictionarys zurück. Jeder Listeneintrag stellt eine Zeile dar.

result[2]["value6"][1] wäre dann nach deinen letzten Daten 20

lg,..
 

Neue Beiträge

Zurück