什么是在C#中解析此字符串的最佳方法?

24

我有一个字符串,从另一个系统中读取。它基本上是一个代表一系列键值对的长字符串,这些键值对之间由空格分隔。它看起来像这样:

 key:value[space]key:value[space]key:value[space]

于是我编写了以下代码进行解析:

string myString = ReadinString();
string[] tokens = myString.split(' ');
foreach (string token in tokens) {
     string key = token.split(':')[0];
     string value = token.split(':')[1];
     .  . . . 
}
现在问题是一些值中有空格,因此我在顶部的“简单”拆分不再起作用。我想知道如何仍然解析出键值对列表(以空格为分隔符),现在我知道值字段中也可能有空格,因为拆分似乎不再能够工作。注意:我现在确认KEYs不会有空格,所以我只需要担心值。对于混淆表示歉意。

1
你能控制输入格式吗? - Stefan
3
是否至少强制规定值内不得包含 :?如果没有,你将陷入困境。如果生成了长字符串,则有可能转义字符以避免问题,但这时你需要比 Split 更好的读取输入的方法。 - jdehaan
12
这是无法完成的。你如何知道一个单词属于值还是下一个键? - vidstige
+1 给 vidstige,我们可以假设键值可能没有空格,: 可以被转义吗? - Jodrell
如果值中包含冒号,这是不可行的。考虑 a:b c:d。这是两对吗,a => bc => d,还是只有一对,a => b c:d - Eric
显示剩余6条评论
9个回答

22

使用这个正则表达式:

\w+:[\w\s]+(?![\w+:])

我在上面进行了测试

test:testvalue test2:test value test3:testvalue3

它返回了三个匹配项:

test:testvalue
test2:test value
test3:testvalue3

您可以将\w替换为出现在输入中的任何字符集。

用于测试的代码:

var regex = new Regex(@"\w+:[\w\s]+(?![\w+:])");
var test = "test:testvalue test2:test value test3:testvalue3";

foreach (Match match in regex.Matches(test))
{
    var key = match.Value.Split(':')[0];
    var value = match.Value.Split(':')[1];

    Console.WriteLine("{0}:{1}", key, value);
}
Console.ReadLine();

正如Wonko the Sane指出的那样,这个正则表达式在具有冒号 : 的值上会失败。如果您预测到这种情况,请使用\w+:[\w: ]+?(?![\w+:]) 作为正则表达式。虽然当value中的冒号前面有空格时,这仍将失败...我会思考解决方案。


15
“任何足够先进的正则表达式都无法区分与魔法相区别。” - Alex
注意:\s 也会匹配制表符和换行符,所以如果您认为它们可能出现在值中,请将 [\w\s] 更改为 [\w ] - Episodex
注意 - "test:testvalue test2:test:withcolon value test3:testvalue3" 未通过 test2 测试。 - Wonko the Sane
@Wonko the Sane: 你说得对。我添加了解决方案。但仍然不完美。 - Episodex
没错 - 问题在于使用解析器对模式进行处理时,模式必须始终是可预测的。 - Wonko the Sane
现在你有两个问题。 :) - Wavel

5

如果不将空格分隔更改为其他字符(如“|”),则此方法无法工作。

考虑以下内容:

阿尔弗雷德·贝斯特:阿尔弗雷德·贝斯特 阿尔弗雷德:阿尔弗雷德·贝斯特

  • 这是“阿尔弗雷德·贝斯特”键和“阿尔弗雷德”值,还是“阿尔弗雷德”键和“贝斯特·阿尔弗雷德”值?

4
string input = "foo:Foobarius Maximus Tiberius Kirk bar:Barforama zap:Zip Brannigan";

foreach (Match match in Regex.Matches(input, @"(\w+):([^:]+)(?![\w+:])"))
{
   Console.WriteLine("{0} = {1}", 
       match.Groups[1].Value, 
       match.Groups[2].Value
      );
}

给你带来以下好处:
foo = Foobarius Maximus Tiberius Kirk
bar = Barforama
zap = Zip Brannigan

不错!我更喜欢这个解决方案,因为它将键和值分组,而不是依赖于拆分。这样可以在正则表达式中添加更多逻辑,并允许进行更多自定义,例如使用类似Google的分组来封装值字符串。 例如:key1:(this:is:a funky value) - Jason

2
您可以尝试对空格之间的内容进行Url编码(键和值,而不是:符号),但这需要您对输入方法具有控制权。
或者,您可以使用另一种格式(如XML或JSON),但同样需要您对输入格式具有控制权。
如果您无法控制输入格式,您可以使用正则表达式,在单词加:之后搜索单个空格。 更新(感谢Jon Grant):似乎您可以在键和值中使用空格。如果是这种情况,您需要认真重新考虑您的策略,因为即使正则表达式也无法帮助您。

