Cでのゴルフのヒント


137

Cでゴルフをするための一般的なヒントは何ですか?私は、少なくともCに特有のゴルフ問題全般のコーディングに適用できるアイデアを探しています(たとえば、「コメントの削除」は答えではありません)。回答ごとに1つのヒントを投稿してください。また、ヒントがC89および/またはC99に適用される場合、および特定のコンパイラでのみ機能する場合も含めてください。


8
最大の単一文のヒントは、IOCCCに提出された受賞コードを読むことだと思います。
VSZ

回答:


107

ビットごとのXORを使用して、整数間の不等式をチェックします。

if(a^b)代わりにif(a!=b)1文字を保存します。


73
a-b同じ効果が得られます。
ugoren

22
同様a*bに、代わりに使用することができますa&&b(優先順位が異なる、悪い場合もそうでない場合もあります)。/ = -bがわかっている場合(例:符号なし)、a||b==a+b
walpen

3
?:(ifの代わりに)エルビス演算子と組み合わせることをお勧めします。異なる場合に何かを実行するための例: a^b?_diff_:;
Olivier Dulac

1
@OlivierDulac falseブランチの場合、空の3進数を受け入れるコンパイラはありますか?
ジョナサンフレッチ

1
@OlivierDulac確認できます。私が知っていることから、GCCが持つ?:にはちょうど同等であるオペレータa ? a : b
クロム

75
  • Abuse mainの引数リストを使用して、1つ以上の整数変数を宣言します。

    main(a){for(;++a<28;)putchar(95+a);}
    

    プログラミング言語のアルファベット回答)

    この解決策は、プログラムが引数なしで呼び出される場合、a(aka argc)として始まるという事実も悪用し1ます。

  • グローバル変数を使用して、物事をゼロに初期化します。

    t[52],i;main(c){for(;i<52;)(c=getchar())<11?i+=26:t[i+c-97]++;
    for(i=27;--i&&t[i-1]==t[i+25];);puts(i?"false":"true");}
    

    アナグラムコードゴルフへの答え


62

カンマ演算子を使用して、ブレースを回避しながら、1つのブロックで複数の式を実行できます。

main(){                                                                                     

int i = 0;                                                                                  
int j = 1;                                                                                  
if(1)                                                                                       
    i=j,j+=1,printf("%d %d\n",i,j); // multiple statements are all executed                                                  
else                                                                                        
    printf("failed\n");                                                                     

}

出力: 1 2


ステートメントの1つがの場合は機能しませんbreak
マキシムミハイロフ

9
@MaxLawnboy breakは声明であり、この答えは式に関するものです。
-NieDzejkob

59

壊滅的な関数引数の型宣言を避ける

5つの引数がすべてintsである関数を宣言している場合、人生は良いものです。あなたは簡単に書くことができます

