0
0

More than 3 years have passed since last update.

unless をリライトするために

Posted at

発端

現在見ている他人様のコードに、↓なコードがあるわけだ。

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

あとは、||&& にするとか、! を付けるとか、テストしたい条件文に合せて、その場その場で対応する。

ああ、これで、ストレス少なく、unlessif に変えられる。

蛇足

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
こちらだと、二桁にすると厳しい。


  1. perl にも product あればいいのに。CPAN? 面倒臭いから却下。 

  2. 最初は此方を書いてて、途中で 2進数で書けばいい事に思い当った。久しぶりのコードなので残す。 

0
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0