はじめに
注:この記事は元サイトのプレゼントキャンペーン終了後に執筆しています。
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;
}
回答
(解説→ソースの順で記載)
ひとまずreturn
とendl
はなくても正解したため削除。
入力部
string
に読み込んでvector<vector<char>>
に1文字ずつ格納しているが、8ラインの計算をループで回せたほうが得だと女神が教えてくれたので、1次元であるchar[]
配列に変更する。
それならstring
をクッションにせず、scanf
で読み込めばいいので変更。
この時点でC言語相当になる。
入力から9文字を格納するため正しくはchar a[10]
ですが、
コードの文字数を重視してchar a[9]
としています。
#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ならば、"#,#,#"である。
#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
を読み込めばいいので変更
- #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)
でした。
両方試して通ったらヨシ!
(業務では書かないでください)
#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とすることで、一筆書きもどきで導出しております。
また、scanf
をfor
の初期式に含めました。
#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。
#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
$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が'###'のときに失敗する気がします。
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で抜けるようにし、回答のカウント変数として使用。
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桁で構成。
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
に右横書き文字を含んだので、コピペには注意。
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());
}
}
まだまだご意見お待ちしております。