ルモーリン
ホーム 更新 Perl Sample サービス 雑談 鉄ゲタ Linux リンク 連絡先

組み合わせを生成する

投稿:2020-11-06

こちらのツイートを拝見したので。

シェル(Unix/Linuxのコマンドプロンプト)限定です。 しかもPerlでないときたもんだ。 「え、なんでできるの!?」と思った方が少なからずいらっしゃると思います。 私もそう思いました(笑)。

printf "%s\n" {A,C,G,T}{A,C,G,T}{A,C,G,T}{A,C,G,T}

嘘じゃねえって、ホントにこれで出るんだって!

AAAA
AAAC
AAAG
AAAT
AACA
AACC
AACG
AACT
AAGA
AAGC
AAGG
AAGT
AATA
AATC
AATG
AATT
ACAA
ACAC
ACAG
ACAT
ACCA
ACCC
ACCG
ACCT
ACGA
ACGC
ACGG
ACGT
ACTA
ACTC
ACTG
ACTT
AGAA
AGAC
AGAG
AGAT
AGCA
AGCC
AGCG
AGCT
AGGA
AGGC
AGGG
AGGT
AGTA
AGTC
AGTG
AGTT
ATAA
ATAC
ATAG
ATAT
ATCA
ATCC
ATCG
ATCT
ATGA
ATGC
ATGG
ATGT
ATTA
ATTC
ATTG
ATTT
CAAA
CAAC
CAAG
CAAT
CACA
CACC
CACG
CACT
CAGA
CAGC
CAGG
CAGT
CATA
CATC
CATG
CATT
CCAA
CCAC
CCAG
CCAT
CCCA
CCCC
CCCG
CCCT
CCGA
CCGC
CCGG
CCGT
CCTA
CCTC
CCTG
CCTT
CGAA
CGAC
CGAG
CGAT
CGCA
CGCC
CGCG
CGCT
CGGA
CGGC
CGGG
CGGT
CGTA
CGTC
CGTG
CGTT
CTAA
CTAC
CTAG
CTAT
CTCA
CTCC
CTCG
CTCT
CTGA
CTGC
CTGG
CTGT
CTTA
CTTC
CTTG
CTTT
GAAA
GAAC
GAAG
GAAT
GACA
GACC
GACG
GACT
GAGA
GAGC
GAGG
GAGT
GATA
GATC
GATG
GATT
GCAA
GCAC
GCAG
GCAT
GCCA
GCCC
GCCG
GCCT
GCGA
GCGC
GCGG
GCGT
GCTA
GCTC
GCTG
GCTT
GGAA
GGAC
GGAG
GGAT
GGCA
GGCC
GGCG
GGCT
GGGA
GGGC
GGGG
GGGT
GGTA
GGTC
GGTG
GGTT
GTAA
GTAC
GTAG
GTAT
GTCA
GTCC
GTCG
GTCT
GTGA
GTGC
GTGG
GTGT
GTTA
GTTC
GTTG
GTTT
TAAA
TAAC
TAAG
TAAT
TACA
TACC
TACG
TACT
TAGA
TAGC
TAGG
TAGT
TATA
TATC
TATG
TATT
TCAA
TCAC
TCAG
TCAT
TCCA
TCCC
TCCG
TCCT
TCGA
TCGC
TCGG
TCGT
TCTA
TCTC
TCTG
TCTT
TGAA
TGAC
TGAG
TGAT
TGCA
TGCC
TGCG
TGCT
TGGA
TGGC
TGGG
TGGT
TGTA
TGTC
TGTG
TGTT
TTAA
TTAC
TTAG
TTAT
TTCA
TTCC
TTCG
TTCT
TTGA
TTGC
TTGG
TTGT
TTTA
TTTC
TTTG
TTTT

こちらの解説をごらんください。 シェルすげえな。

ようやくPerlで書く段になりました。 さあ、どうぞ。 で、何故シェルより短いんだ?

perl -E "say for <@{['{A,C,G,T}' x 4]}>"

この記事を書くためにシェルの展開とか調べると「シェルだと痒い所に手が届かないから何か良い物を」とPerlが作られた頃の雰囲気を感じました。 という通り、中カッコはシェルの挙動を模倣しています。 globの機能にしているのはシェルでもファイル名として展開する用途だったのでしょうか?(・_・ ) ( ・_・) その辺りは想像なんですが。

改行するsayを使いたいので-Eを使います。 -eではsayを使えません。 するとこんなコードになります。 シェバン、バージョン番号、文字コード、警告プラグマ、バッファなし指定は嘘です、付きません、私のサンプルのスニペットです(笑)。

#!/usr/bin/env perl

use v5.26;
use utf8;
use warnings;
use strict;

use Encode::Locale;

use feature "say";
use open IO => ":utf8";

binmode STDOUT, ":encoding(console_out)";
binmode STDERR, ":encoding(console_out)";

$| = 1;

say for <@{['{A,C,G,T}' x 4]}>;
perlrun - Perl インタプリタの起動方法 - perldoc.jp
-e commandline 1 行のプログラムを指定するのに使用します。 -e が指定されると Perl は引数のリストからはファイル名を探しません。 複数の -e コマンドで、複数行のスクリプトを構成することができます。 通常のプログラムでセミコロンを置くところには、セミコロンを使うことに 気を付けてください。 -E commandline -e と同様に振る舞いますが、暗黙に全てのオプション機能を(main コンパイル単位で)有効にします。 feature を参照してください。

