LoginSignup
2
0
お題は不問!Qiita Engineer Festa 2023で記事投稿!

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

Last updated at Posted at 2023-07-04

はじめに

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

Paizaさんのエンタメ問題[リンク]の#8のコードゴルフに回答した際の備忘録です。

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

筆者のC++の回答を解説します。

PHPの回答先駆者1様のコードと同率にしか詰められなかったので今回は解説しません。
→回答詰めれた。後述

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

問題

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

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

解答例

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

解答例改
#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;
}

回答

(解説→ソースの順で記載)

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

入力部

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

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

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

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ならば、"#,#,#"である。

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.hiosを読み込めばいいので変更
3
- #include<stdio.h>
+ #import<ios>

標準ライブラリの3文字族ではios,set,mapで読み込める。

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

結論

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

結論
#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);
}

どなたか詰めてほしい。

追記

コメントにて藤田 望 氏に詰めていただきました。
添え字をconst charに持つことで短くできました。

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

追走
#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);
}

回答してきた。リンク
もっと、どなたか詰めてほしい。

追記2

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

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

追走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

コメントにてfuzzball 氏にコード・ご意見をいただきました。

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

追送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()-EOFscanf()+1putchar()に切り替えの後、
範囲for文に切り替えて短縮。
127文字+改行1。

追走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);
}

回答してきた。リンク
さすがにもうゴールだと思いますが、
引き続き、ご意見お待ちしております。

PHP編

おまけ。
execの返り値を使えるのが強い。
たぶんこれが一番短いと思います。リンク

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編

おまけ2。
たぶんこれが一番短いと思います。リンク

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

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編

おまけ3。
たぶんこれが一番短いと思います。リンク

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

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

やっぱりこれが一番短いと思います。リンク

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

追走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 氏にご意見いただきました。ありがとうございます。
いただいたコードをもとに、少し詰めました。
たぶんこれが一番短いと思います。リンク

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

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

追走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());
    }
}

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

  1. kebhr氏 回答リンク

2
0
8

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
2
0