尽管我很讨厌正则表达式,但我认为在这种情况下它是最好的选择。 - ZombieSheep
1
这就是为什么我使用它。不是因为我能,而是因为我必须。 :D - Johann du Toit
2
问题中提到键和值中可能会有空格...即使是正则表达式也无法解决这个问题。 - Jon Grant
啊,我错过了原问题的那一部分。既然如此,你是对的,OP可能不得不求助于最佳猜测...也许如果键来自预定义的可能性列表呢? - ZombieSheep
这是可能的。您可以扫描字符串并搜索{key}的位置,然后从该位置到下一个:进行子字符串操作,然后检查字符串中是否仍有任何键,并替换它。但这似乎非常“丑陋”。您不能更改输入格式吗?或者这是第三方库? - Johann du Toit

1
string input = "key1:value key2:value key3:value";
Dictionary<string, string> dic = input.Split(' ').Select(x => x.Split(':')).ToDictionary(x => x[0], x => x[1]);

第一个将会产生一个数组:
"key:value", "key:value"

然后是一个数组的数组:

{ "key", "value" }, { "key", "value" }

然后是一个字典:

"key" => "value", "key" => "value"

请注意,Dictionary<K,V>不允许重复的键,如果出现这种情况,它会引发异常。如果可能出现这种情况,请使用ToLookup()

1

使用正则表达式可以解决您的问题:

private void DoSplit(string str)
{
    str += str.Trim() + " ";
    string patterns = @"\w+:([\w+\s*])+[^!\w+:]";
    var r = new System.Text.RegularExpressions.Regex(patterns);
    var ms = r.Matches(str);
    foreach (System.Text.RegularExpressions.Match item in ms)
    {
        string[] s = item.Value.Split(new char[] { ':' });
        //Do something
    }
}

0

我猜你可以稍微扩展一下你的方法来处理这些东西...

有点伪代码的感觉:

List<string> parsedTokens = new List<String>();
string[] tokens = myString.split(' ');
for(int i = 0; i < tokens.Length; i++)
{
    // We need to deal with the special case of the last item, 
    // or if the following item does not contain a colon.
    if(i == tokens.Length - 1 || tokens[i+1].IndexOf(':' > -1)
    {
        parsedTokens.Add(tokens[i]);
    }
    else
    {
        // This bit needs to be refined to deal with values with multiple spaces...
        parsedTokens.Add(tokens[i] + " " + tokens[i+1]);
    }
}

另一种方法是在冒号上进行分割... 这样,您的第一个数组项将是第一个键的名称,第二个项将是第一个键的值和第二个键的名称(可以使用LastIndexOf进行拆分),以此类推。如果值中包含冒号或键中包含空格,则显然会变得非常混乱,但在这种情况下,您几乎没有什么办法...

0
这段代码可以实现它(遵循以下规则)。它解析键和值,并将它们返回到一个Dictionary<string, string>数据结构中。我在末尾添加了一些代码,假设根据您的示例,整个字符串/流的最后一个值将附加一个[空格]:
private Dictionary<string, string> ParseKeyValues(string input)
        {
            Dictionary<string, string> items = new Dictionary<string, string>();

            string[] parts = input.Split(':');

            string key = parts[0];
            string value;

            int currentIndex = 1;

            while (currentIndex < parts.Length-1)
            {
                int indexOfLastSpace=parts[currentIndex].LastIndexOf(' ');
                value = parts[currentIndex].Substring(0, indexOfLastSpace);
                items.Add(key, value);
                key = parts[currentIndex].Substring(indexOfLastSpace + 1);
                currentIndex++;
            }
            value = parts[parts.Length - 1].Substring(0,parts[parts.Length - 1].Length-1);


            items.Add(key, parts[parts.Length-1]);

            return items;

        }

注意:此算法假定遵循以下规则:
  1. 值中没有空格
  2. 键中没有冒号
  3. 值中没有冒号

0

不使用任何正则表达式或字符串连接,作为一个可枚举对象(假设键没有空格,但值可以有):

    public static IEnumerable<KeyValuePair<string, string>> Split(string text)
    {
        if (text == null)
            yield break;

        int keyStart = 0;
        int keyEnd = -1;
        int lastSpace = -1;
        for(int i = 0; i < text.Length; i++)
        {
            if (text[i] == ' ')
            {
                lastSpace = i;
                continue;
            }

            if (text[i] == ':')
            {
                if (lastSpace >= 0)
                {
                    yield return new KeyValuePair<string, string>(text.Substring(keyStart, keyEnd - keyStart), text.Substring(keyEnd + 1, lastSpace - keyEnd - 1));
                    keyStart = lastSpace + 1;
                }
                keyEnd = i;
                continue;
            }
        }
        if (keyEnd >= 0)
            yield return new KeyValuePair<string, string>(text.Substring(keyStart, keyEnd - keyStart), text.Substring(keyEnd + 1));
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接