次の行が機能するプログラムを(説明付きで)記述する必要があります。
for(;P("\n"),R--;P("|"))for(e=C;e--;P("_"+(*u++/8)%2))P("| "+(*u/4)%2);
1行のみですが、C言語の理解の深さを判断するために使用できます。 この行はC ++でも機能します。 手を試してみることをお勧めします。 面白くないでしょう。 おそらく役に立つでしょう。
プログラマーとしてのキャリアの夜明けに、友人がCとUNIXがエイプリルフールのジョークであるという事実についての記事を見せてくれました。 上記のコード行は、言語の不条理の証拠として使用されました。 私の意見では、非常に機能しています。 インタビュー中にしばらくして、このジョークは記憶されました。 他の多くのテストと同様に、重要なのは結果であり(作業の目標を設定します)、分析と理解のプロセスです。
私が覚えていない記事をどこで手に入れたか。 「si and unix April Fools 'joke」というフレーズの検索エンジンで彼を見つけるたびに( たとえば、ここに )。 これらの再投稿では、「R」と「e」の後の増分で1マイナスが一度失われ、2番目のバックスラッシュが文字列「\ n」に現れました。
自分でタスクを把握してみてください。 今のところプログラムの意味を考えないでください。
書式設定は素晴らしい
改行、インデント、スペースを配置して、この1行の不名誉を読み取り可能な形式にすることを強くお勧めします。
通常のプログラムのテキストを見た場合、これは非常に簡単です。 目をスペースに近づけることはできますが、ループは異なるインデントの異なる行に配置する必要があります。
for ( ; P("\n"), R--; P("|")) for (e = C; e--; P("_" + (*u++ / 8) % 2)) P("| " + (*u / 4) % 2);
通常のプログラムのテキストを見た場合、これは非常に簡単です。 目をスペースに近づけることはできますが、ループは異なるインデントの異なる行に配置する必要があります。
「Hello world!」などの基本プログラムを作成する必要があります。
全世界に挨拶を表示する代わりに、タスク自体のテキストを挿入し、いくつかの変数を宣言する必要があります(これはさらに先になります)。
これについてはすでに議論できます。 含める必要があるのはなぜですか? そして、彼はここで必要ですか? 返品なしで可能ですか? そして非常に残酷な質問。 メイン関数のパラメーターは何ですか?
怠zyにならず、これらの質問に自分で答えようとしてください。
#include <stdio.h> int main() { ... for ( ; P("\n"), R--; P("|")) for (e = C; e--; P("_" + (*u++ / 8) % 2)) P("| " + (*u / 4) % 2); return 0; }
これについてはすでに議論できます。 含める必要があるのはなぜですか? そして、彼はここで必要ですか? 返品なしで可能ですか? そして非常に残酷な質問。 メイン関数のパラメーターは何ですか?
怠zyにならず、これらの質問に自分で答えようとしてください。
外部ループ解析
人がこの段階に成功した場合、2つのネストされたサイクルがあることをすでに理解しています。 外部のものを分析しましょう。
ここで、非常に単純な問題に出会います。 初期化子はありません(開き括弧の後にセミコロンがすぐに続きます)。 気になります。 これは、人が別の言語、たとえばPascalでプログラムを作成する場合によく起こります。
経験豊富なプログラマでさえ、本当の障害は「P( "\ n")、R--」という表現に出くわします。 多くの人は、そのような「コンマ」操作が存在すること、およびその作業の結果がコンマの後の式の結果であることを単純に知りません。 小数点の前の式も計算されますが、その結果は使用されません。 さらに、この操作の優先順位は最低です。 したがって、最初にP( "\ n")が実行され、次にR--が実行されます。
式R--の結果は、ここで履行の条件です。 この手法はよく使用されますが、これも一部を混乱させます。 多くのプログラマーは、if(a!= 0)のような条件付きifステートメントや式を記述する必要がないと感じています...同様のケースがあります(R--!= 0)。 最初の変数の宣言を追加します。 増分は、これは間違いなく実数ではないことを示しています。 整数型であれば、符号なしでも可能です。 この変数は宣言するだけでなく、正の値(できれば小さい値)で初期化する必要があります。
通常、ここに到達すると、文字列を入力として受け取る関数Pがあることは誰にとってもすでに明らかです。 問題ありません。 この関数を宣言する必要があります。 意味は私たちにとって重要ではないので、空であってもかまいません。 画面にテキストを表示する機能が好きです(ここでは手書きの#include <stdio.h>が便利です)。 私は、あらゆるレベルのプログラマーがこの関数を書くことができると信じています。
for ( ; P("\n"), R--; P("|"))
ここで、非常に単純な問題に出会います。 初期化子はありません(開き括弧の後にセミコロンがすぐに続きます)。 気になります。 これは、人が別の言語、たとえばPascalでプログラムを作成する場合によく起こります。
経験豊富なプログラマでさえ、本当の障害は「P( "\ n")、R--」という表現に出くわします。 多くの人は、そのような「コンマ」操作が存在すること、およびその作業の結果がコンマの後の式の結果であることを単純に知りません。 小数点の前の式も計算されますが、その結果は使用されません。 さらに、この操作の優先順位は最低です。 したがって、最初にP( "\ n")が実行され、次にR--が実行されます。
式R--の結果は、ここで履行の条件です。 この手法はよく使用されますが、これも一部を混乱させます。 多くのプログラマーは、if(a!= 0)のような条件付きifステートメントや式を記述する必要がないと感じています...同様のケースがあります(R--!= 0)。 最初の変数の宣言を追加します。 増分は、これは間違いなく実数ではないことを示しています。 整数型であれば、符号なしでも可能です。 この変数は宣言するだけでなく、正の値(できれば小さい値)で初期化する必要があります。
通常、ここに到達すると、文字列を入力として受け取る関数Pがあることは誰にとってもすでに明らかです。 問題ありません。 この関数を宣言する必要があります。 意味は私たちにとって重要ではないので、空であってもかまいません。 画面にテキストを表示する機能が好きです(ここでは手書きの#include <stdio.h>が便利です)。 私は、あらゆるレベルのプログラマーがこの関数を書くことができると信じています。
内部ループ分析
サイクルのすべてはすでにおなじみです。 上記のように、デクリメントはサイクルの実行時にチェックされます。 Rに似た変数eを追加します。同じ型の変数Cをすぐに宣言できますが、定数でも定義でもかまいません。 ここに著者の意志があります。
ここで興味深いのは、関数Pの呼び出しです。
さらに調べると、関数本体に同様の構造が表示されます。
我慢する価値はあります。 目標は近いです。 これがこの「傑作」の冠です。 急いで次の説明を開かないでください。
for (e = C; e--; P("_" + (*u++ / 8) % 2) )
サイクルのすべてはすでにおなじみです。 上記のように、デクリメントはサイクルの実行時にチェックされます。 Rに似た変数eを追加します。同じ型の変数Cをすぐに宣言できますが、定数でも定義でもかまいません。 ここに著者の意志があります。
ここで興味深いのは、関数Pの呼び出しです。
P("_" + (*u++ / 8) % 2)
さらに調べると、関数本体に同様の構造が表示されます。
P("| " + (*u / 4) % 2);
我慢する価値はあります。 目標は近いです。 これがこの「傑作」の冠です。 急いで次の説明を開かないでください。
ゼスト
2つの式を解析します。
次に、最初の式を検討します。 より複雑です。 ここでは、括弧内の式が最初に計算され、2で割った余りがそれから取得され、最後にこの番号が行に追加されることは明らかです。
最も単純なのは、除算の剰余の計算です。 時折、そのような操作を使用しないプログラマーがいます。 彼らは恥ずかしいかもしれません。 主なことは、この操作が整数型で実行され、結果も整数であることです。 独立した研究のための陰湿な質問、式(* u ++ / 8)%2の結果は否定的ですか?
括弧内の式の結果は整数でなければならないため、演算は整数除算と整数の被除数になります。 初心者プログラマの場合、式* u ++は不確実性を引き起こす可能性があります。式にポストインクリメントが存在し、ポストインクリメント操作を実行してポインタを逆参照する優先順位があります。 この手法は、配列を移動するときにCプログラムで使用されることがあります。 この式は、現在のポインター(増分前)で値を返し、ポインターを次の要素にシフトします。 したがって、変数uは単なるポインターではなく、配列でもあります。 追加の質問は、この配列のサイズ(要素単位)を指定することです。
最も「美しい」トリックは、文字列に数字を追加することです。 これはC言語であることを覚えておく必要があります。 数値から文字列への変換、さらには文字列から数値への変換を待たないでください。 すべては一見思われるよりもはるかに奇妙ですが、Cにとっては非常に論理的です。 文字列は文字の配列であり、最初の文字が置かれているメモリへのポインタを意味します。 これがポインターである場合、整数を追加することは、元のポインターに対して指定された要素数だけシフトされたアドレスを計算することを意味します。 この例では、2で割った余りを受け取った後、0または1が出力されます。したがって、文字列をオフセットなしで関数Pに渡す(そのまま)か、1文字を文字列の末尾にシフトします。 簡単な質問ですが、1文字で構成される行で1文字だけシフトするときに問題が発生する可能性があります(この場合のように)。
式(X / 8)%2はちょうど4番目のビットを取得しています。 符号なし整数の場合、これは(X >> 3)&1と同等です。そして、結論として、追加のタスクはこのステートメントで負の数をチェックすることです。
"_" + (*u++ / 8) % 2
"| " + (*u / 4) % 2
次に、最初の式を検討します。 より複雑です。 ここでは、括弧内の式が最初に計算され、2で割った余りがそれから取得され、最後にこの番号が行に追加されることは明らかです。
最も単純なのは、除算の剰余の計算です。 時折、そのような操作を使用しないプログラマーがいます。 彼らは恥ずかしいかもしれません。 主なことは、この操作が整数型で実行され、結果も整数であることです。 独立した研究のための陰湿な質問、式(* u ++ / 8)%2の結果は否定的ですか?
括弧内の式の結果は整数でなければならないため、演算は整数除算と整数の被除数になります。 初心者プログラマの場合、式* u ++は不確実性を引き起こす可能性があります。式にポストインクリメントが存在し、ポストインクリメント操作を実行してポインタを逆参照する優先順位があります。 この手法は、配列を移動するときにCプログラムで使用されることがあります。 この式は、現在のポインター(増分前)で値を返し、ポインターを次の要素にシフトします。 したがって、変数uは単なるポインターではなく、配列でもあります。 追加の質問は、この配列のサイズ(要素単位)を指定することです。
最も「美しい」トリックは、文字列に数字を追加することです。 これはC言語であることを覚えておく必要があります。 数値から文字列への変換、さらには文字列から数値への変換を待たないでください。 すべては一見思われるよりもはるかに奇妙ですが、Cにとっては非常に論理的です。 文字列は文字の配列であり、最初の文字が置かれているメモリへのポインタを意味します。 これがポインターである場合、整数を追加することは、元のポインターに対して指定された要素数だけシフトされたアドレスを計算することを意味します。 この例では、2で割った余りを受け取った後、0または1が出力されます。したがって、文字列をオフセットなしで関数Pに渡す(そのまま)か、1文字を文字列の末尾にシフトします。 簡単な質問ですが、1文字で構成される行で1文字だけシフトするときに問題が発生する可能性があります(この場合のように)。
式(X / 8)%2はちょうど4番目のビットを取得しています。 符号なし整数の場合、これは(X >> 3)&1と同等です。そして、結論として、追加のタスクはこのステートメントで負の数をチェックすることです。
特に、私はプログラムのテキストを提供しません。 すべてのプロンプトの後、このプログラムは簡単に作成できると思います。
これが架空の例だと思うなら、私は賭けることができます。 解析するのがはるかに難しいコードに出くわします。 そして、私があなたにそのような恐怖を書くように勧めるとは思わないでください。
そのようなテストに落ちる人のために:ここでの主なことは怖がらないことです、あなたはおそらくこのタスクなしで雇われるでしょう。
面接に使用したい人のために:面接のためにこのタスクを与えることに決め、申請者の目に笑顔が点滅する場合、それはあなたが両方ともこの投稿を読んでいることを意味します。 しかし、落胆しないでください、彼に説明で繰り返しさせてください...
読者向け:このタスクをすぐに実行したのは1人だけです(私ではありませんでした)。
PS
ありがとうnwalker :
「これはIOCCC 1985の受賞者の1人、 Carl Shapiroによるshapiro.cです。コードは標準出力に迷路を描きます。...検索プロセスで、Cの難読化と迷路についての興味深い投稿も見つけました。