找回密码
 注册
搜索
[新手上路]批处理新手入门导读[视频教程]批处理基础视频教程[视频教程]VBS基础视频教程[批处理精品]批处理版照片整理器
[批处理精品]纯批处理备份&还原驱动[批处理精品]CMD命令50条不能说的秘密[在线下载]第三方命令行工具[在线帮助]VBScript / JScript 在线参考
查看: 22|回复: 1

[原创教程] [Perl]捕获配对括号(多层镶嵌)、平衡组的歪招

[复制链接]
发表于 昨天 22:03 | 显示全部楼层 |阅读模式
本帖最后由 523066680 于 2026-3-25 22:58 编辑

我看到之前论坛有不少相关的帖子和讨论。但是本人没有参与,主要是自己几乎没有遇到类似需求。
不过最近遇到了 :-)

首先考虑偷懒的做法,比如交给 DOM 解析器处理

  1. # 523066680@163.com
  2. use Modern::Perl;
  3. use Mojo::DOM;
  4. STDOUT->autoflush(1);

  5. my $text =<<CODE;
  6. lv0:
  7. line1 (lv1:a (lv2:test(lv3:part1(lv4:deep)),turn(lv3:part2)) string);
  8. line2 (lv1:b (lv2:I) (lv2:II));
  9. CODE

  10. # 把括号转成标签,标签名自定
  11. $text =~ s/\(/<b>/g;
  12. $text =~ s/\)/<\/b>/g;

  13. # 交给 DOM 解析器
  14. my $dom = Mojo::DOM->new->xml(1)->parse( $text );
  15. print_st($dom, 0);

  16. sub print_st
  17. {
  18.     my ( $node, $lv ) = @_;
  19.     for my $e ( $node->children->each() )
  20.     {
  21.         my $s = $e;
  22.         # 还原括号
  23.         $s =~ s/<b>/\(/g;
  24.         $s =~ s/<\/b>/\)/g;
  25.         printf "%s%s\n", "  "x$lv, $s;
  26.         print_st( $e, $lv+1 );
  27.     }
  28. }
复制代码


输出结果:
  1. (lv1:a (lv2:test(lv3:part1(lv4:deep)),turn(lv3:part2)) string)
  2.   (lv2:test(lv3:part1(lv4:deep)),turn(lv3:part2))
  3.     (lv3:part1(lv4:deep))
  4.       (lv4:deep)
  5.     (lv3:part2)
  6. (lv1:b (lv2:I) (lv2:II))
  7.   (lv2:I)
  8.   (lv2:II)
复制代码


性能差了点,耗时 600ms,不过比起写正则应该节省了不少调试的时间。
 楼主| 发表于 昨天 22:49 | 显示全部楼层
搞完野路子了,看看有没有正道。相信这种问题肯定有成熟的模块,AI 推的第一个模块:
Text::Balanced - Extract delimited text sequences from strings.

先看第一个极简示例
  1. use Text::Balanced qw(extract_bracketed);

  2. my $text = "(inside(lv2:(lv3:deep)))right";
  3. my ($extracted, $remainder) = extract_bracketed($text, '()');

  4. printf "%s\n", $extracted;
  5. printf "%s\n", $remainder;
复制代码

结果
  1. (inside(lv2:(lv3:deep)))
  2. right
复制代码


这个extract_bracketed 只负责提取从(括号起到右侧配对的)括号范围的内容,它不会返回带层次的结构体。
如果左侧带有其他字符例如 "abc(括号内的)..." 那么需要手动去掉左侧内容或者提取从左括号( 开始的子串交给 extract_bracketed 处理
接着让ai写了一个递归的版本,像剥洋葱一样处理
  1. #!/usr/bin/env perl
  2. use strict;
  3. use warnings;
  4. use Text::Balanced qw(extract_bracketed);

  5. my $text = "left (inside(lv2:(lv3:deep))) def(inside2(lv2:(lv3:deep1)(lv3:deep2))) right";

  6. print "orig: $text\n";
  7. print "-" x 50 . "\n";

  8. # 第一步:处理顶层的所有独立括号组
  9. # 因为字符串开头可能不是括号,或者中间有 "def" 隔开,我们需要循环查找
  10. process_level($text, 0);

  11. sub process_level
  12. {
  13.     my ($str, $level) = @_;
  14.    
  15.     # 缩进显示
  16.     my $indent = "  " x $level;
  17.    
  18.     # 只要字符串里还有左括号,就继续尝试提取
  19.     while ($str =~ /\(/)
  20.     {
  21.         # 找到第一个左括号的位置,截取子串供 extract_bracketed 使用
  22.         # 因为 extract_bracketed 必须从字符串开头就是括号才能工作
  23.         my $start_pos = index($str, '(');
  24.         my $substring = substr($str, $start_pos);
  25.         
  26.         # 执行提取
  27.         my ($extracted, $remainder) = extract_bracketed($substring, '()');
  28.         
  29.         if (defined $extracted)
  30.         {
  31.             # 1. 打印当前层级的完整括号内容
  32.             print "${indent}Lv$level: $extracted\n";
  33.             
  34.             # 2. 准备下一层级的数据:去掉最外层的括号
  35.             # 提取的内容格式是 "(...)", 我们取中间部分 [1 .. length-2]
  36.             my $inner_content = substr($extracted, 1, -1);
  37.             
  38.             # 3. 递归处理内部内容(进入下一层)
  39.             process_level($inner_content, $level + 1);
  40.             
  41.             # 4. 更新 $str,跳过刚才处理过的这部分,继续寻找同级的下一个括号
  42.             # 原字符串 = (括号前的垃圾) + (提取的部分) + (剩余部分)
  43.             # 我们需要保留:(括号前的垃圾) + (extract_bracketed 返回的 remainder)
  44.             # 注意:$substring 是从 $start_pos 开始的,所以 $start_pos 之前的部分要拼回去
  45.             my $prefix = substr($str, 0, $start_pos);
  46.             $str = $prefix . $remainder;
  47.             
  48.         }
  49.         else
  50.         {
  51.             # 理论上如果 =~ /\(/ 成立,extract_bracketed 应该能提取到,除非括号不匹配
  52.             warn "检测到左括号但无法提取完整结构,可能存在语法错误。\n";
  53.             last;
  54.         }
  55.     }
  56. }
复制代码


打印结果
  1. orig: left (inside(lv2:(lv3:deep))) def(inside2(lv2:(lv3:deep1)(lv3:deep2))) right
  2. --------------------------------------------------
  3. Lv0: (inside(lv2:(lv3:deep)))
  4.   Lv1: (lv2:(lv3:deep))
  5.     Lv2: (lv3:deep)
  6. Lv0: (inside2(lv2:(lv3:deep1)(lv3:deep2)))
  7.   Lv1: (lv2:(lv3:deep1)(lv3:deep2))
  8.     Lv2: (lv3:deep1)
  9.     Lv2: (lv3:deep2)
复制代码


耗时 200 ms
您需要登录后才可以回帖 登录 | 注册

本版积分规则

Archiver|手机版|小黑屋|批处理之家 ( 渝ICP备10000708号 )

GMT+8, 2026-3-26 03:26

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表