発端
現在見ている他人様のコードに、↓なコードがあるわけだ。
tgt
unless hash.has_key?("1") || hash.has_key?("2") || hash.has_key?("3") || arr1.size == 0 || arr2.size == 0
....
end
実際には否定とか混ってたりする。テスト実行するにも、膨大なコードのど真ん中。
他にも複数条件の unless
文がけっこう、、、
俺のスカスカな脳味噌では、挙動が想像出来ない。お手上げ。
ググってみてもド・モルガンの説明なんて、精々 2つの条件で説明してる程度
仕方ないので、確認用の雛形スクリプトを作る。
コンセプト
まずは、2つの条件で。
条件式の代りに 0, 1 の値を呼ぶ。
2.pl
my @d = ( [0,0],[1,0],[0,1],[1,1] ) ;
for my $r ( @d ){
print join "", @{$r} unless $r->[0] || $r->[1] ;
}
$ perl -l 2.pl
00
unless A || B
の成立する条件は、一つ目の条件式が偽(0)、二つ目の条件式も偽(0)だと分る。つまり if ! A and ! B
本題
じゃ、tgt の場合は?
高々5ヶなのでごり押しのコード
$ ruby -e 'p [0,1].product([0,1],[0,1],[0,1],[0,1])'
[[0, 0, 0, 0, 0], [0, 0, 0, 0, 1], [0, 0, 1, 0, 0], [0, 0, 1, 0, 1], [0, 1, 0, 0, 0], [0, 1, 0, 0, 1],
[0, 1, 1, 0, 0], [0, 1, 1, 0, 1], [1, 0, 0, 0, 0], [1, 0, 0, 0, 1], [1, 0, 1, 0, 0], [1, 0, 1, 0, 1],
[1, 1, 0, 0, 0], [1, 1, 0, 0, 1], [1, 1, 1, 0, 0], [1, 1, 1, 0, 1]]
これをソースコードに埋め込んで
5.pl
# !/usr/bin/env perl
my @d = (
[0, 0, 0, 0, 0], [0, 0, 0, 0, 1],
[0, 0, 0, 1, 0], [0, 0, 0, 1, 1],
[0, 0, 1, 0, 0], [0, 0, 1, 0, 1],
[0, 0, 1, 1, 0], [0, 0, 1, 1, 1],
[0, 1, 0, 0, 0], [0, 1, 0, 0, 1],
[0, 1, 0, 1, 0], [0, 1, 0, 1, 1],
[0, 1, 1, 0, 0], [0, 1, 1, 0, 1],
[0, 1, 1, 1, 0], [0, 1, 1, 1, 1],
[1, 0, 0, 0, 0], [1, 0, 0, 0, 1],
[1, 0, 0, 1, 0], [1, 0, 0, 1, 1],
[1, 0, 1, 0, 0], [1, 0, 1, 0, 1],
[1, 0, 1, 1, 0], [1, 0, 1, 1, 1],
[1, 1, 0, 0, 0], [1, 1, 0, 0, 1],
[1, 1, 0, 1, 0], [1, 1, 0, 1, 1],
[1, 1, 1, 0, 0], [1, 1, 1, 0, 1],
[1, 1, 1, 1, 0], [1, 1, 1, 1, 1]
) ;
for my $r ( @d ){
print join "", @{$r} unless $r->[0] || $r->[1] || $r->[2] || $r->[3] || $r->[4] ;
}
実行する。
$ perl -l 5.pl
00000
改良
ついでに数も変えられる様に、@d
生成を perl
で記載1。
5.2.pl
# !/usr/bin/env perl
sub comb{
my $in = shift ;
# 0 から、2進数の組合せ -1 までの十進数を、0x回数埋めの 2進数に変換した後、
# 1文字ずつ分割。配列リファレンスにする。で、戻り値は、Array of Arrays
map{ [ unpack '(A)*', sprintf "%0${in}b", $_ ] } 0 .. 2 ** $in - 1 ;
}
my @d = comb $ARGV[0] ;
for my $r ( @d ){
# $ARGV[0] - 1 回も || を書くのは面倒臭いので、
# || で連結した文字列を、eval してそれを unless の引数にしてる。
# 実際には、検証対象に合せたコードに変更
print join "", @{$r} unless eval join '||', map{ $r->[$_] } 0 .. $ARGV[0] - 1 ;
}
$ perl -l 5.2.pl 12
000000000000
あとは、||
を &&
にするとか、!
を付けるとか、テストしたい条件文に合せて、その場その場で対応する。
ああ、これで、ストレス少なく、unless
を if
に変えられる。
蛇足
2進数を利用しない時。
sub comb {
my $in = shift ;
# ([0,0,0..])な配列を作る
my @d = ( [(0)x$in] ) ;
# キャッシュ用ハッシュ
my %seen ;
# @d を舐めるのだが、@d はループ内で push されて数が増えるので
# 抜ける条件は別に付ける
for my $r ( @d ){
# $r = [ x,x,x,x,x ... ]の要素を順番に 1 に変えて、
# 未知の組合せだったら、@d に追加
push @d, grep{ ! $seen{ join "", @{$_} } ++ }
map { my @k = @{$r} ; $k[$_] = 1 ; [@k] }
0 .. $in - 1 ;
# [全部 1 ] が最後の組合せ
last if +(join '', @{$d[-1]}) eq '1' x $in ;
}
return @d ;
}
お作法本的にはタブーなコード2。
こちらだと、二桁にすると厳しい。