f(a,b,c,d,e){

しかし仮定するdニーズがあることをchar、またはさえint*。その後、あなたはめちゃくちゃです!1つのパラメーターの前に型が付いている場合、それらはすべて次のようにする必要があります。

f(int a,int b,int c,int*d,int e){

ちょっと待って!役に立たないキャラクターのこの悲惨な爆発を回避する方法があります。こんなふうになります:

f(a,b,c,d,e) int *d; {

mainコマンドライン引数を使用する必要がある場合、これは標準宣言を節約します:

main(c,v)char**v;{

より2バイト短い

main(int c,char**v){

PPCGでこれまで出会ったことがないので、これを発見して驚いた。


6
なぜ地球上で機能するのですか?
ナサニエル14年

30
どうやらこれはK&Rスタイルと呼ばれ、ANSI Cに10年先行しています。
デニス14年

K&R機能と新しい(たとえば'99)機能を一緒に使用することはできないか、不可能なことに注意してください。コンパイラに依存します。
dmckee

5
@dmckeeは正しい。C99は暗黙的なintを許可しないため、使用する必要が-std=gnu99あり、今は移植性がありません。clc-speakでは、「C」コード自体を書いているのではなく、「Gnu99-C」を書いています。「ここではラウンドはほとんど無視していますが、コンパイラ固有のコードを投稿する場合は言及するのが良いでしょう。時には人々は実際にこれらのプログラムをダウンロードして実行ます。:)
luser droog

@luserdroog:を使用-std=c89して、gccまたはclangに古い標準に従ってコードをコンパイルするように指示できます。これにより、警告のみで暗黙的なintが許可されます。
ピーター

37

> =および<=の代わりに、比較される値がゼロよりも大きい場合に整数除算(/)を使用するだけで、1文字節約できます。例えば:

putchar(c/32&&126/c?c:46); //Prints the character, but if it is unprintable print "."

もちろん、これは、たとえば>と^だけを使用して(場合によっては&&や||と書くのを避ける賢い方法です)、まだ縮小可能です。

putchar(c>31^c>126?c:46);

整数除算のトリックは、たとえば、数字が100未満かどうかを判断するのに役立ちます。これにより、文字が節約されます。

a<100 vs 99/a

これは、より高い優先順位が必要な場合にも適しています。


あなたは書くことができますputchar(c>31&c<127?c:46);
ジンX

37

GCCなどの特定のコンパイラでは、の基本的な#includes、param、および戻り値の型を省略できますmain

以下は、GCCでコンパイル(警告付き)する有効なC89およびC99プログラムです。

main(i) { printf("%d", i); }

#includefor stdio.hが欠落していること、戻り値の型mainが欠落していること、および型宣言が欠落していることに注意してくださいi


17
mainは1つではなく0または2つのパラメーターを受け入れるため、技術的には標準に従って無効です。誰もがコードゴルフに関心があるわけではありません。
コンラッドボロスキー14年

printf()プロトタイプなしで呼び出す(または任意の可変引数関数)と、未定義の動作が発生します。GCCはデフォルトでは標準Cをコンパイルしません。C89モード(gcc -ansi -pedantic)またはC99モード(gcc -std=c99 -pedantic)でgccを呼び出すと、少なくとも後者の場合、かなりの苦情が寄せられます。
ニッセEngström14年

@NisseEngström:メインストリームC実装の呼び出し規約により、プロトタイプなしで可変引数関数を安全に呼び出すことができます。そのため、ほとんどのC実装は動作を定義します。
ピーター

29

三項条件演算子は?:、多くの場合、単純なためでスタンドとして使用することができますif- elseかなりの節約に文。

C ++の同等物とは異なり演算子は正式には左辺値を生成しませんが、一部のコンパイラ(特にgcc)はそれを回避できます。これは素晴らしいボーナスです。


追加:ifのみが必要で、elseは必要ない場合は、3項が依然として有用です。
ケーシー

9
&&||にも使用することができます:if(x==3)f()あなたの提案となりx==3?f():0、さらにに向上することができますx==3&&f()。ただし、演​​算子の優先順位に注意してください- f()をに置き換えた場合y=1&&ソリューションには追加の括弧が必要です。
ウゴレン

1
私は、gcc ?:が左辺値を生成することを理解していませんでした。本番コードで使用できますか?笑
ジェフバードジェス

4
@ugoren:x==3&&f()さらにゴルフすることができますx^3||f()
fgrieu

@fgrieu、はい、それはここのトピックそのものではありませんが(この答えはそれを示唆しています)。
ウゴレン

27

http://graphics.stanford.edu/~seander/bithacks.html

ビットがいいです。

~-x = x - 1
-~x = x + 1

しかし、優先順位は異なりますが、xを++や-のように変更しないでください。また、これは実際に特定のケースで使用できます:〜9は-10より短いです。

if(!(x&y)) x | y == x ^ y == x + y
if(!(~x&y)) x ^ y == x - y

それはもっと難解ですが、私はそれを使用する機会がありました。短絡を気にしない場合

x*y == x && y
if(x!=-y) x+y == x || y

また:

if(x>0 && y>0) x/y == x>=y   

5
最後のヒント((x/y) == (x>=y))は本当に便利です。
ウゴレン

24

ラムダを使用する(移植不可)

の代わりに

f(int*a,int*b){return*a>*b?1:-1;}
...
qsort(a,b,4,f);

または(gccのみ)

qsort(a,b,4,({int L(int*a,int*b){a=*a>*b?1:-1;}L;}));

または(ブロックをサポートするllvm)

qsort_b(a,b,4,^(const void*a,const void*b){return*(int*)a>*(int*)b?1:-1;});

のようなものを試してください

qsort(a,b,4,"\x8b\7+\6\xc3");

...ここで、引用符で囲まれた文字列には、「ラムダ」関数のマシン言語命令が含まれています(すべてのプラットフォームABI要件に準拠)。

これは、文字列定数が実行可能とマークされている環境で機能します。デフォルトでは、これはLinuxおよびOSXに当てはまりますが、Windowsには当てはまりません。

独自の「ラムダ」関数を書くことを学ぶ愚かな方法の1つは、Cで関数を記述し、それをコンパイルし、何かで検査しobjdump -D、対応する16進コードを文字列にコピーすることです。例えば、

int f(int*a, int*b){return *a-*b;}

... gcc -Os -cLinux x86_64ターゲット用にコンパイルすると、次のようなものが生成されます

0:   8b 07                   mov    (%rdi),%eax
2:   2b 06                   sub    (%rsi),%eax
4:   c3                      retq

GNU CC goto

これらの「ラムダ関数」を直接呼び出すことはできますが、呼び出しているコードがパラメーターを受け取らず、返されない場合はgoto、数バイトを節約するために使用できます。の代わりに

((int(*)())L"ﻫ")();

または(環境にアラビア語のグリフがない場合)

((int(*)())L"\xfeeb")();

試して

goto*&L"ﻫ";

または

goto*&L"\xfeeb";

この例でeb feは、x86マシン言語のようなものfor(;;);であり、パラメーターを使用せず、返されないものの簡単な例です:-)

goto呼び出し元の親に戻るコードを作成できることがわかりました。

#include<stdio.h>
int f(int a){
 if(!a)return 1;
 goto*&L"\xc3c031"; // return 0;
 return 2; // never gets here
}
int main(){
 printf("f(0)=%d f(1)=%d\n",f(0),f(1));
}

上記の例(Linuxでを使用してコンパイルおよび実行する場合がありますgcc -O)は、スタックレイアウトの影響を受けます。

編集:ツールチェーンによっては、-zexecstackコンパイルフラグを使用する必要がある場合があります。

すぐに明らかにならない場合、この答えは主に笑いのために書かれました。これを読んだことによるゴルフの良し悪しや悪い心理的結果について、私は一切責任を負いません。


2
C関数の一部を標準入力から読み取り、Cラムダを出力するスクリプト作成しました。あなたの答えに言及する価値があるかもしれませんが、あなたが私に最初にこれを行うことを教えたので、あなたにとって見るだけでいいかもしれません。
MD XF

23

ポインターの代わりにカーソルを使用します。brk()最初にスナッグし、それをベースポインターとして使用します

char*m=brk();

次に、メモリアクセス用に#defineを作成します。

#define M [m]

M*整数に適用される接尾辞になります。(古いa [x] == x [a]トリック。)

しかし、もっとあります!次に、ポインタ引数を使用して、マクロよりも短い関数で返すことができます(特に「return」を省略した場合)。

f(x){return x M;} //implicit ints, but they work like pointers
#define f(x) (x M)

ポインターからカーソルを作成するには、ベースポインターを減算し、intに切り捨てられるptrdiff_tを生成します。損失はyer bizです。

char *p = sbrk(sizeof(whatever)) - m;
strcpy(m+p, "hello world");

この手法は、型なしラムダ計算のインタプリタ作成するという私の答えで使用されます。


21

変数の代わりにパラメーターを定義します。

f(x){int y=x+1;...}

f(x,y){y=x+1;...}

実際に2番目のパラメーターを渡す必要はありません。

また、演算子の優先順位を使用して括弧を保存できます。
たとえば、に(x+y)*2なりx+y<<1ます。


またはx+y*2、さらに別の文字を保存します。
ブレーデンベスト14

4
@ B1KMusicは、x+y*2演算子の優先順位のため、同じではありません。
ウゴレン14

そうだね それはx +(y * 2)になります。x+y<<1として評価されていると仮定して、例に固執しx+(y<<1)*2代わりに提案しました。ビットシフト操作が次のように評価されることを知りませんでした(x+y)<<2
Braden Best 14

20

通常EOF == -1は、ビット単位のNOT演算子を使用してEOFを確認します:while(~(c=getchar()))またはwhile(c=getchar()+1)、すべての場所でcの値を変更します


1
私はCを十分に知りませんが、while(1+c=getchar())動作しませんか?
ɐɔıʇǝɥʇuʎs

6
@号は、加算演算子はɐɔıʇǝɥʇuʎs +代入演算子よりも優先順位が高い=ので、1+c=getchar()と等価である(1+c)=getchar()ので、コンパイルしない、(1+c)左辺値ではありません。
ace_HongKongIndependence

19

三項演算子?:は、2つの部分に分かれているという点で異常です。このため、標準の演算子優先順位ルールに少し抜け道があります。これは、括弧を避けるのに役立ちます。

次の例をご覧ください。

if (t()) a = b, b = 0;  /* 15 chars */

通常のゴルフのアプローチはifwith を置き換える&&ことですが、カンマ演算子の優先順位が低いため、追加の括弧が必要です。

t() && (a = b, b = 0);  /* still 15 chars */

ただし、三項演算子の中央セクションには括弧は必要ありません。

t() ? a = b, b = 0 : 0;  /* 14 chars */

同様のコメントが配列添え字に適用されます。


7
この例でb-=a=bは、さらに短くなっています。?:トリックは、まだ役に立つ-=も低い優先度を持っているので。
ウゴレン

いい視点ね; 私の例は不必要に複雑でした。
ブレッドボックス

もう一つのポイントは、時にはあなたが状態を反転したいということです:ためx>0||(y=3)x>0?0:(y=3)役に立たないですが、x<1?y=3:0仕事をしていません。
ウゴレン

clangとgccはどちらも、3進数で空のtrueケースを許可します。省略した場合、その値は条件の値です。たとえば、x>5?:y=1
クリスウズダヴィニス

19

コードの何回か繰り返される部分は、プリプロセッサで置き換える候補です。

#define R return

コードに複数の関数が含まれる場合、非常に一般的な使用例です。以下のような他の長めのキーワードwhiledoubleswitch、とcaseも候補です。同様に、コード内で偶像的なものもすべて。

私は通常、この目的のために大文字を予約しています。


1
短い交換はになります-DR=return。特定の文字を含める場合、defineを囲む一重引用符または二重引用符が必要になる場合があることに注意してください-DP='puts("hello")'

15

プログラムが各ステップごとに読み取りまたは書き込みを行っている場合は、常にgetchar()およびputchar()の代わりに読み取りおよび書き込み機能を使用してください

stdinを反転し、stdoutに配置

main(_){write(read(0,&_,1)&&main());}

演習:このテクニックを使用して、ここで良いスコアを取得します


各ステップごとにどういう意味ですか?
ケーシー

Casey:プログラムが何かを読んでいて、それを操作し、出力を書き込んでいるという意味だと思います。言うまでもなく、ストリーミング方式で。すべての入力を一度に読み取って処理する必要があるアプローチとは対照的です。
ジョーイ

ジョーイは正しい、私は同じことを意味し、今日まで受信トレイをチェックしなかった。
キクソティック

8
そのスタック操作は素晴らしいです。
アンドレアビオンド

14

逆ループ

可能であれば、交換してみてください

for(int i=0;i<n;i++){...}

for(int i=n;i--;){...}


12

ゼロ値への戻り値を使用します。何らかの関数を呼び出し、その関数が通常の状態でゼロを返す場合、ゼロが期待される場所にその関数を配置できます。同様に、関数が強打を追加してゼロ以外を返すことがわかっている場合。結局のところ、コードゴルフでは適切なエラー処理を行わないのですよね?

例:

close(fd);foo=0;   →  foo=close(fd);    /* saves two bytes */
putchar(c);bar=0;  →  bar=!putchar(c);  /* saves one byte  */

12

リターンの代わりに割り当てます。

これは実際には標準のCではありませんが、私が知っているすべてのコンパイラとCPUで動作します。

int sqr(int a){return a*a;}

次と同じ効果があります。

int sqr(int a){a*=a;}

最初の引数は戻り値と同じCPUレジスタに格納されるためです。

注:1つのコメントで述べたように、これは未定義の動作であり、すべての操作で機能することは保証されていません。そして、コンパイラの最適化はそれをスキップします。

Xマクロ

別の便利な機能:変数のリストがあり、それらすべてを含む操作を行う必要がある場合、X-Macrosが役立ちます。

https://en.wikipedia.org/wiki/X_Macro


3
私はこれを引用し、修正されました、これは単に真実ではありません。乗算と除算、および最適化がオフになっている場合にのみ機能します。これは、両方の操作で結果がeaxに返されるためです。パラメーターは、スタックまたはecxまたはedxに保存されます。自分で試してみてください。
Gaspa79

3
あなたは正しい、それは未定義の振る舞いであり、それはコンパイラとアーキテクチャにも依存します、私は通常このトリックを使って答えを投稿する前にx86とarmv7でgccをチェックします。そしてもちろん、最適化を有効にすると、スマートコンパイラは不要な乗算を削除するだけです。
GB

3
私はこの作品をGCCで見ましたが、他では見ませんでした
アルバートレンショー

1
@ Gaspa79:gccは-O0常に、戻り値レジスタ内の式を評価することを選択します。私は少なくとも(gcc.godbolt.orgで)x86、ARM、およびMIPSを見てきましたが、gccはでそれを行う方法から外れているようです-O0。あなたは、これを利用場合でも、覚えて、あなたがでプログラミングしている言語gcc -O0ないCのないCとして、あなたはそれに応じてあなたの答えにラベルを付ける必要があります-O0デバッグモード以外の最適化レベルでは失敗し、clang IIRCでは機能しません。
ピーター

11
  1. 配列の最初の要素にアクセスする*a代わりに使用しa[0]ます。

  2. 関係演算子(!=>など)は、0またはを与え1ます。これを算術演算子とともに使用して、条件がtrueまたはfalseであるかどうかに応じて異なるオフセットを指定しa[1+2*(i<3)]ますa[1]。if i >= 3およびa[3]elseにアクセスします。


11
a[i<3?3:1]は、より短い2文字ですa[1+2*(i<3)]
レトコラディ

10

IOCCCアーカイブ(国際難読化Cコードコンテスト)をご覧ください。

注目すべきトリックの1つは、展開に不均衡な中括弧/括弧があるマクロを#defineすることです。

#define P printf(

16
不一致の括弧は、それ自体には価値がありません。ポイントは、できるだけ多くの繰り返しパターンを定義することです。でさらに進んでください#define P;printf(
ウゴレン

これはどのようにバイト数を短縮しますか?おそらく例を提供しますか?
チョイス

2
@Cyoceたとえば、この回答を参照してください。
ジョナサンフレッチ

8

for(int i=0;i<n;i++){a(i);b(i);} いくつかの方法で短くすることができます:

for(int i=0;i<n;){a(i);b(i++);}ループの++最後iに 移動する場合は-1

for(int i=0;i<n;b(i++))a(i); -3さらに、1つの文を除くすべての文をメインループの最上部に移動し、ブレースを削除します


コンマ演算子を使用することは、場合によっては中括弧を回避する別の方法です。
ピーター

8

機能する!

同じシグニチャを持ち、単一の式として定義された単純な関数に問題を減らすことができれば#define r return、関数を定義するためのほぼすべての定型文よりも優れた結果を得ることができます。

#define D(f,...)f(x){return __VA_ARGS__;}
D(f,x+2)
D(g,4*x-4)
D(main,g(4))

プログラムの結果は、OSまたは制御シェルまたはIDEに返されるステータス値です。

を使用__VA_ARGS__すると、コンマ演算子を使用してこれらのfunction-expressionにシーケンスポイントを導入できます。これが不要な場合は、マクロを短くすることができます。

#define D(f,b)f(x){return b;}

7
  1. scanf("%*d ");ダミー入力の読み取りに使用します。(入力がさらなるプログラムで無意味である場合)scanf("%d",&t);、変数tを宣言する必要がある場所よりも短くなります。

  2. 文字をint配列に格納することは、文字配列よりもはるかに優れています。例。

    s[],t;main(c){for(scanf("%*d ");~(c=getchar());s[t++]=c)putchar(s[t]);}


2
実は、私が使用し%*d、それはまた、1つは、例えば、中に改行をスキップしたいと思うような状況で有用ですので、ゴルフだけでなくscanf("%[^\n]%*c",str);:)
tomsmeding

6

次の代わりに、文字を印刷してから復帰します。

printf("%c\n",c);

または

putchar(c);putchar('\n'); // or its ascii value, whatever!

単純に、cをintとして宣言し、次のようにします。

puts(&c);

9
これはおそらくリトルエンディアンのアーキテクチャに依存していることを指摘する価値があります。cがビッグエンディアンのintである場合、キャリッジリターンを取得するだけです。(一方、cがcharの場合、キャリッジリターンの代わりにランダムなガベージを取得する可能性があります。)
ブレッドボックス

@breadboxうん、あなたはまったく正しい。編集したばかりです。最後の抜粋では、cをintとして使用する必要があります(このように宣言するのは簡単です)。
モアラ

puts(&c)本当に仕事?それは必ずしもヌルで終わるわけではありません。
エソランジングフルーツ

1
@EsolangingFruit 32ビット整数のリトルエンディアンでは、整数0≤c <256はバイトシーケンスc 0 0 0として格納されます。cのアドレスをasとして解釈するとchar *、シングルトン文字列、つまり文字cの後にヌルバイトが続きます。
デニス

6

を使用asprintf()すると、明示的に割り当てられ、文字列の長さを測定することも節約されますchar*!これは、おそらくコードゴルフにはあまり便利ではありませんが、char配列を使用して日常の作業を容易にします。21世紀Cにはさらに良いアドバイスがあります。

使用例:

#define _GNU_SOURCE
#include <stdio.h>

int main(int argc, char** argv) {
  char* foo;
  asprintf(&foo, "%s", argv[1]);
  printf("%s",foo);
}

6

import あなたがする必要がある場合

最初の回答で述べたように、一部のコンパイラ(特にGCCとclang)では、#include標準ライブラリ関数のs を省略しても問題ありません。

を単に削除できない場合でも、それを回避する他の方法#includeがあるかもしれませんが、それは常に実用的または特にゴルフに適しているわけではありません。

残りのケースでは、#import<header file>代わりに#include<header file>を使用してバイトを保存できます。これはGNU拡張機能であり、非推奨と見なされますが、少なくともgcc 4.8、gcc 5.1、およびclang 3.7で機能します。


6

cpow()代わりに試してくださいcos()

の代わりに

double y=cos(M_PI*2*x);

のようなものを試してください

double y=cpow(-1,x*2);

これは、オイラーの式、少し複雑な分析、および複素数をdoubleに割り当てると実数部が得られるという観察を使用します(可変長関数呼び出しやその他の微妙さに注意してください)。

cos2πx+jsin2πx=ej2πx=ejπ2x=(1)2x

このタイプのトリックは、

double y=cpow(-1,x/2);

double y=cpow(1i,x);

(1)x2=j2x2=jx

LATEX


5

ここに、私が使ったいくつかのヒントを示します。私は恥知らずに他人からそれらを盗んだので、私以外の誰にでも信用:

割り当てと関数呼び出しを組み合わせる

これの代わりに:

r = /* Some random expression */
printf("%d", r);

これを行う:

printf("%d", r = /* Some random expression */);

複数の変数を一緒に初期化する(可能な場合)

これの代わりに:

for(i=0,j=0;...;...){ /* ... */ }

これを行う:

for(i=j=0;...;...){ /* ... */ }

ゼロ/非ゼロ値を折りたたむ

これは私がここにいる誰かから拾ったすてきなトリックです(覚えていないで、申し訳ありません)。整数値があり、それを1または0に折りたたむ必要がある場合!!、簡単に使用できます。これは、のような他の選択肢のために有利であることがあります?:

この状況を考えてみましょう。

n=2*n+isupper(s[j])?1:0; /* 24 */

代わりにこれを行うことができます:

n=n*2+!!isupper(s[j]); /* 22 */

もう一つの例:

r=R+(memcmp(b+6,"---",3)?R:0); /* 30 */

次のように書き換えることができます。

r=R+R*!!memcmp(b+6,"---",3)); /* 29 */

1
多分R*-~!!mxxxx
l4m2

5

基本的な論理的等式を知ることで、数バイトを節約できる場合があります。たとえば、代わりにif (!(a&&b)){}DeMorganの法則を使用してみてくださいif (!a||!b){}。同じことがビット単位の関数にも適用されます:~(a|b)doの代わりに~a&~b


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.