sayは改行付きprintです。
「printと同様ですが、暗黙に改行が追加されます。」
Perlの組み込み関数 say の翻訳 - perldoc.jp
引数を指定しないので$_を使いますからprint "$_\n"になります。

#!/usr/bin/env perl

use v5.26;
use utf8;
use warnings;
use strict;

use Encode::Locale;

use feature "say";
use open IO => ":utf8";

binmode STDOUT, ":encoding(console_out)";
binmode STDERR, ":encoding(console_out)";

$| = 1;

print "$_\n" for <@{['{A,C,G,T}' x 4]}>;

後ろに付けたfor修飾子でループします。
「修飾子」 「for(each)修飾子は反復子です:LISTの値それぞれ毎に文を実行します(実行中は$_がそれぞれの値のエイリアスとなります)。」
perlsyn - Perl の文法 - perldoc.jp

#!/usr/bin/env perl

use v5.26;
use utf8;
use warnings;
use strict;

use Encode::Locale;

use feature "say";
use open IO => ":utf8";

binmode STDOUT, ":encoding(console_out)";
binmode STDERR, ":encoding(console_out)";

$| = 1;

for (<@{['{A,C,G,T}' x 4]}>) {
	print "$_\n";
}

世の中には一々名前の付いた変数に入れる事を好む人がいるので変数に入れておきましょう。 ただし、その変数はエイリアスだということを忘れないでください。 変数を更新する操作はループの要素を更新しますし、要素がリテラルの場合はエラー停止します。 あと$itemのレキシカルスコープはforの中だけで、forを抜けると消えます。

#!/usr/bin/env perl

use v5.26;
use utf8;
use warnings;
use strict;

use Encode::Locale;

use feature "say";
use open IO => ":utf8";

binmode STDOUT, ":encoding(console_out)";
binmode STDERR, ":encoding(console_out)";

$| = 1;

for my $item (<@{['{A,C,G,T}' x 4]}>) {
	print "$item\n";
}

文字列を乗法演算子xで複数回コピーできます。
「乗法演算子」
「二項演算子の"x"は繰り返し演算子です。スカラコンテキストまたは左辺値が括弧で括られていない場合は、左被演算子を右被演算子に示す数だけ繰り返したもので構成される文字列を返します。」
perlop - Perl の演算子と優先順位 - perldoc.jp

#!/usr/bin/env perl

use v5.26;
use utf8;
use warnings;
use strict;

use Encode::Locale;

use feature "say";
use open IO => ":utf8";

binmode STDOUT, ":encoding(console_out)";
binmode STDERR, ":encoding(console_out)";

$| = 1;

for my $item (<@{['{A,C,G,T}{A,C,G,T}{A,C,G,T}{A,C,G,T}']}>) {
	print "$item\n";
}

@{[~]}は便利な記法で、文法上いきなり~を書けず@を書ける所なら「@で始まってるからセーフ」という反則技です(いや、セーフだから)。 [~]は中にある要素をリストにしてそのリファレンスを表します。 @{~}は中にあるリファレンスをデリファレンスしたリストを表します。 なので結果は~になるという訳。 例えば"@{[1 + 2]}"が"3"になるのでお試しください。

#!/usr/bin/env perl

use v5.26;
use utf8;
use warnings;
use strict;

use Encode::Locale;

use feature "say";
use open IO => ":utf8";

binmode STDOUT, ":encoding(console_out)";
binmode STDERR, ":encoding(console_out)";

$| = 1;

for my $item (<'{A,C,G,T}{A,C,G,T}{A,C,G,T}{A,C,G,T}'>) {
	print "$item\n";
}

<~>の~がファイルハンドル以外であればglobを呼びます。
「I/O 演算子」
「山括弧の中の文字列がファイルハンドルでもファイルハンドル名、型グロブ、 型グロブリファレンスのいずれかが入った単純スカラ変数でもなければ、 グロブを行なうファイル名のパターンと解釈され、コンテキストによってファイル名のリストか、そのリストの次のファイル名が返されます。この区別は単に構文的に行われます。<$x>は常に間接ハンドルからreadline()しますが、<$hash{key}>は常にglob()します。$xは単純スカラー変数ですが、$hash{key}は違う(ハッシュ要素)からです。<$x >(余分な空白に注意)ですらreadline($x)ではなくglob("$x ")として扱われます。」
perlop - Perl の演算子と優先順位 - perldoc.jp

#!/usr/bin/env perl

use v5.26;
use utf8;
use warnings;
use strict;

use Encode::Locale;

use feature "say";
use open IO => ":utf8";

binmode STDOUT, ":encoding(console_out)";
binmode STDERR, ":encoding(console_out)";

$| = 1;

for my $item (glob('{A,C,G,T}{A,C,G,T}{A,C,G,T}{A,C,G,T}')) {
	print "$item\n";
}

中カッコを4個使っているので4重ループ処理が自動で行われます。
「空でない中かっこがglobで使われている唯一のワイルドカード文字列の場合、ファイル名とはマッチングせず、可能性のある文字列が返されます。」
Perlの組み込み関数 glob の翻訳 - perldoc.jp

#!/usr/bin/env perl

use v5.26;
use utf8;
use warnings;
use strict;

use Encode::Locale;

use feature "say";
use open IO => ":utf8";

binmode STDOUT, ":encoding(console_out)";
binmode STDERR, ":encoding(console_out)";

$| = 1;

for my $item ('AAAA', 'AAAC', 'AAAG','AAAT', ~ , 'TTTA', 'TTTC', 'TTTG', 'TTTT') {
	print "$item\n";
}