次の識別子を含むファイルがあります。
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)))
このファイルでは、識別子は3文字(クラスタ)で始まります。 A / B / Eで始まる識別子を別々のファイルに自動的に抽出したいと思います。各ファイルには、同じクラスタの識別子のみが含まれています。
同じ括弧内の識別子は同じグループに属します。例えば、((B#198094,B#176280),B#273119)
B#198094とB#196280は同じ内部グループに属し、B#273119と一緒に3つのグループがより大きいグループに属しています。つまり、識別子抽出プロセスでは括弧が重要です。
基本的に、私がアルゴリズム的に想像できるのは、括弧内のすべての識別子が同じクラスタ(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)
*同じクラスターの識別子が同じグループ(異常値)に配置されない可能性があるため、抽出出力ファイルに複数の行がある可能性があります。サンプルファイルに示すように、2つの識別子は、クラスタ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)
(...)
再帰的一致に使用されるため、ゼロ個以上のs以外のシーケンスを含むペア、または次のものを()
含む(...)
他のペアと一致すると言えます。(?:...)
キャプチャしていないバージョンです(...)
。グループ化専用です。++
+
(1つ以上ですが、逆追跡ではありません)の非逆追跡バージョンです。(?(?{code})pattern)
pattern
成功すると、正規表現が動的に挿入されますcode
。ここに(*FAIL)
aka(*F)
または を挿入して、(?!)
最初のキャプチャグループに一致する ABE 文字が 2 つ以上の同じ文字のシーケンスでない場合、今回は一致するものがないことを正規表現エンジンに通知します。
perldoc perlre
詳細より。
次に、これらの一致から文字を抽出し、一致を対応する出力ファイルに書き込みます。
慣れていない人のためにperl
:
perl -ln
は input の各行に対してsed
コード(ここに渡される)を実行するパターンです。ここで は のパターン空間と同じです。-e
$_
sed
m{regex}g
はい、代替構文です/regex/g
。リストコンテキストでは、すべてのキャプチャグループと一致するアイテムを別の要素(存在する場合)として返し、そうでない場合はすべての一致を返します(キャプチャグループが1つだけあり、一致するもの全体が含まれるため、これに違いはありません)。$_
テーマが指定されていない場合(使用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)