【问题标题】:Regex pipe character used as delimiter within subgroups正则表达式管道字符用作子组内的分隔符
【发布时间】:2018-05-19 17:24:03
【问题描述】:

我有由管道字符分隔的字符串。这是一个可重复的序列:

<machinenr>|<controldone>|<nrofitems|<items>

无论您在哪里看到项目标签,您都会看到项目编号也由中间的竖线字符分隔。好吧,它不是一种智能格式,但我必须解决它,我想在 C# 中使用正则表达式。所以假设上面的格式让我们有一个真实的例子:

446408|0|2|111|6847|446408||0||

注意理论上管道之间不需要有值,内容也不受长度限制。项目 ID 可以是 111 或 877333,但也可以是混合字母数字 ID XB111。所以这里我们有两台没有物品的机器:

446408|0|0||447400||0||

这里我们有几台机器没有或有一些物品。请注意,管道字符也用于分隔项目,因此管道中有管道:

446408|0|1|111|446408|0|3|99884|111|73732|446408|0|0||

这台机器有三个项目: 446408|0|3|99884|111|73732|

项目ID:

99884|111|73732

正则表达式应该是什么样的?我已尝试使用以下命名组(更易于阅读),但它不起作用:

^(?P<machinenr>.*?)\|
(?P<controldone>.*?)\|
(?P<nrofitems>.*?)\|
(?P<items>.*?)\|

这里是对@Atterson @sln 和 @ 的澄清。注意,物品的数量可以是0-n,数量没有限制。让我们举个例子,一个带有机器的长字符串及其项目:446408|0|1|111|446408|0|3|99884|111|73732|446408|0|0||我期望正则表达式做的是将此字符串分解为三个匹配项/部分及其值,第一个匹配项是:446408|0|1|111|第二场比赛:446408|0|3|99884|111|73732|第三场比赛:446408|0|0||好的,以每个部分应该被分成的值为例,让我们使用第二个匹配/部分。它是一台nr 446408的机器,它没有被控制为0,它有3个项目,项目ID:99884|111|73732。在这些项目之后,一个新的序列:

<machinenr>|<controldone>|<nrofitems|<items>

可以关注。 @Sanxofon 请在此处检查您的正则表达式:[链接]https://regex101.com/r/kC3gH0/87,不幸的是,您会看到它不匹配。

【问题讨论】:

  • 请清楚输入:2 或 3 个示例字符串以及您期望的完全匹配或替换后的最终字符串(如果您想替换)
  • 到目前为止,您还没有正确描述 管道分隔字段管道分隔值。这是必需的,即使是固定数量的字段。
  • 您使用的是. 而不是[^\|]。和beeing greedy,你的正则表达式不起作用。
  • 这个一点都不清楚。请提供一些非常清楚的格式解释,您的插图只是“不要点击”。
  • 有没有办法区分机器号和项目字段的一部分?

标签: c# regex pipe delimiter


【解决方案1】:

这不能用正则表达式来解决,没有办法告诉正则表达式:“匹配.*?\| 与某个捕获组相同的次数......恰好包含一个数字。”不过,这是使用普通旧 C# 解决此问题的直接方法。

string items = "446408|0|1|111|446408|0|3|99884|111|73732|446408|0|0|";
var fields = items.Split('|');
for (int i = 0; i < fields.Length;) {
    Console.WriteLine("machinenr:" + fields[i++]);
    Console.WriteLine("controldone:" + fields[i++]);
    int numSubItems = Int32.Parse(fields[i++]);
    Console.WriteLine("num subitems:" + numSubItems);
    if (numSubItems == 0) {
        i++;
        continue;
    }                

    for (int subItemIndex = 0; subItemIndex < numSubItems; subItemIndex++) {
        Console.WriteLine("\tItem:" + (subItemIndex + 1) + ": " + fields[i++]);
    }                
}

仅供参考,我修剪了尾随的“|”你原来的字符串有,所以

string items = "446408|0|1|111|446408|0|3|99884|111|73732|446408|0|0|";

而不是

string items = "446408|0|1|111|446408|0|3|99884|111|73732|446408|0|0||";

【讨论】:

  • 谢谢,但是通过正则表达式来做这件事更具挑战性。这是一个类似的问题:[链接] stackoverflow.com/questions/28200875/… 但是,这是一个更简单的场景,因为要忽略的管道序列被转义了。在我必须解析的格式中,没有特殊的标签/字符来区分包含项目的字符串部分。 C#正则表达式具有平衡组等特殊功能。它必须以某种方式使用正则表达式,比如包装包含项目的子组? @Wiktor Stribiżew
  • 虽然这不是正则表达式,但这可能是更直接、更高效的方法。
  • 您当然可以继续尝试使用正则表达式,但我可以提前告诉您您定义问题的方式,这是不可能的。 “谢谢,但是通过正则表达式来做这件事更具挑战性。”那么,您是故意避免使用简单的解决方案吗?这看起来很奇怪,但没关系:)
  • 我认为如果无法使用正则表达式解决此问题,则必须使用拆分解决方案。感谢大家的帮助/答案和示例来自:@aquinas
【解决方案2】:

命名的捕获组在 C# 中是 (?&lt;nam&gt;...) 而不是 (?P&lt;name&gt;...)。此外,您表达了重复匹配的愿望(因此我将您的正则表达式包装在重复的 (?&lt;grp&gt;..) 中。

您需要弄清楚如何区分物品和机器。例如,如果您可以说所有机器编号都是 6 位数字,并且项目是 0-5 位数字,那么您可以这样做......您仍然需要拆分 items 集合。

^(?<grp>(?<machinenr>[^\|]{6})\|
(?<controldone>[^\|]*)\|
(?<nrofitems>[^\|]*)\|
(?<items>(?:[^\|]{0,5}\|){1,}))*$

示例 C# 实现:

class Program
{

    static void Main(string[] args)
    {
        string strRegex = 
@"^(?<grp>(?<machinenr>[^\|]{6})\|
(?<controldone>[^\|]*)\|
(?<nrofitems>[^\|]*)\|
(?<items>(?:[^\|]{0,5}\|){1,}))*$";
        Regex myRegex = new Regex(strRegex, RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace);
        string strTargetString = @"446408|0|1|111|446408|0|3|99884|111|73732|446408|0|0||";

        MatchCollection matches = myRegex.Matches(strTargetString);

        foreach (Match m in matches)
        {
            for (int idx = 0; idx < m.Groups["grp"].Captures.Count; idx++)
            {
                Console.WriteLine("Group:");
                Console.WriteLine($"\tmachinenr={m.Group["machinenr"].Captures[idx]}");
                Console.WriteLine($"\tcontroldone={m.Groups["controldone"].Captures[idx]}");
                Console.WriteLine($"\tnrofitems={m.Groups["nrofitems"].Captures[idx]}");
                Console.WriteLine($"\titems={m.Groups["items"].Captures[idx]}");
            }
        }
    }
}


使用 C# IEnumerable 算法

拆分字符串并解析后续数组似乎更容易。但是,如果您担心处理大字符串或不想使用String.Split(),您可以使用IEnumerable&lt;T&gt; 方法。这是一种方法...

class Program
{

    public class Entry
    {
        public string MachineNr { get; set; }
        public string ControlDone { get; set; }
        public int Count { get; set; }
        public List<string> Items { get; set; }

        private static IEnumerable<string> fields(string list)
        {
            int idx = 0;
            do
            {
                int ndx = list.IndexOf('|', idx);
                if (ndx == 1)
                    yield return list.Substring(idx);
                else
                    yield return list.Substring(idx, ndx - idx);                        

                idx = ++ndx;
            }
            while (idx > 0 && idx < list.Length-1) ;
        }

        public static IEnumerable<Entry> parseList(string list)
        {
            int idx =0;
            var fields = Entry.fields(list).GetEnumerator();
            while (fields.MoveNext())
            {
                var e = new Entry();
                e.MachineNr = fields.Current;
                if (fields.MoveNext())
                {
                    e.ControlDone = fields.Current;
                    if (fields.MoveNext())
                    {
                        int val = 0;
                        e.Count = int.TryParse(fields.Current, out val) ? val : 0;
                        e.Items = new List<string>();
                        for (int x=e.Count;x>0;x--)
                        {
                            if (fields.MoveNext())
                                e.Items.Add(fields.Current);
                        }
                    }
                }

                yield return e;
            }
        }
    }
    static void Main(string[] args)
    {
        string strTargetString = @"446408|0|1|111|446408|0|3|99884|111|73732|446408|0|0||";
        foreach (var entry in Entry.parseList(strTargetString))
        {
            Console.WriteLine(
$@"Group:
    Machine:        {entry.MachineNr}
    ControlDone:    {entry.ControlDone}
    Count:          {entry.Count}
    Items:          {string.Join(", ",entry.Items)}");
        }

    }
}

【讨论】:

  • 拆分项目集合没有问题。你有没有测试过你的正则表达式,我似乎无法让它工作。但是从阅读它来看,它确实非常接近解决方案,但是因为我无法区分 machinenr 和 itemnrs 它不会工作。 machinenr 可以是 0-10000000000 之间的任何值,任何项目编号也是如此。但我知道 C# 版本的正则表达式有一些独特的功能,其中是解决方案的关键。但是,我还不是这样的正则表达式大师,还没有弄清楚如何使用它们。
  • 我已经用 Multiline 和 IgnorePatternWhiteSpace 进行了测试
  • 记住:如果您使用的是 C# 字符串,请确保它是文字 @"^(?&lt;grp&gt;(?&lt;machinenr&gt;[^\|]{6})\| (?&lt;controldone&gt;[^\|]*)\| (?&lt;nrofitems&gt;[^\|]*)\| (?&lt;items&gt;(?:[^\|]{0,5}\|){1,}))*$",否则请对反斜杠进行双重转义。
  • 数据库中的字符串将是一个长的单行字符串,包含数十或数百台机器及其各自的项目。
  • 我用示例实现更新了我的答案。这假设您有多行。如果没有,只需删除 Multiline RegexOption。
猜你喜欢
  • 1970-01-01
  • 2023-03-15
  • 1970-01-01
  • 1970-01-01
  • 2021-08-09
  • 2014-04-28
  • 1970-01-01
  • 2014-08-17
  • 1970-01-01
相关资源
最近更新 更多