unless をリライトするために


## 発端

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

```ruby: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 の値を呼ぶ。

```perl: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ヶなのでごり押しのコード

```shell-session:
$ 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]]
```

これをソースコードに埋め込んで

```perl: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] ;
}
```

実行する。

```shell-session:
$ perl -l 5.pl
00000
```

## 改良

ついでに数も変えられる様に、`@d` 生成を `perl` で記載[^1]。

[^1]: `perl` にも `product` あればいいのに。`CPAN`? 面倒臭いから却下。

```perl: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進数を利用しない時。

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

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