C#字符串约定解析

时间:2009-04-01 21:58:14

标签: c# string parsing

我对C#很新,并且有一个用户输入字符串(我希望有效。)

此字符串将构成信息的多个部分和子部分。

例如:

1-7 will //represent values 1 2 3 4 5 6 7

3:.25:7 //will be the numbers contained between 3 and 7 in increments of .25 
        // (3 3.25 3.5 3.75 4 ... 6.75 7)
1,4,5  //will represent values 1 4 5

我希望能够迭代一个类似于此的单个字符串并获得尽可能多的数组,因为数据用分号分隔(;)

主要目标是解析这样的事情:

1-7;3:.25:10;1,5,9;4-7

由于上面有4个“数据集”,我应该创建4个包含这些值的新数组。如果我有n个“数据集”,我应该创建n个新数组。

稍后,我想使用所有组合以嵌套方式遍历数组。

如果可能(不要求),也可以进行某种形式的混合,如下所示:

1-7,9,16:2:20;

我希望我在这里尝试完成的事情在我的例子中是有意义的。

谢谢!

8 个答案:

答案 0 :(得分:3)

好吧,可能会有一个聪明的RegEx的答案,我会尝试使用我最喜欢的string.Split()函数。

作为第一步,您可以将输入字符串拆分为';'

string[] datasets = inputString.Split(';');

就你的最后一点而言,似乎逗号“,”或多或少相同,你可以将其与Split(';', ',')合并,或者将它们与

分开
string[] parts = datasets[i].Split(',');

然后,部分是三种情况之一:单个数字,范围或步进范围。

您可以使用string.IndexOf()和/或

进行探测
string[] rangeParts = parts[j].Split('-');
string[] steppedParts = parts[j].Split(':');

结果应分别为长度2和3。

然后应使用TryParse()检查生成的字符串,并且由于使用了标点字符,您最好修复文化:

bool valid = double.TryParse(parts[k], 
  System.Globalization.NumberStyles.AllowDecimalPoint, 
  System.Globalization.CultureInfo.InvariantCulture, out value);

这些是部件,需要一些装配。

答案 1 :(得分:2)

我会使用正则表达式来表达。首先,我将使用以下表达式分成几个部分。

^((?<section>[^;]+)(;|$))+

然后将每个部分拆分成小节。

^((?<subsection>[^,]+)(,|$))+

现在匹配三种可能的子部分类型。

(?<value>^[0-9]+$)|
(?<range>^[0-9]+-[0-9]+$)|
(?<rangewithstep>^[0-9]+:\.[0-9]+:[0-9]+$)

最后,您必须分析范围类型小节。

^(?<start>[0-9]+)-(?<end>[0-9]+)$

^(?<start>[0-9]+):(?<step>\.[0-9]+):(?<end>[0-9]+)$

现在需要将提取的字符串解析为数字并将其添加到数组中。


我把所有东西放在一个小的控制台应用程序中完成了这件事。它远非完美 - 没有错误处理,没有任何东西,只是解析一个演示输入。我合并了之前提到的一些表达式,以使代码更紧凑,可能更好。

using System;
using System.Text.RegularExpressions;
using System.Globalization;

namespace RangeParser
{
    class Program
    {
        static void Main(string[] args)
        {
            String input = "1-7,9,16:2:20;1-7; 3:.75 : 10;1,5,9;4-7";

            Match sections = (new Regex(@"^((?<section>[^;]+)(;|$))+")).Match(input.Replace(" ", ""));

            foreach (Capture section in sections.Groups["section"].Captures)
            {
                Console.Write("Section ");

                Match subsections = (new Regex(@"^((?<subsection>[^,]+)(,|$))+")).Match(section.Value);

                foreach (Capture subsection in subsections.Groups["subsection"].Captures)
                {
                    Match subsectionparts = (new Regex(@"^(?<start>[0-9]*\.?[0-9]+)(((:(?<step>[0-9]*\.?[0-9]+):)|-)(?<end>[0-9]*\.?[0-9]+))?$")).Match(subsection.Value);

                    if (subsectionparts.Groups["start"].Length > 0)
                    {
                        Decimal start = Decimal.Parse(subsectionparts.Groups["start"].Value, CultureInfo.InvariantCulture);
                        Decimal end = start;
                        Decimal step = 1;

                        if (subsectionparts.Groups["end"].Length > 0)
                        {
                            end = Decimal.Parse(subsectionparts.Groups["end"].Value, CultureInfo.InvariantCulture);

                            if (subsectionparts.Groups["step"].Length > 0)
                            {
                                step = Decimal.Parse(subsectionparts.Groups["step"].Value, CultureInfo.InvariantCulture);
                            }
                        }

                        Decimal current = start;

                        while (current <= end)
                        {
                            Console.Write(String.Format("{0} ", current));

                            current += step;
                        }
                    }
                }

                Console.WriteLine();
            }

            Console.ReadLine();
        }
    }
}

