J - 181 190 170 CHAR
これは悪夢でした。それはただ私を悩ませ続けたので、私はそれを二度ゼロから書き直しました。これは、単一の文字列引数を取り、STDOUTに出力する関数です。
(0&$`((2{.{:@>&.>)((j{.]),-i@=`p@.~:~/@[,]}.~#@p+j=.0{p I.@E.])i 5;@}.&,'/';"0;&.>)@.(2<#)@}.[4:1!:2~{:@>@p=.>@{.@[)@((0;(0,:~1 0,.2);'\';&<1 0)<;._1@;:'/'&,)i=. ::](^:_)
説明するために、サブ表現に分割します。
i =. ::](^:_))
parse =: ((0;(0,:~1 0,.2);'\';&<1 0)<;._1@;:'/'&,)
print =: 4:1!:2~{:@>@p=.>@{.@[
eval =: 0&$`((2{.{:@>&.>)sub 5;@}.&,'/';"0;&.>)@.(2<#)@}.
sub =: ((j{.]),-i@=`p@.~:~/@[,]}.~#@p+j=.0{p I.@E.])i
interp =: (eval [ print) @ parse i
i
(iterateの略)は副詞です。左側の動詞引数を取り、動詞を返します。この動詞(f)i
は、引数に適用さf
れると、固定小数点(y = f y
)が見つかるか、エラーがスローされるまで、引数に繰り返し適用されます。固定小数点の動作はに固有^:_
であり::]
、エラー処理を行います。
parse
入力を半解析形式と呼ぶものにトークン化し、エスケープされていない「/」で切り取ります。エスケープされたバックスラッシュをキャラクターにバインドしますが、バックスラッシュを取り除きません。したがって、必要に応じて元に戻すか終了することができます。
興味深い作品の大部分はで発生し;:
ます。これはシーケンシャルマシンインタープリタープリミティブで(0;(0,:~1 0,.2);'\';&<1 0)
、左側にマシン()の説明を、右側に解析対象を記述します。これはトークン化を行います。この特定のマシンは、実際に最初の文字を特別なものとして扱い\
ます。これにはいくつかの理由があります。(1)状態テーブルが単純なので、さらにゴルフをすることができます。(2)問題を回避するために、ダミー文字を前面に簡単に追加できます。(3)ダミー文字は追加費用なしで半分解析されるので、次にそれを使用して切断フェーズのセットアップを行うことができます。
また<;._1
、エスケープされていないトークン化された結果をカットするために使用し/
ます(これが最初の文字になります)。これは、out/patt/repl/rest
すべての出力、パターン、置換を1つのステップで引き出すのに便利ですが、残念なことに、プログラムの残りの部分もカットさ/
れます。私は中にこれらのバックスプライスeval
作るので、<;._1
より多くの原価計算任せるそれらだけで終了します。
fork (eval [ print)
はprint
、parse
副作用の結果に対して実行され、実行されますeval
。print
は単純な動詞で、最初のボックス(確かに出力が確認されているボックス)を開き、解析を終了して、STDOUTに送信します。ただし、ユーティリティ動詞を定義することもできますp
。
p
はとして定義されている>@{.@[
ので、その左引数(引数を1つだけ指定した場合はIDのように動作します)、その最初の項目(スカラーを指定した場合はID)を取得し、それをアンボックスします(すでにボックス化されていない場合はアイデンティティ)。これは非常に便利ですsub
。
eval
処理されたプログラムの残りを評価します。完全なパターンまたは完全な置換がない場合、eval
それをスローし、空のリストを返すだけです。これは、次の反復で;:
(from parse
)エラーを出すことで評価を終了します。そうでない場合eval
は、パターンと置換を完全に解析し、ソースの残りを修正してから、両方をに渡しsub
ます。爆発により:
@}. NB. throw out printed part
@.(2<#) NB. if we have a pattern and repl:
2{. NB. take the first two cuts:
&.> NB. in each cut:
{:@> NB. drop escaping \ from chars
( ) NB. (these are pattern and repl)
&.> NB. in each cut:
; NB. revert to source form
'/';"0 NB. attach a / to each cut
&, NB. linearize (/ before each cut)
5 }. NB. drop '/pattern/repl/'
;@ NB. splice together
( sub ) NB. feed these into sub
` NB. else:
0&$ NB. truncate to an empty list
sub
置換の1つの(無限の可能性がある)ラウンドが発生する場所です。設定方法eval
により、ソースは正しい引数であり、パターンと置換は左側にまとめられています。引数はこのように順序付けられており、パターンと置換が置換のラウンド内で変更されないことがわかっているため、別の機能を使用することができますi
-正しい引数のみを変更し、同じ左に渡し続けるという事実- Jに状態の追跡を心配する必要性。
ただし、2つの問題点があります。1つ目は、J動詞は最大2つの引数を持つことができるため、ここではパターンや置換など、一緒にバンドルされているものにアクセスする簡単な方法がないことです。p
定義したユーティリティを賢く使用することで、これはそれほど大きな問題ではありません。実際に、私達はちょうど使用することにより、一つの文字パターンにアクセスすることができp
、そのにより、>@{.@[
左の引数の最初の項目のVHS版:定義。代替品を入手するのは難しいですが、最短の方法はp&|.
、手動で交換するよりも2文字短くなります。
2番目の問題は、i
永久にループするのではなく固定小数点で終了することです。パターンと置換が等しく、置換を行うと、Jに対する固定小数点のように見えます。1を否定する無限ループに入り、それらが等しいことを検出した場合:これは-i@=`p@.~:~/
、を置き換える部分p&|.
です。
p E.] NB. string search, patt in src
I.@ NB. indices of matches
0{ NB. take the first (error if none)
j=. NB. assign to j for later use
#@p+ NB. add length of pattern
]}.~ NB. drop that many chars from src
/@[ NB. between patt and repl:
~ NB. patt as right arg, repl as left
@.~: NB. if equal:
-i@= NB. loop forever
`p NB. else: return repl
(j{.]) NB. first j chars of src
, , NB. append all together
( )i NB. iterate
このサイクルは、の使用によりi
、sub
エラー以外の何かがなくなるまで繰り返されます。私が知っている限りでは、これはキャラクターが不足しているとき、またはパターンと置換の不完全なセットを捨てるときにのみ起こります。
このゴルフの楽しい事実:
- 一度使用する
;:
と、文字列を手動で反復するよりも短くなります。
0{
sub
無限ループに入る前にエラーが発生する可能性があるため、パターンが置換に一致してもソースの残りに表示されない場合は正常に動作するはずです。ただし、ドキュメントで引用を見つけることができないため、これは不特定の動作である場合とそうでない場合があります。おっと。
- キーボード割り込みは、実行中の機能内で自発的なエラーとして処理されます。ただし、の性質により
i
、これらのエラーもトラップされます。Ctrl + Cを押すタイミングに応じて、次のことができます。
sub
無限否定ループを終了し、数値を文字列に連結しようとすることでループからエラーを出し、文字列をそれ自体で無限に置換し終えたかのように///を解釈し続けます。
sub
中途半端のままにして、半サブブの///式の解釈に進みます。
- インタプリタから抜け出し、未評価の///プログラムをREPLに返します(ただし、STDOUTではありません)。
使用例:
f=:(0&$`((2{.{:@>&.>)((j{.]),-i@=`p@.~:~/@[,]}.~#@p+j=.0{p I.@E.])i 5;@}.&,'/';"0;&.>)@.(2<#)@}.[4:1!:2~{:@>@p=.>@{.@[)@((0;(0,:~1 0,.2);'\';&<1 0)<;._1@;:'/'&,)i=. ::](^:_)
f 'no'
no
f '/ world! world!/Hello,/ world! world! world!'
Hello, world!
f '/foo/Hello, world!//B\/\\R/foo/B/\R'
Hello, world!
f '//' NB. empty string
f '/\\/good/\/'
good
/-/World//--/Hello//--W/--, w/---!
愛してはいけないことは何ですか?(最後からダッシュを削除してみてください)