読み取り不可、2199 2145 2134 2104 2087 2084バイト
両方をサポートk
/ j
など▲
/ ▼
構文。
読みやすくない伝統では、アポストロフィと二重引用符の区別をわかりにくくするために、プロポーショナルフォントでフォーマットされたプログラムがあります。
'"" "" ""' "" "" "" "" '"" "'" "'" "'" "'" "'" "" '"" "" "'" "" "" "'' "" '""' "" "" "'" ""' "" '"" "" ""' "" "" "" "" "" "" "" '"" "" ""' " "'" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""' "" "" "" "" "'"" ""' "" "" '"" "" ""' "" '""' "" '""' "" '"" "'" "'" ""' "" "" "" '""' "" '""' "" '"" "'" "" "" "" "" "" "" "" "" "" "" "" "'" "'" "''" "'" ""' "" "" "" "" '"" "" "" "" ""' "" '""' "" "" "" '"" "" "" "" """'" "'" "" "" '""' "" '"" "'" "" "" "" "" '"" "" "" "'" "'" "'" "'" " '""' "" "'" "" "" "" "'" "" "" "" '""' "" '""' "" '""' "" "'" "'" "' "" "" "" "" "'" ""' "" "" "" "" "'" "" "" "" "" "" "" "" "" "" ""'"" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "'" "" "" ""' '" "" '"" "" "" "" "'" "'" "" "" ""' "" "'" "" "" "" "" ""' "" '"" "" "" "" "" '"" "" "" "'" "'" "'" "" '""' "" "'" "" "" "" ""' "" "" "" "'""' "" '""' "" "" "'" ""' "" "'" "'" "'" ""' "" "" "" '"" "" "" "" " "" "" "" "" "" "" "" "" "" "" "" "" "'" "'" "" "" "" "" "" "" "" "" "" " "" "" '"" "" "'" "'" "'" "" "" "" '"" "'" "" '""' "" '"" "'" "'" ""'" "" "" "" "" "" "" "" "" ""' "" '""' "" '"" "" ""' "" "" "" "" '" "" "" "" "" "" "" "" '""' "" "" "'" "'" "" "" "" "" "" '"" "" "" "'" "'" "'" "" "" '"" "'" "" "'" "" "'" "" "" "" "" "" "" "" "'" "'" "'""' "" "'" "" "" "'" "'" "'" "'" ""' "" '"" "" "" "'" "'" "'" "'" " "'" "" "" "" "" "'" "'" "'" "" '""' "" "" "" "'" "'" "'" ""' "" "" "" '""' "" "'" "" "" "" "'" "" "" "" '""' "" "" "" "" "" "" "" "'" ""'"" "" "" "" "" "" "" "'" ""' "" "" '"" "" "" "" "" "" "'" "'" "" "" " '"" "" "" ""' "" "" "" "'" "" ""' "" '"" "" "" "" "" "" "" "" "" ""' " "'" "'" "" '"" "" ""' "" '""' "" "'" "" "" "" "" "" "" ""' "" '""'"" "'" "" '"" "" ""' "" '"" "'" "'" "" "" ""' "" '"" "'" "" "" "'" " '"" "" "" "'" "" "" "" "'" ""' "" '"" "" "" "'" "'" ""' "" "" "" "" "" "" "" '"" "" ""' "" '""' "" '"" "'" "'" "" "" "'" "'" "'" "'" ""'"" ""' "" "" "" '""' "" "'" "" "" "" "'" "" "" "" '""' "" "'" "" "" "" '""' "" '"" "'" "'" "" "" ""' "" '""' "" "" "" "" "" "" "" "" "'" "" "" "" "" "" "" "" '"" "" "" "'" "'" "" "" "" "" "" "" "" "" "'" "'"" "" "" "" "" "" "" "" "" "" "" "" "" '"" "" "" "'" "" "" "" "" "" "" " "'" "" "" "" "" "" "'" "" "" "" "" "" "" "" "" "" "" "" "" '"" "" "" "" '"" "'" "'" "" "" ""' "" "" "" "'" ""' "" "" "'" "'" "'" "" "" "'"" "" "" "" "" "" "" "" "" "" "" "" "'" "" "" "" "" "" ""' "" "" "" "" "'" "" "" "" "" "" "" "" "" "" ""' "" '""' "" "" "" "" "'" "'" "'" " '"" "" "" "" "" "" "" "" "" ""' "" "" "" "" "" "" "" "" "" "" "" "" ""'" "" "" "" "" "" "" "" "" "" "" ""' "" "" "" "" "" "'" "" "" ""' '" "" "" "" '"" "" "" ""' "" "'"' "" "" "" "" "" "" "" "" '"" "" "" ""' '" "" '""' "" '"" "" "" "'" "" '"" "" "" ""' "" "" "" "'" "'" "" "" ""'" "" "" "" ""' "" '""' "" "" "'" "'" "'" "'" "'" "'" "'" "'" "'" " '""' "" '""' "" '""' "" '""' "" "" "" "" "" "" "" '"" "" "" "" "" "" " "" '"" "'" "" "" "'" "" "" "" "'" "" '"" "" "" "" "" ""' "" "'"' """" "" "'" "'" ""
これは驚くべき挑戦でした。投稿していただきありがとうございます!
説明
Unreadableができることとできないことの感覚をつかむために、両方向に無限のテープを持つBrainfuckを想像してください。しかし、一度に1つのセルを移動するメモリポインターの代わりに、ポインターを逆参照することで任意のメモリセルにアクセスできます。これは、このソリューションでは非常に役立ちますが、モジュロを含む他の算術演算は手動で行う必要があります。
以下は、監督のコメント付きの擬似コードとしてのプログラムです。
// Initialize memory pointer. Why 5 will be explained at the very end!
ptr = 5
// FIRST PASS:
// Read all characters from stdin, store them in memory, and also keep track of the
// current line number at each character.
// We need the +1 here so that EOF, which is -1, ends the loop. We increment ptr by 2
// because we use two memory cells for each input character: one contains the actual
// character (which we store here); the other will contain the line number at which the
// character occurs (updated at the end of this loop body).
while ch = (*(ptr += 2) = read) + 1:
// At this point, ch will be one more than the actual value.
// However, the most code-economical way for the following loop is to
// decrement inside the while condition. This way we get one fewer
// iteration than the value of ch. Thus, the +1 comes in handy.
// We are now going to calculate modulo 4 and 5. Why? Because
// the mod 4 and 5 values of the desired input characters are:
//
// ch %5 %4
// ^ 1
// v 2
// k 3
// j 4
// ▲ 0 2
// ▼ 0 0
//
// As you can see, %5 allows us to differentiate all of them except ▲/▼,
// so we use %4 to differentiate between those two.
mod4 = 0 // read Update 2 to find out why mod5 = 0 is missing
while --ch:
mod5 = mod5 ? mod5 + 1 : -4
mod4 = mod4 ? mod4 + 1 : -3
// At the end of this loop, the value of mod5 is ch % 5, except that it
// uses negative numbers: -4 instead of 1, -3 instead of 2, etc. up to 0.
// Similarly, mod4 is ch % 4 with negative numbers.
// How many lines do we need to go up or down?
// We deliberately store a value 1 higher here, which serves two purposes.
// One, as already stated, while loops are shorter in code if the decrement
// happens inside the while condition. Secondly, the number 1 ('""") is
// much shorter than 0 ('""""""""'""").
up = (mod5 ? mod5+1 ? mod5+3 ? 1 : 3 : 2 : mod4 ? 3 : 1)
dn = (mod5 ? mod5+2 ? mod5+4 ? 1 : 3 : 2 : mod4 ? 1 : 3)
// As an aside, here’s the reason I made the modulos negative. The -1 instruction
// is much longer than the +1 instruction. In the above while loop, we only have
// two negative numbers (-3 and -4). If they were positive, then the conditions in
// the above ternaries, such as mod5+3, would have to be mod5-3 etc. instead. There
// are many more of those, so the code would be longer.
// Update the line numbers. The variables updated here are:
// curLine = current line number (initially 0)
// minLine = smallest linenum so far, relative to curLine (always non-positive)
// maxLine = highest linenum so far, relative to curLine (always non-negative)
// This way, we will know the vertical extent of our foray at the end.
while --up:
curLine--
minLine ? minLine++ : no-op
maxLine++
while --dn:
curLine++
minLine--
maxLine ? maxLine-- : no-op
// Store the current line number in memory, but +1 (for a later while loop)
*(ptr + 1) = curLine + 1
// At the end of this, minLine and maxLine are still relative to curLine.
// The real minimum line number is curLine + minLine.
// The real maximum line number is curLine + maxLine.
// The total number of lines to output is maxLine - minLine.
// Calculate the number of lines (into maxLine) and the real minimum
// line number (into curLine) in a single loop. Note that maxLine is
// now off by 1 because it started at 0 and thus the very line in which
// everything began was never counted.
while (++minLine) - 1:
curLine--
maxLine++
// Make all the row numbers in memory positive by adding curLine to all of them.
while (++curLine) - 1:
ptr2 = ptr + 1
while (ptr2 -= 2) - 2: // Why -2? Read until end!
*ptr2++
// Finally, output line by line. At each line, we go through the memory, output the
// characters whose the line number is 0, and decrement that line number. This way,
// characters “come into view” in each line by passing across the line number 0.
while (--maxLine) + 2: // +2 because maxLine is off by 1
ptr3 = 5
while (ptr -= 2) - 5:
print (*((ptr3 += 2) + 1) = *(ptr3 + 1) - 1) ? 32 : *ptr3 // 32 = space
ptr = ptr3 + 2
print 10 // newline
プログラムロジックについてはこれだけです。次に、これを判読不能に変換し、さらにいくつかの興味深いゴルフトリックを使用する必要があります。
変数は、読み取り不能で数値的に常に間接参照されます(例:のa = 1
ようになります*(1) = 1
)。数値リテラルの中には、他のものよりも長いものがあります。最短は1で、2が続きます。負の数がどれだけ長いかを示すために、-1〜7の数を次に示します。
-1 '""""""""'""""""""'""" 22
0 '""""""""'""" 13
1 '""" 4
2 '""'""" 7
3 '""'""'""" 10
4 '""'""'""'""" 13
5 '""'""'""'""'""" 16
6 '""'""'""'""'""'""" 19
7 '""'""'""'""'""'""'""" 22
明らかに、コード内で最も頻繁に発生する変数に変数#1を割り当てます。最初のwhileループでは、これは間違いなくmod5
であり、10回出現します。しかしmod5
、最初のwhileループの後はもう必要ないため、後で使用する他の変数に同じメモリ位置を再割り当てできます。これらはptr2
とptr3
です。これで、変数は合計21回参照されます。(発生回数を自分で数えようとする場合はa++
、値を取得するために1回と設定するために1回、2回などを数えることを忘れないでください。)
再利用できる変数は他に1つしかありません。モジュロ値を計算した後ch
は、もう必要ありません。up
そしてdn
そのいずれかで結構です、同じ回数を思い付きます。とマージch
しましょうup
。
これにより、合計8つの一意の変数が残ります。変数0〜7を割り当て、8でメモリブロック(文字と行番号を含む)を開始できます。しかし!7はコードの-1と同じ長さなので、変数-1〜6を使用してメモリブロックを7から開始することもできます。このように、メモリブロックの開始位置へのすべての参照はコードでわずかに短くなります。これにより、次の割り当てが行われます。
-1 dn
0 ← ptr or minLine?
1 mod5, ptr2, ptr3
2 curLine
3 maxLine
4 ← ptr or minLine?
5 ch, up
6 mod4
7... [data block]
これは、最上部の初期化を説明しています。これは、 7(メモリブロックの開始)-2(最初のwhile条件での必須増分)であるため、5です。同じことが、最後のループの他の2つのオカレンス5にも当てはまります。
0と4はコードの長さが同じでptr
あり、minLine
どちらの方法でも割り当てることができることに注意してください。...それともできますか?
2番目の最後のwhileループの謎の2はどうですか?これは6ではないでしょうか?データブロック内の数値を減らすだけですよね?6に達すると、データブロックの外にいるので、停止する必要があります。これは、バッファオーバーフローバグエラーエラーセキュリティ脆弱性です!
停止しないとどうなるか考えてください。変数6と4を減らします。変数6はmod4
です。これは最初のwhileループでのみ使用され、ここではもう必要ないため、害はありません。変数4はどうですか?変数4はどうあるべきですか、ptr
それともあるべきminLine
ですか?そうminLine
です、この時点ではもう使用されていません!したがって、変数#4はminLine
安全にデクリメントでき、ダメージを与えません!
更新1!の値の計算にまだ使用されているにも関わらず、マージdn
できることを認識してmod5
、2199バイトから2145バイトまでゴルフをしmod5
ましたdn
。新しい変数の割り当ては次のとおりです。
0 ptr
1 mod5, dn, ptr2, ptr3
2 curLine
3 maxLine
4 minLine
5 ch, up
6 mod4
7... [data block]
更新2!while ループで0にカウントさmod5
れると同じ変数になったため、明示的に0に初期化する必要がなくなったことを認識して、2145バイトから2134バイトにゴルフしdn
ましたmod5
。
更新3!2つのことを実現することにより、2134バイトから2104バイトまでゴルフしました。第一に、「負のモジュロ」のアイデアは価値がありましたmod5
が、同じ推論は当てはまりません。これは、などmod4
に対してテストしないためですmod4+2
。したがって、変更mod4 ? mod4+1 : -3
するmod4 ? mod4-1 : 3
と2110バイトになります。第二に、mod4
常に0または2であるmod4
ため、0の代わりに2に初期化し、2つの3成分を(のmod4 ? 3 : 1
代わりにmod4 ? 1 : 3
)逆にすることができます。
更新4!モジュロ値を計算するwhileループが常に少なくとも1回実行されることを認識して、2104〜2087バイトまでゴルフを行います。したがって、while --ch: [...]; up = (mod5 ? mod5+1 ? [...]
今ではなくup = ((while --ch: [...]) ? mod5+1 ? [...]
(そしてwhileループ内でmod4
最初に計算するので、これmod5
が最後のステートメントです)。
更新5!定数32
と10
(スペースと改行)を書き込む代わりに、(今は未使用の)変数#2に数値10を格納できることを認識して、2087バイトから2084バイトまでゴルフを行います(呼び出しましょうten
)。ptr3 = 5
書く代わりにten = (ptr3 = 5) + 5
、に32
なりten+22
、にprint 10
なりprint ten
ます。