我有一些包含标识符的文件,如下所示:
B#205918
A#273075
E#554065
例如。文件 1 的示例:
((((A#273075,A#273116),((A#224325,A#192952),A#243232)),(((E#7955,E#7165),E#6239),E#4530)),(((((E#3075,E#3702),B#251221),E#35128),B#243275),((B#198094,B#176280),B#273119)))
在这个文件中,标识符仅从三个字母(簇)开始; A/B/E。我希望自动将以 A/B/E 开头的标识符提取到单独的文件中,其中每个文件仅包含同一集群中的标识符。
同一括号内的标识符属于同一组。例如,((B#198094,B#176280),B#273119)
B#198094 和 B#196280 位于同一内部组内,并且与 B#273119 一起,其中三个位于更大的组内。也就是说,括号在标识符的提取过程中确实很重要。
基本上,我可以在算法上想象的是,当括号内的所有标识符都以同一簇(A/B/E)中的标识符开头时,提取标识符以及包含它们的所有匹配的开括号和闭括号。
预期输出文件:
集群A:
((A#273075,A#273116),((A#224325,A#192952),A#243232))
集群B:
((B#198094,B#176280),B#273119)
簇 E*:
(((E#7955,E#7165),E#6239),E#4530)
(E#3075,E#3702)
*提取输出文件中可以不止一行,因为同一簇的标识符有可能不被放置在同一组中(异常值)——在示例文件中可以看出,这两个组簇 E 文件中的标识符没有被任何公共括号括起来,除了括住所有标识符的括号之外。
这是我迄今为止获得的聚类 A 提取结果:
grep -o "(*(A#.*)*" file1 | sed 's/,*E#.*//g'
但这不适用于在文件的不同部分中多次出现的簇,即本例中的簇 E。此外,它实际上并没有关注所提取的括号的数量,这将导致输出文件出现错误(开括号和闭括号的数量不同)。
sed
并且perl
命令对我不起作用。我尝试在每个逗号处拆分文件并提取以 E 开头的每个后续行(以提取 E 簇)。
sed 's/,/,\n/g' file1 | sed -n '/*E.*,\n(E/p'
sed -n ':begin;$!N;/*(E#.*\n*(E/p' file1
sed 's/,/,\n/g' file1 | perl -ane 'if(/.*E#.,\n*E#./ ... /^}/){$counter++ if /\(E#/; print if $counter==1}'
我有点迷失在这个过程里,并努力用最简单、最简短的方式强调这一点。如果有遗漏或部分不太清楚,请告诉我。
答案1
可能是这样的:
<file1 perl -lne '
for (m{(\((?:[^()]++|(?1))*\))(?(?{($1 =~ s/[^ABE]//gr) !~ /^(.)\1+$/})(*FAIL))}g) {
($cluster) = /([ABE])/;
open($out{$cluster}, ">", "cluster $cluster.txt") unless $out{$cluster};
print {$out{$cluster}} $_;
}'
这里使用了一些 Perl 的高级正则表达式运算符:
(?1)
用于递归匹配,因此我们可以说匹配一对(...)
包含 0 个或多个非 s 序列()
或另一对(...)
包含...的序列,依此类推。(?:...)
只是 的非捕获版本(...)
。仅用于分组。++
+
是(一个或多个,但不回溯)的非回溯版本。(?(?{code})pattern)
pattern
如果成功,则动态插入正则表达式code
。这里我们插入(*FAIL)
aka(*F)
or(?!)
来告诉正则表达式引擎,如果第一个捕获组匹配的 ABE 字母不是两个或多个相同字母的序列,则此时没有匹配项。
perldoc perlre
详情请参阅。
然后只需从这些匹配中提取字母并将匹配写入相应的输出文件中。
对于那些不熟悉的人perl
:
perl -ln
是针对输入的每一行运行sed
代码(此处传递给 )的模式,其中相当于 的模式空间。-e
$_
sed
m{regex}g
是 的替代语法/regex/g
。在列表上下文中,它返回所有捕获组匹配的内容作为单独的元素(如果有),否则返回所有匹配项(此处没有任何区别,因为只有一个捕获组并且包含整个匹配项)。$_
如果未指定主题(带有subject =~ m{...}g
),则适用。for (list) {code}
是for $var (list) {code}
循环遍历列表的元素,但不指定变量,因此默认为$_
./(ABE)/
与列表内容中的相同m{(ABE)}
(这里是对列表的赋值),这里没有g
, 返回捕获组匹配的内容(A、B 或 E 字母的第一次出现)。如果没有捕获组,它只会返回一个布尔值。$1 =~ s/[^ABE]//gr
应用s
替代(g
全局)并r
返回结果。因此,这里返回捕获组的内容,其中删除了除 ABE 字母之外的所有内容。
x
您可以通过使用允许插入空格和注释并命名捕获组的标志来使其更加清晰:
<file1 perl -lne '
for (
m{
(?<paren> [(] (?: [^()] ++ | (?&paren) ) * [)])
(?(?{ ($+{paren} =~ s/[^ABE]//gr) !~ /^(.)\1+$/ })(*FAIL))
}xg
) {
($cluster) = /([ABE])/;
open($out{$cluster}, ">", "cluster $cluster.txt") unless $out{$cluster};
print {$out{$cluster}} $_;
}'
答案2
一种方法是编写输入文件结构的语法。
perl -M5.010 -Mautodie -lne 'my $code =
sub($) {
qr{
((?&list))
(?(DEFINE)
(?<element> [$_[0]][#]\d+)
(?<value> (?:(?&element)|(?&list)))
(?<list> \((?&value)(?:,(?&value))*\))
) #DEFINE
}x; #qr
}; #sub
for my $v ( qw(A B E) ) {
my $re = $code->(quotemeta $v);
open my $fh, ">", "cluster_$v.txt";
select $fh;
print for grep(/\S/,/$re/g);
close $fh;
}' file
笔记:-
- 如果我们拉长并查看输入文件,它看起来如下:
sample of file1:
(
(
(
(A#273075,A#273116),
(
(A#224325,A#192952),
A#243232
)
),
(
(
(E#7955,E#7165),
E#6239
),
E#4530
)
),
(
(
(
(
(E#3075,E#3702),
B#251221
),
E#35128
),
B#243275
),
(
(B#198094,B#176280),
B#273119
)
)
)
- 所以我们看到它本质上是列表的集合或列表的列表。
- 我们的语法就是基于这一观察。
输出:- 创建文件cluster_[ABE].txt
,其组合输出如下。
((A#273075,A#273116),((A#224325,A#192952),A#243232))
((B#198094,B#176280),B#273119)
(((E#7955,E#7165),E#6239),E#4530)
(E#3075,E#3702)