<强>更新

修改为允许“1.5:0.2:3.6”之类的内容。

更新

为什么使用十进制而不是单一或双?

输入中的数字是十进制数,不能用单数或双精度表示,因为它们使用基数2表示。所以0.1例如由单值0.100000001490116119384765625表示。

Single x = 0.0F;

for (int i = 0; i < 8; i++)
{
   x += 0.1F;
}

Console.WriteLine(x);

该程序仅在8次迭代后打印0.8000001。在1000次迭代之后,误差增加到0.00095,显示99.99905而不是100.0,经过一百万次迭代后,结果为100,958.3而不是100,000。

十进制没有这样的错误,因为十进制使用基数10表示,并且能够精确地表示十进制数字,如0.1。

答案 2 :(得分:2)

这里有一些应该做你想做的C#代码:

    var results = ParseExpression("1-7;3:.25:10;1,5,9;4-7");

    private static List<List<float>> ParseExpression(string expression)
    {
        // "x-y" is the same as "x:1:y" so simplify the expression...
        expression = expression.Replace("-", ":1:");

        var results = new List<List<float>>();
        foreach (var part in expression.Split(';'))
            results.Add(ParseSubExpression(part));

        return results;
    }

    private static List<float> ParseSubExpression(string part)
    {
        var results = new List<float>();

        // If this is a set of numbers...
        if (part.IndexOf(',') != -1)
            // Then add each member of the set...
            foreach (string a in part.Split(','))
                results.AddRange(ParseSubExpression(a));
        // If this is a range that needs to be computed...
        else if (part.IndexOf(":") != -1)
        {
            // Parse out the range parameters...
            var parts = part.Split(':');
            var start = float.Parse(parts[0]);
            var increment = float.Parse(parts[1]);
            var end = float.Parse(parts[2]);

            // Evaluate the range...
            for (var i = start; i <= end; i += increment)
                results.Add(i);
        }
        else
            results.Add(float.Parse(part));

        return results;
    }

答案 3 :(得分:2)

以下关于我的正则表达式解决方案的评论煽动我进行分析。

  

这不是正则表达式有点过于丑陋的情况吗?一个直接的代码   我认为,解决方案会更简单,更容易理解。 - C.罗斯

我的回答如下。

  可能是。我决定使用正则表达式,因为你覆盖了所有案例(如果   你的正则表达式是正确的)。如果你使用String.Split()和朋友,你   如果输入有效,则获得更简单的解决方案。但抓住所有无效输入   使用字符串方法可能真的变得恐怖。当我开始解析数字时   十进制我已经知道它是一个有效的数字,不会失败。要么   想想像'1-2-3;, - 1:。:'这样的输入 - 你会分开它们,但会在以后崩溃   非常肯定或返回无意义的结果。在大卫的代码访问部分[2]将是   超出输入'1:2'的范围,解析可能会失败,并且有相当的   确定一些更难忘的错误。抓住他们所有可能会   代码比正则表达式代码更难以阅读。 - danbruc

所以我决定使用微软真棒工具PEX并分析我的正则表达方法和David的字符串操作方法。我没有修改大卫的代码,并在我的解决方案中替换了控制台输出,其中的语句像大卫那样将结果构建为List<List<Decimal>>

为了使完​​全分析成为可能,我约束PEX只生成短于45个字符的输入,并且只使用以下9个不同的字符。

019.;,-:!

不需要使用所有数字,因为它们(应该)的行为完全相同。我包括9,以便很容易发现溢出,但0和1也应该足够 - PEX可能会找到1000而不是999.我包括0和1发现一个非常小的数字错误,如0.000 [...] 001但没有出现。我假设非常小的数字被默默地舍入为零,但我没有进一步调查。或者可能是44(44因为小数的精确度为28到29位加上其他字符的一些空间)字符只是为了产生足够小的数字。包含其他字符,因为它们是输入中的其他有效字符。最后,我将感叹号作为无效字符的代理。

分析结果证明我是对的。 PEX在我的代码中发现了两个错误。我没有检查空输入(我跳过那个故意专注于重要部分)导致众所周知的NullReferenceException并且PEX发现输入“999999999999999999999999999999”导致Decimal.Parse()失败并出现OverflowException。 PEX还报告了一些假阴性结果。例如,“!; 9 ,;。0; 990:!!:,900:09”被报告为导致FormatException的输入。重新生成生成的测试不会产生异常。事实证明,“。0”导致测试在探索期间失败。查看其他失败的测试显示,Decimal.Parse()在探索期间以小数点开始的(所有)输入失败。但它们是有效数字,在正常执行期间不会失败。我无法解释这种误报。

