2010-05-03 36 views
5

cách hiệu quả nhất để phân tích một chuỗi C# theo hình thứcChuỗi Parsing trong C#

"(params (abc 1.3)(sdc 2.0)(www 3.05)....)" 

vào một cấu trúc theo hình thức

struct Params 
{ 
    double abc,sdc,www....; 
} 

Cảm ơn

EDIT là gì Cấu trúc luôn có cùng các thông số (cùng tên, chỉ tăng gấp đôi, được biết ở thời gian biên dịch) .. nhưng thứ tự không được cấp .. chỉ có một cấu trúc t một thời gian ..

+2

bạn có thể hiển thị ví dụ đầy đủ để bao gồm tất cả chuỗi mẫu không? – Glennular

+0

Rõ ràng những gì bạn đang yêu cầu. Bạn muốn có những giá trị nào cho a và b? Cấu trúc có động không, tức là nó có thể không phải lúc nào cũng là cùng một tập các biến? –

+1

Bạn không chắc chắn cách 'abc' là gấp đôi, bạn có thể tìm hiểu thêm về ví dụ của mình không? – RedFilter

Trả lời

2

Tùy thuộc vào ngữ pháp hoàn chỉnh của bạn, bạn có một vài lựa chọn: nếu đó là một ngữ pháp rất đơn giản và bạn không phải kiểm tra lỗi trong đó, bạn có thể chỉ cần đi theo bên dưới (sẽ nhanh chóng)

var input = "(params (abc 1.3)(sdc 2.0)(www 3.05)....)"; 
var tokens = input.Split('('); 
var typeName = tokens[0]; 
//you'll need more than the type name (assembly/namespace) so I'll leave that to you 
Type t = getStructFromType(typeName); 
var obj = TypeDescriptor.CreateInstance(null, t, null, null); 
for(var i = 1;i<tokens.Length;i++) 
{ 
    var innerTokens = tokens[i].Trim(' ', ')').Split(' '); 
    var fieldName = innerTokens[0]; 
    var value = Convert.ToDouble(innerTokens[1]); 
    var field = t.GetField(fieldName); 
    field.SetValue(obj, value); 
} 

tuy nhiên cách tiếp cận đơn giản này yêu cầu chuỗi phù hợp hoặc sẽ hoạt động không đúng.

Nếu ngữ pháp phức tạp hơn một chút, ví dụ: lồng nhau() sau đó cách tiếp cận đơn giản đó sẽ không hoạt động.

bạn có thể thử sử dụng regEx nhưng vẫn yêu cầu ngữ pháp khá đơn giản, do đó nếu bạn có một ngữ pháp phức tạp, lựa chọn tốt nhất của bạn là trình phân tích cú pháp thực. Irony là dễ sử dụng vì bạn có thể viết tất cả trong C# đơn giản (một số kiến ​​thức của BNF là một mặc dù).

2

Bạn có cần hỗ trợ nhiều cấu trúc không? Nói cách khác, điều này cần phải năng động; hoặc bạn có biết định nghĩa struct tại thời gian biên dịch không?

Phân tích cú pháp chuỗi bằng regex sẽ là lựa chọn hiển nhiên.

Đây là một regex, mà sẽ phân tích định dạng chuỗi của bạn:

private static readonly Regex regParser = new Regex(@"^\(params\s(\((?<name>[a-zA-Z]+)\s(?<value>[\d\.]+)\))+\)$", RegexOptions.Compiled); 

Chạy rằng regex trên một chuỗi sẽ cung cấp cho bạn hai nhóm có tên là "tên" và "giá trị". Thuộc tính Captures của mỗi nhóm sẽ chứa tên và giá trị.

Nếu loại cấu trúc không xác định tại thời gian biên dịch, thì bạn sẽ cần sử dụng sự phản chiếu để điền vào các trường.

Nếu bạn muốn tạo định nghĩa struct khi chạy, bạn cần sử dụng Reflection để phát ra kiểu; hoặc bạn sẽ cần tạo mã nguồn.

Bạn đang gặp sự cố nào?

+1

Nếu hiệu suất là rất quan trọng thì RegEx không phải là lựa chọn đầu tiên. Họ không thực hiện gần như cũng như các hoạt động chuỗi đơn giản như Split và cắt –

0

Bạn có muốn xây dựng biểu diễn dữ liệu của cú pháp đã xác định không?

Nếu bạn đang tìm kiếm khả năng bảo trì dễ dàng, không cần phải viết các câu lệnh RegEx dài, bạn có thể tạo trình phân tích cú pháp Lexer của riêng bạn. đây là một cuộc thảo luận trước về SO với các liên kết tốt trong các câu trả lời cũng như để giúp bạn

