PaizaのコードゴルフにC++で回答した(PHPとPythonとJavaも)

# はじめに
注：この記事は元サイトのプレゼントキャンペーン終了後に執筆しています。

Paizaさんのエンタメ問題[[リンク](https://ppg.paiza.jp/code_review_bingo)]の#8のコードゴルフに回答した際の備忘録です。

コードゴルフ大会のはずが、嘘解法が流行ったことで（筆者の観測範囲内では）話題のイベントです。本記事では真面目に解きます。
記事の性質上、回答のネタバレになるため、自衛のほどオナシャス。

筆者のC++の[回答](https://ppg.paiza.jp/code_review_bingo/answers/9aba3616-997a-409a-a1b4-4e8c71255471/share)を解説します。

PHPの[回答](https://ppg.paiza.jp/code_review_bingo/answers/bd9b4af6-8161-4cd3-80be-ff9df8dfb9cc/share)は~~先駆者[^1]様のコードと同率にしか詰められなかったので今回は解説しません。~~
→回答詰めれた。後述
[^1]:kebhr氏 [回答リンク](https://ppg.paiza.jp/code_review_bingo/answers/9594dd53-5659-4cd7-b247-3a4bf015a828/share)

→Pythonも追加。
→Javaも追加。

# 問題
問題文略。3文字×3行を読み込み、1文字出力する。
出力最後の改行は、あってもなくても通るっぽい。

入力3行目の末尾の改行はあったりなかったりする模様[要確認]

# 解答例
解答例から、変数名を`a`=入力、`r`=出力に変換、空白を削除。

```cpp:解答例改
#include<iostream>
#include<vector>
#include<string>
using namespace std;
int main(){
    vector<vector<char>>a;
    for(int i=0;i<3;i++){
        vector<char>row;
        string line;
        getline(cin,line);
        for(char c:line){
            row.push_back(c);
        }
        a.push_back(row);
    }
    int r=0;
    r+=(a[0][0]=='#'&&a[0][0]==a[0][1]&&a[0][1]==a[0][2])?1:0;
    r+=(a[1][0]=='#'&&a[1][0]==a[1][1]&&a[1][1]==a[1][2])?1:0;
    r+=(a[2][0]=='#'&&a[2][0]==a[2][1]&&a[2][1]==a[2][2])?1:0;
    r+=(a[0][0]=='#'&&a[0][0]==a[1][0]&&a[1][0]==a[2][0])?1:0;
    r+=(a[0][1]=='#'&&a[0][1]==a[1][1]&&a[1][1]==a[2][1])?1:0;
    r+=(a[0][2]=='#'&&a[0][2]==a[1][2]&&a[1][2]==a[2][2])?1:0;
    r+=(a[0][0]=='#'&&a[0][0]==a[1][1]&&a[1][1]==a[2][2])?1:0;
    r+=(a[0][2]=='#'&&a[0][2]==a[1][1]&&a[1][1]==a[2][0])?1:0;
    cout<<r<<endl;
    return 0;
}
```

# 回答
（解説→ソースの順で記載）

ひとまず`return`と`endl`はなくても正解したため削除。

## 入力部
`string`に読み込んで`vector<vector<char>>`に1文字ずつ格納しているが、8ラインの計算をループで回せたほうが得だと女神が教えてくれたので、1次元である`char[]`配列に変更する。

それなら`string`をクッションにせず、`scanf`で読み込めばいいので変更。
この時点でC言語相当になる。

:::note alert
入力から9文字を格納するため正しくは`char a[10]`ですが、
コードの文字数を重視して`char a[9]`としています。
:::

```cpp:1
#include<stdio.h>
int main(){
    char a[9]={};
    scanf("%s%s%s",a,a+3,a+6);
    int r=0;
    r+=(a[0]=='#'&&a[0]==a[1]&&a[1]==a[2])?1:0;
    r+=(a[3]=='#'&&a[3]==a[4]&&a[4]==a[5])?1:0;
    r+=(a[6]=='#'&&a[6]==a[7]&&a[7]==a[8])?1:0;
    r+=(a[0]=='#'&&a[0]==a[3]&&a[3]==a[6])?1:0;
    r+=(a[1]=='#'&&a[1]==a[4]&&a[4]==a[7])?1:0;
    r+=(a[2]=='#'&&a[2]==a[5]&&a[5]==a[8])?1:0;
    r+=(a[0]=='#'&&a[0]==a[4]&&a[4]==a[8])?1:0;
    r+=(a[2]=='#'&&a[2]==a[4]&&a[4]==a[6])?1:0;
    printf("%d",r);
}
```

## 計算部
入力は下記の添え字で読み込んでいる。
> 012
345
678
>

8ラインの計算をループで回したい。
i(0<=i<3)とおくとラインごとの添え字の組は、
縦ラインを(3i,3i+1,3i+2)、横ラインを(i,i+3,i+6)で表せる。
また、斜めラインを(2i,4,8-2i)とする。

ここで(4,4,4)が"#,#,#"のとき、即ち4が'#'のときを余計に数えるため、4が'#'の場合は出力を-1する。
（他ユーザの回答を眺めてみるに、出力0が空白になってしまう件に並んで、忘れているユーザが多い模様）

3数が"#,#,#"であるかの判定について、
int('#')=35、int('.')=46であり、偶奇が異なる。
よって、3数の積を2で割った余りが1ならば、"#,#,#"である。

```cpp:2
#include<stdio.h>
int main(){
    char a[9]={};
    scanf("%s%s%s",a,a+3,a+6);
    int r=0;
    for(int i=0;i<3;i++)
        r+=a[3*i]*a[3*i+1]*a[3*i+2]%2+a[i]*a[i+3]*a[i+6]%2+a[2*i]*a[4]*a[8-2*i]%2;
    printf("%d",r-a[4]%2);
}
```

## その他
- `int main()`の`int`は、削ると通らない模様
- `stdio.h`は`ios`を読み込めばいいので変更


```diff_cpp:3
- #include<stdio.h>
+ #import<ios>
```
標準ライブラリの3文字族では`ios`,`set`,`map`で読み込める。

:::note warn
お手元のGCCの`-H`オプションでコンパイルして`stdio.h`が読み込まれることを確認してください。コンパイラに依ると思うが、通ったからヨシ！
:::

## 結論
多分これが一番短いと思います。
確認はできてません。
（他ユーザ回答は閲覧できる[要ログイン]が、検索できない・嘘解法が混ざっているため辛い）

```cpp:結論
#import<ios>
char a[9],i=3,r;
int main(){
    for(scanf("%s%s%s",a,a+3,a+6);i--;)
        r+=a[3*i]*a[3*i+1]*a[3*i+2]%2+a[i]*a[i+3]*a[i+6]%2+a[2*i]*a[4]*a[8-2*i]%2;
    putchar(48+r-a[4]%2);
}
```

どなたか詰めてほしい。

## 追記
[コメント](#comment-44e53732af3b97b0a465)にて藤田 望 氏に詰めていただきました。
添え字を`const char`に持つことで短くできました。

コメントから改変し、
(4,4,4)を余計に読む必要がなくなったため、出力を-1する機構が削られました。
また、`char a[]`の48～56に格納することで、`-48`を削減しました。
これにより、NULL終端の分を確保できるようになりました。やったね。
（1文字目がNULLになったので、そもそも確保する必要がないかも…）

```cpp:追走
#import<ios>
char a[58],i=8,r=48;
int main(){
    for(scanf("%s%s%s",a+r,a+51,a+54);i--;)
        r+=a["00031262"[i]]*a["13444475"[i]]*a["26857688"[i]]%2;
    putchar(r);
}
```

回答してきた。[リンク](https://ppg.paiza.jp/code_review_bingo/answers/418ce03f-ac09-4164-a33a-55a34f383680/share)
もっと、どなたか詰めてほしい。

## 追記2
[コメント](#comment-8653e8006a199244d45b)にてREI 氏にご意見いただきました。
各変数のメモリがスタック上で連続していることを利用しております。
今のところ上記と同率です。

:::note alert
メモリの確保はコンパイラの最適化に依るようです。
このコードでうちのGCCだと、`-O0`では順番に確保されるため`scanf(,&a)`ですが、
`-O1,-O2,-O3`だと逆順のため`scanf(,&i)`でした。
両方試して通ったらヨシ！
（業務では書かないでください）
:::

```cpp:追走2
#import<ios>
char a,b,c,d,e,f,g,h,i;
int main()
{
    scanf("%s%s%s",&a,&d,&g);
    //scanf("%s%s%s",&i,&f,&c);
    putchar(
         a%2*(b*c%2+d*g%2)
        +i%2*(g*h%2+c*f%2)
        +e%2*(d*f%2+b*h%2+a*i%2+c*g%2)
        +48
    );
}
```

引き続き、ご意見お待ちしております。

## 追記3
[コメント](#comment-52220ee664f0cb346ad9)にてfuzzball 氏にコード・ご意見をいただきました。

まず、fuzzball 氏から。
3文字ずつの判定対象を1つの文字列にし、
インクリメントを++,++,0とすることで、一筆書きもどきで導出しております。
また、`scanf`を`for`の初期式に含めました。

```cpp:追送3
#import<set>
char t[58]="01285341786308426",c=48,*p=t;
int main(){
    for(scanf("%s%s%s",t+c,t+51,t+54);*p;)
        c+=t[*p++]&t[*p++]&t[*p]&1;
    putchar(c);
}
```

## 追記4(現時点の記録)
続いて、紹介いただいた Ace310K 氏のコードから。
各ラインの成否を8bitでもち、初期値は0b11111111とし、
`if(i=='.') x&={109,119,122,0,175,180,187,0,206,215,217}[j];`
でドットがある度に0で上書きすることで、導出しております。
`+109`がミソで、9値を文字表記できる範囲に収めています。

`read()`と`write()`を、`scanf()-EOF`≒`scanf()+1`と`putchar()`に切り替えの後、
範囲for文に切り替えて短縮。
127文字+改行1。

```cpp:追走4
#import<ios>
int i,x=255;
int main(){
    for(int a:"\0\n\r_BGN_ajl")
        scanf("%c",&i),i-46?:x&=a+109;
    for(i=48;x;x/=2)
        i+=x&1;
    putchar(i);
}
```

回答してきた。[リンク](https://ppg.paiza.jp/code_review_bingo/answers/7ca50dc0-4e27-4dd9-b8a2-dacbc3a40f91/share)
さすがにもうゴールだと思いますが、
引き続き、ご意見お待ちしております。

# PHP編
おまけ。
`exec`の返り値を使えるのが強い。
たぶんこれが一番短いと思います。[リンク](https://ppg.paiza.jp/code_review_bingo/answers/842835bf-108e-4eb6-bf58-0a0839a0a03b/share)

```php:PHP
<?php
$b=bindec(exec("tr .# 10|tr -d '\n'"));
foreach([7,56,448,273,73,146,292,84]as$v)
    $c+=!($b&$v);
echo$c;
```

# Python編
おまけ２。
たぶんこれが一番短いと思います。[リンク](https://ppg.paiza.jp/code_review_bingo/answers/6b87b467-7e08-4972-b7b7-4f02fcea6d6c/share)

`a+join(for)`にしている人を見かけましたが、
入力3行目の末尾の改行はあったりなかったりするようなので、
891が'###'のときに失敗する気がします。

```python:Python
j=' '.join
a=j(open(0))
print((j(a[i::6-i]+' '+a[i*2::5]for i in(0,1,2))+a).count('###'))
```
# Java編
おまけ３。
たぶんこれが一番短いと思います。[リンク](https://ppg.paiza.jp/code_review_bingo/answers/78f9b380-1840-45a2-b85d-069d104e4867/share)

読み込んだ入力を改行か問わずにビットシフトして、`b`を11桁で構成。
ループ変数`i`を0で抜けるようにし、回答のカウント変数として使用。

```java:Java
interface Main{
    static void main(String[]a)throws Exception{
        int b=0,i=12;
        for(;--i>0;)
            b+=System.in.read()<36?1<<i-1:0;
        for(int e:new int[]{7,112,1792,273,546,1092,1057,292})
            if((e&b)==e)
                ++i;
        System.out.print(i);
    }
}
```

## 追走J1
やっぱりこれが一番短いと思います。[リンク](https://ppg.paiza.jp/code_review_bingo/answers/d5b5245b-94d4-4256-8352-4bfa8b0c3ecc/share)

上1桁をループカウント用にし、12桁で構成。

```java:追走J1
interface Main{
    static void main(String[]a)throws Exception{
        int b=1,x=0;
        for(;b<2048;b+=System.in.read()<36?1:0)
            b*=2;
        for(int e:new int[]{7,112,1792,273,546,1092,1057,292})
            if((e&b)==e)
                ++x;
        System.out.print(x);
    }
}
```

## 追走J2
jugon 氏にご意見いただきました。ありがとうございます。
いただいたコードをもとに、少し詰めました。
たぶんこれが一番短いと思います。[リンク](https://ppg.paiza.jp/code_review_bingo/answers/b5b952f6-3365-4016-ab97-852f6ba0d682/share)

`int`配列を`String`に変更。
それにより`int`では使えなかった4桁の数を使うように切り替え。
各ビンゴで使わないbitを立てた{255,1935,2040,955,1501,1774,990,1755}と、
読み込んだ入力とをORして、4095になれば1列です。

`String`に右横書き文字を含んだので、コピペには注意。

```java:追走J2
interface Main{
    static void main(String[]a)throws Exception{
        int b=1,c;
        for(;b<2048;)
            b+=b+System.in.read()%2;
        c=b;
        System.out.print("ÿޏ߸λםۮϞۛ".chars().map(d->(d|c)/4095).sum());
    }
}
```

まだまだご意見お待ちしております。