这是针对字符串操作解决方案的一次PEX运行的结果。两个实现共享缺少的空检查和溢出异常。但是简单的字符串操作解决方案无法处理许多格式错误的输入。它们几乎都导致FormatException,但PEX也发现了我预测的IndexOutOfRangeException。

FormatException:           "!,"
FormatException:           ","
FormatException:           "1,"
FormatException:           "!"
FormatException:           ";9"
FormatException:           "::"
FormatException:           "!.999009"
FormatException:           "!.0!99!9"
FormatException:           "0,9.90:!!,,,!,,,,,,!,,,0!!!9,!"
FormatException:           ""
FormatException:           "-99,9"
FormatException:           "1,9,,,!,,,,,,9,,,9,1,!9,,,,!,!"
FormatException:           "!:,"
FormatException:           "!9!:.!!,!!!."
FormatException:           "!:"
IndexOutOfRangeException:  "1:9"
FormatException:           "09..::!"
FormatException:           "9,0..:!.!,,,!,,,,,,!,,,!!-,!,!"
OverflowException:         "99999999999999999999999999999999999999999999"
FormatException:           "!."
FormatException:           "999909!!"
FormatException:           "-"
FormatException:           "9,9:9:999,,,9,,,,,,!,,,!9!!!,!"
FormatException:           "!9,"
FormatException:           "!.09!!0!"
FormatException:           "9-;"
FormatException:           ":"
FormatException:           "!.!9!9!!"
NullReferenceException:    null
FormatException:           ":,"
FormatException:           "!!"
FormatException:           "9;"

现在的问题是,处理所有这些案件有多难。简单的解决方案是使用try / catch子句保护解析指令。我不确定这是否足以保证输入良好部分的正确操作。但是,这可能不是必需的,并且输入格式错误会导致空结果,这将使修复解决方案变得容易。

最后,这里是代码覆盖率结果。请注意,我使用decimal和single分析了正则表达式解决方案,因为PEX无法检测Decimal.Parse()中使用的一个方法。

ParseExpression(string)            100,00%  10/10 blocks
ParseSubExpression(string)          96,15%  25/26 blocks

ParseExpressionRegex(string)        95,06%  77/81 blocks
ParseExpressionRegexSingle(string)  94,87%  74/78 blocks

结论对我来说 - 应该首选正则表达式解决方案。它们设计和理解起来有些困难,但是它们比基于字符串操作的简单实现更能处理格式错误的输入。而且不要忘记 - 如果返回的结果是正确的,我根本没有做好准备。这是另一种情况。

答案 4 :(得分:1)

C#中没有用于解析范围的约定,所以你可以自由地做任何最有意义的事情。

但是,您可能希望在数学中从Interval Notation得出您的符号。

[2,4] - numbers between 2 and 4
(0,7] - numbers between 0 and 7, but not including 0

答案 5 :(得分:0)

我不确定我完全理解你的问题,但听起来你正在寻找String.Split()

答案 6 :(得分:0)

好的,所以当我使用Hank的前两部分分割字符串时(假设数据集中有a)

然后我可以进入并用剩余的信息填充一个数组。

对于 - 分隔的,我会取 - 之前的值 - 并从那里做一个for循环到after值。

对于:分离的,我做了几乎相同的事情,除了for循环更新的i ++增量,我做一个i + =(中间值)。

要解析 - 或者:字符之前和之后的值,我可以再次拆分并知道数组中的哪些indeces对应于什么。

谢谢,

我将在明天用最终解决方案更新。

如果Henk Holterman想要用我上面所述的内容(解析其他部分的描述)更新他的解决方案,我将在我的家庭账户上投票。出于某种原因,他们在这里阻止了openID。

附注:我不明白为什么他们不会让我接受解决方案,即使作为客人,我应该能够提供正确的电子邮件地址吗?

答案 7 :(得分:0)

首先在分号上拆分字符串以获取单独的集合。然后用逗号分割每个集合以获得集合中的单独数字或范围。

您现在拥有的字符串可以是:

  • 单个号码,例如42

  • 一系列数字,例如1-7

  • 步长范围,例如1:.5:7

您可以通过检查字符串是否包含连字符或冒号来识别第二个和第三个。然后,您将拆分这些字符串并进行一些循环以将数字添加到集合中。

通过像这样处理同一级别的数字和范围,它们可以完全按照您想要的方式混合。

一些提示:

使用double.TryParse来解析数字。使用CultureInfo.InvariantCulture作为格式提供程序,它使用句点作为小数分隔符。

您可以使用List<double>来保存每组的数字。最终结果可以是列表数组,或者如果需要数组数组,可以使用ToArray方法从列表中创建数组。