Poor man's "lexer" for C#

3
using System; 

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     struct Params 
     { 
      public double abc, sdc; 
     }; 

     static void Main(string[] args) 
     { 
      string s = "(params (abc 1.3)(sdc 2.0))"; 
      Params p = new Params(); 
      object pbox = (object)p; // structs must be boxed for SetValue() to work 

      string[] arr = s.Substring(8).Replace(")", "").Split(new char[] { ' ', '(', }, StringSplitOptions.RemoveEmptyEntries); 
      for (int i = 0; i < arr.Length; i+=2) 
       typeof(Params).GetField(arr[i]).SetValue(pbox, double.Parse(arr[i + 1])); 
      p = (Params)pbox; 
      Console.WriteLine("p.abc={0} p.sdc={1}", p.abc, p.sdc); 
     } 
    } 
} 

Lưu ý: nếu bạn sử dụng một lớp thay vì cấu trúc thì việc đánh boxing/bỏ hộp sẽ không cần thiết.

+0

+1 để sử dụng 'String.Split'. – Brian

+1

Tôi nghĩ anh ấy muốn có cấu trúc được tạo động, có thể thông qua một đối tượng kiểu từ điển. (Ví dụ bây giờ bao gồm 'www') – Glennular

+0

@Glennular: Chỉnh sửa của anh ấy nói cấu trúc đã được sửa. Nhưng tôi đồng ý với bạn anyway; Tôi muốn sử dụng một từ điển hơn so với phản ánh cho một cái gì đó như thế này. –

2

Một regex có thể làm công việc cho bạn:

public Dictionary<string, double> ParseString(string input){ 
    var dict = new Dictionary<string, double>(); 
    try 
    { 
     var re = new Regex(@"(?:\(params\s)?(?:\((?<n>[^\s]+)\s(?<v>[^\)]+)\))"); 
     foreach (Match m in re.Matches(input)) 
      dict.Add(m.Groups["n"].Value, double.Parse(m.Groups["v"].Value)); 
    } 
    catch 
    { 
     throw new Exception("Invalid format!"); 
    } 
    return dict; 
} 

sử dụng nó như:

string str = "(params (abc 1.3)(sdc 2.0)(www 3.05))"; 
var parsed = ParseString(str); 

// parsed["abc"] would now return 1.3 

Đó có thể phù hợp tốt hơn so với việc tạo ra rất nhiều cấu trúc khác nhau cho mỗi chuỗi đầu vào có thể, và sử dụng phản ánh để lấp đầy chúng. Tôi không nghĩ rằng đó là giá trị nỗ lực.

Hơn nữa, tôi cho rằng chuỗi đầu vào luôn ở định dạng chính xác mà bạn đã đăng.

1

Bạn có thể xem xét thực hiện thao tác chuỗi vừa đủ để làm cho đầu vào giống như đối số dòng lệnh chuẩn sau đó sử dụng trình phân tích cú pháp đối số dòng lệnh như NDesk.Options để điền đối tượng Params. Bạn từ bỏ một số hiệu quả nhưng bạn thực hiện nó trong bảo trì.

public Params Parse(string input) 
{ 
    var @params = new Params(); 
    var argv = ConvertToArgv(input); 
    new NDesk.Options.OptionSet 
     { 
      {"abc=", v => Double.TryParse(v, out @params.abc)}, 
      {"sdc=", v => Double.TryParse(v, out @params.sdc)}, 
      {"www=", v => Double.TryParse(v, out @params.www)} 
     } 
     .Parse(argv); 

    return @params; 
} 

private string[] ConvertToArgv(string input) 
{ 
    return input 
     .Replace('(', '-') 
     .Split(new[] {')', ' '}); 
} 
0

Tôi sẽ chỉ làm một trình phân tích cú pháp đệ quy cơ bản. Nó có thể tổng quát hơn bạn muốn, nhưng không có gì khác sẽ nhanh hơn nhiều.

0

Dưới đây là một out-of-the-box cách tiếp cận: convert() để {} và [SPACE] để ":", sau đó sử dụng System.Web.Script.Serialization.JavaScriptSerializer.Deserialize

string s = "(params (abc 1.3)(sdc 2.0))" 
    .Replace(" ", ":") 
    .Replace("(", "{") 
    .Replace(")","}"); 

return new System.Web.Script.Serialization.JavaScriptSerializer().Deserialize(s); 
+0

Dường như với tôi điều này sẽ phá vỡ tất cả để địa ngục nếu params có thể chứa không gian hoặc parens ... – cHao