「」に属する記事(最新5件のみ展開表示)

メイン

2007年03月16日

HaskellでKEMURIソルバーα0.2

HaskellでKEMURIソルバーができたけど、 「1つのPUSHで要求されたデータを作る」という条件が付いている。 まだ最小のコードを出力するとは限らない。 あと、1文字決定するのに11秒くらいかかる。 あ、「g」とかだと、メモリを1ギガ以上食ってものすごく時間がかかりそう。 ダメだなぁ、まだ枝刈りが必要だ。

__ 0.2にバージョンアップ。 0.1では「ROT ROTの後にはROTをおかない、DUPのあとにはDUPをおかない」というルールを入れていたのだけど、これでは「ROT DUP ROT DUP ...」でどんどん無駄に長いスタックを作れてしまう。そこで 「ROT ROTの後にはDUPをおかない、DUP ROTの後にはDUPをおかない、 DUP ROTの後にはROTをおかない」というルールを追加した。 厳密には「目的の文字列を出力するための最小の命令列にはこれらの命令列が含まれていない」ということを証明しないといけない。

でもとりあえずgも出力されるようになった。

(H): `^^^^^^^^^^'^"^^
(o): `^^^^''^"^^^^^^^^
(g): `^^^^^^'^''^"^^^^^
(J): `^^^^'^'^'^'^"^^^^^
Hより2ステップ長い。この2ステップがバージョン0.1では致命的だったのだ。

僕が以前手で書いたJをスタックに積むコードは

`"^^"^^^^"^^"^^^^^'"^^"^^'"^^"^^"'^"^^
なので、このソルバが出した
`^^^^'^'^'^'^"^^^^^
という解は約半分の長さ。

__ ダメだ。「u」の計算が終わらない。 まだまだだ。 アドホックな枝刈りでは解決できない。 冷静に考えてみよう。

スタックにn個積んである状態から、スタックに1個しかないじょうたいに行く過程では、 かならずn-1個の状態を通る。積まれた数が減る命令がXORだけだから。 というわけでn個からn-1個に変化する過程に注目しよう。

言い忘れたけども今回はNOTは使わないことにする。 証明していないけどもなくても構わないと思うので。

そうすると、ROTとDUPとXORの組み合わせで何ができるか、という話になる。 ROTで上3つを並び替えるのが一番深くまで触る命令だから、 スタックのそれより深い部分は無視していい。 つまりa b c -> ... -> x y。

ROTとXORとDUPの組み合わせでできる命令列のうち、 長さ3のスタック[1, 2, 3]を与えて実行すると スタックの長さが2になるような命令列を抽出(take 10)

^: Just [3,4]
'^: Just [6,1]
"^^: Just [2,4]
''^: Just [5,2]
"^'^: Just [6,0]
"'^^: Just [2,4]
'"^^: Just [4,1]
'''^: Just [3,4]
"^"^^: Just [2,4]
"^''^: Just [4,2]
見ての通り、ROT ROT ROT XOR は XOR と同じ結果になる。自明。 他にも DUP XOR XOR は DUP ROT XOR XORと同じ結果。

重複するものを取り除く。

^: Just [3,4]
'^: Just [6,1]
"^^: Just [2,4]
''^: Just [5,2]
"^'^: Just [6,0]
'"^^: Just [4,1]
"^''^: Just [4,2]
これが正しい道筋リスト。 ここで、1と2と4のXORで作ることのできる数は 0~7の8通りなので、 「スタックの長さが2になったときのスタックの中身」 は64通り。 重複したものを取り除いた後でtake 64する。

重複するものを取り除くところで下のようなことをしていたので無限ループに突入(苦笑)

uniq = foldl uniqAdd []

uniqAdd xs x = 
  if not(any (== (stack x)) (map stack xs)) then
    (x:xs)
  else
    xs
まぁ、十分な長さでtakeすることで、64本の「正しい道筋」リストが完成。 巻末に掲載。
= ソルバ0.2が作ったコードを見て思ったのだけど、 まず最初にXORを連打して捨てるべきなんだな。 探索の時も、まずPUSHで積んだ後、 XOR連打でスタックの長さが1になるまで縮めて、 ゴールかどうかを判定。 次にXOR連打で長さ2まで縮めて、下の3通りの「正しい方法」で1に縮めて ゴールかどうかの判定。
"^^: Just [2]
"'^"^^: Just [1]
"''"^^^: Just [0]

次にXOR連打で長さ3まで縮めてから、63通りの「正しい方法」で2に縮めて、 そこから3通りの正しい方法で1に縮めて判定。 …というような探索をすればいい気がする。解が見つかったら長さを枝かりに使う。

重要なのは「探索するごとにスタックの長さが1ずつ短くなる」ということ。 探索空間が有限になったので深さ優先で探索できる。 今度はそれを実装することにしよう。

ここまでやってもまだ最短ではないんだよなぁ。 1文字ずつ生成しているから。 例えば"d!"と表示するのに"d"と"!"を別々に作るより、 "Hello, world!"の最後の2文字を使って一度に2つ作る方が短いコードになるはず。


=

-- Kemuri ソルバ
import Data.Char
import Data.Bits

-- main = print $ start_kemuri "`|"

-- main = print $ showCode solve

-- main = print $ take 10 (bfs visit start)

-- main = print $ isGoal (Situation "" (km_xor (km_xor (km_dup (stack start)))))
-- main = putStr.show $ solve "H"

-- main = do input <- getContents
--          putStr (unlines (map format (map (:[]) input)))

main = putStr.format $ "g"

format goal =
  "(" ++ goal ++ "): " ++ (show (solve goal))

solve goal = (filter (isGoal goal) (bfs visit start)) !! 0

isGoal _ (Situation _ Nothing) = False
isGoal goal (Situation _ (Just x)) =
  x == map ord goal

showCode (Situation code _) = reverse code

bfs :: (a -> [a]) -> a -> [a]
bfs f = bfs' . (:[])
  where bfs' [] = []
        bfs' xs = xs ++ bfs' (xs >>= f)

visit :: Situation -> [Situation]
visit (Situation code Nothing) = []
visit (Situation code stack)
  = [Situation ('^':code) (km_xor stack)]
     ++ add_rot code stack ++ add_dup code stack

add_dup code stack@(Just xs)
  = if length xs < 15 then
      if head code == '"' then []
      else if take 2 code == "'\"" then []
      else if take 3 code == "''\"" then []
      else [Situation ('"':code) (km_dup stack)]
    else
      []

add_rot code stack
  = if take 2 code == "''" then
      []
    else if take 2 code == "\"'" then
      []
    else
      [Situation ('\'':code) (km_rot stack)]


data Situation =
  Situation {doneCode::String, stack::Stack}

instance Show Situation where
  show x = reverse $ doneCode x

start = Situation "`" (km_push (Just []))

operate_stack :: Stack -> Char -> Stack
operate_stack stack c = case c of
  '^' -> km_xor stack
  '~' -> km_not stack
  '\'' -> km_rot stack
  '"' -> km_dup stack
  '`' -> km_push stack
  _ -> stack

type KemuriResult = Maybe String

kemuri :: Stack -> String -> KemuriResult
kemuri stack (c:code)=
  case c of
    '|' -> if stack == Nothing then
              Nothing
           else
              add (km_mkstr stack) (start_kemuri code)
                where add (Just x) (Just y) = Just (x ++ y)
                
    c   -> kemuri (operate_stack stack c) code

kemuri Nothing _ = Nothing
kemuri _ []= Just ""

start_kemuri :: String -> KemuriResult
start_kemuri code = kemuri (Just []) code

type Stack = Maybe [Int]

km_xor :: Stack -> Stack
km_xor (Just (x:(y:rest))) = Just ((xor x y):rest)
km_xor x = Nothing

km_not :: Stack -> Stack
km_not (Just (x:rest)) = Just ((complement x):rest)

km_rot :: Stack -> Stack
km_rot (Just (x:(y:(z:rest))))  = Just (y:z:x:rest)
km_rot x  = Nothing

km_dup :: Stack -> Stack
km_dup (Just (x:rest)) = Just (x:x:rest)

km_push :: Stack -> Stack
km_push (Just xs) = Just (map ord "Hello, world!" ++ xs)

km_mkstr :: Stack -> KemuriResult
km_mkstr (Just xs) = Just (map chr xs)
km_mkstr Nothing = Nothing

= スタック[1, 2, 4]から長さ2のスタックへの「正しい道筋」リスト。
^: Just [3,4]
'^: Just [6,1]
"^^: Just [2,4]
''^: Just [5,2]
"^'^: Just [6,0]
'"^^: Just [4,1]
"^''^: Just [4,2]
"'^'^: Just [5,3]
'"^'^: Just [5,0]
''"^^: Just [1,2]
"^'"^^: Just [4,0]
"'^"^^: Just [1,4]
"'^''^: Just [7,1]
'"'^'^: Just [3,6]
''"^'^: Just [3,0]
"^''"^^: Just [0,2]
""^'^'^: Just [4,3]
"''"^^^: Just [0,4]
'"^'"^^: Just [1,0]
'"'^"^^: Just [2,1]
'"'^''^: Just [7,2]
''"'^'^: Just [6,5]
"^'"'^'^: Just [2,6]
"^''"^'^: Just [2,0]
""^'^''^: Just [7,0]
"'^''"^^: Just [3,1]
"''"^^'^: Just [5,1]
'""^'^'^: Just [1,6]
'"''"^^^: Just [0,1]
''"'^''^: Just [7,4]
"^'"^'"^^: Just [0,0]
"^'"'^''^: Just [6,2]
"^''"'^'^: Just [6,4]
"'^"'^''^: Just [6,3]
"'^'"'^'^: Just [2,5]
'"^'"'^'^: Just [4,5]
'"''"^^'^: Just [3,2]
"^'""^'^'^: Just [0,6]
""^'^"'^'^: Just [7,3]
"'^'"'^"^^: Just [1,3]
"'^''"'^'^: Just [5,7]
'"^'"'^''^: Just [5,4]
'"'^"'^''^: Just [5,6]
"^'"^'"'^'^: Just [4,4]
"^'"''"^^'^: Just [2,2]
"'^"^'"'^'^: Just [1,5]
"'^"'^''"^^: Just [2,3]
"'^'""^'^'^: Just [3,5]
"'^'"''"^^^: Just [0,3]
"''"^^''"^^: Just [1,1]
'"^'""^'^'^: Just [0,5]
'""^'^"'^'^: Just [7,6]
'"'^''"'^'^: Just [3,7]
"^"'^''"'^'^: Just [4,6]
""^'^''"'^'^: Just [4,7]
"'^"'^'"'^'^: Just [1,7]
''""^'^"'^'^: Just [7,5]
''"'^''"'^'^: Just [6,7]
"^'""^'^"'^'^: Just [6,6]
""^'^"'^''"^^: Just [3,3]
"''"^^''"'^'^: Just [5,5]
'"'^"'^'"'^'^: Just [2,7]
""^'^"'^'"'^'^: Just [0,7]
""^'^"'^''"'^'^: Just [7,7]
スタック[1, 2]から長さ1のスタックへの「正しい道筋」リスト。
^: Just [3]
"^^: Just [2]
"'^"^^: Just [1]
"''"^^^: Just [0]

HaskellによるKEMURIインタプリタ

HaskellでKEMURIのインタプリタを書いてみた。 KEMURIとは - はてなダイアリー

超絶短くてびっくり。西尾泰和のブログ: KEMURIのPythonで書かれたのの約半分の行数。 実装時間の半分以上はビット演算と文字から文字コードに変換する方法を調べるのに費やされた。kemuri関数の中身は割とくだらないことをしているので、たぶんもっと短くスマートに書く方法があるだろうと思う。

-- Kemuri インタプリタ
import Data.Char
import Data.Bits

main = getContents >>= putStr.(\code -> kemuri code [])

kemuri :: String -> [Int] -> String
kemuri ('|':code) stack = km_mkstr stack ++ kemuri code []
kemuri ('^':code) stack = kemuri code $ km_xor stack
kemuri ('~':code) stack = kemuri code $ km_not stack
kemuri ('\'':code) stack = kemuri code $ km_rot stack
kemuri ('"':code) stack = kemuri code $ km_dup stack
kemuri ('`':code) stack = kemuri code $ km_push stack
kemuri (c:code) stack = kemuri code stack
kemuri [] _ = ""

km_xor (x:(y:rest)) = (xor x y):rest
km_not (x:rest) = ((complement x):rest)
km_rot (x:(y:(z:rest)))  = (y:z:x:rest)
km_dup (x:rest) = (x:x:rest)
km_push xs = map ord "Hello, world!" ++ xs
km_mkstr xs = map chr xs

__ KEMURIソルバーを作りたかったのだけど、難しかった。幅優先探索と枝刈りをどう実装したらいいのか。 「探索待ち行列に追加」なんてのはリストの破壊的操作ができないから無理だし。リストを順に受け渡していくのか?でも高速な(x:xs)での結合では頭に入れて頭から出してしまうからキューにならないし、 素直にキューにしたらリストの操作だけでかなり重たそう…。


__ Programming:玉手箱:その他。おおー、なんかすごく短い幅優先探索のコードがあった。


__ 捨てるのがもったいないので転載。^'"の3文字からなる文字列を順に生成するリスト。

all_code = foldr1 (++) (iterate f (map (: []) "^'\""))
f xs = [y:x | x <- xs, y <- "^'\""]

2006年09月28日

どの文字をXORすれば目的の文字になるか?

とりあえず「Hello, world!」のどの文字をXORすれば目的の文字になるかを載せておきます。ここに書かれている以外の組み合わせでも目的の文字を作ることが出来る可能性は高く、その「別解」の方がコードが短くなる可能性も否定できません。そのあたりの研究はまだ手つかずです。

続きを読む "どの文字をXORすれば目的の文字になるか?" »

2006年09月25日

KEMURI

This entry is an introduction of new programming language. Here is Japanese version.

KEMURI (means 'smoke' in Japanese) is proposed by NISHIO Hirokazu and OBINATA Daichi in the 39th symposium for young researcher of information science (jouhou kagaku wakate-no-kai in Japanese).

This is a code to print "Just Another Python Hacker," in KEMURI. Notice it is not the shortest code to print that. "^^"^^ can be replaced by ^"^^.

`"^^"^^^^"^^'"^^"^^'"^^"^^'"^^"^^"'"^^"^^`"^^"^^^^"^^"^^"^^"^^"^^'"^^"^^"'"^^"^^
`"^^'^^'"^^"^^'"^^"^^'"^^"^^'"^^"^^"'"^^"^^`'"^^"^^^^'"^^^'"^^"^^'"^^"^^'"^^"^^`
"^^"^^^^^^'"^^"^^'"^^"^^'"^^"^^`"^^"^^"^^'"^^^'"^^"^^'"^^"^^'"^^"^^^`'"^^"^^'"^^
"^^'"^^"^^'"^^"^^'"^^"^^'"^^"^^`"^^"^^^^"^^"^^'"^^"^^'"^^"^^'"^^"^^`"^^"^^^^'"^^
^'"^^"^^'"^^"^^'"^^^`"^^"^^^^'"^^"^^'"^^"^^'"^^"^^'"^^"^^`'"^^''^^'"^^"^^^'"^^"^
^'"^^"^^'"^^"^^`"^^"^^^^"^^"^^"^^^'"^^^'"^^"^^`"^^"^^^^"^^"^^"^^"^^''"^^^^'"^^"'
`'"^^"^^'"^^"^^'"^^"^^^^'"^^"^^'"^^"^^"'`"^^"^^^^"^^"^^'"^^"^^'"^^"^^'"^^"^^'"^^
"^^`"^^"^^^^"^^"^^"^^"^^"^^'"^^"^^"'"^^"^^`"^^'^^'"^^"^^'"^^"^^'"^^"^^'"^^"^^"'"
^^"^^`'"^^''^^'"^^"^^^'"^^"^^'"^^"^^'"^^"^^`"^^"^^^^"^^"^^"^^^'"^^^'"^^"^^`"^^"^
^^^'"^^"^^'"^^"^^'"^^"^^'"^^"^^`"^^"^^^^'"^^^'"^^"^^'"^^"^^'"^^^`^^'"^^"^^'"^^"^
^'"^^"^^'"^^"^^'"^^"^^`"^^"^^^^"^^"^^'"^^"^^'"^^"^^'"^^"^^`"^^"^^^^"^^"^^"^^^'"^
^^'"^^"^^`"^^"^^^^"^^"^^'"^^"^^^'"^^"^^^`'"^^"^^'"^^"^^'"^^^'"^^^^'"^^''`"^^"^^^
^"^^"^^^^^'"^^"^^'"^^"^^"'^"^^|

KEMURI is a Stack machine. You can push byte values (from 0 to 255) into stack. Each letter in KEMURI code is a command. There are only 6 commands in KEMURI syntax. The ^ pops two value from the stack and calculates XOR and pushes the result. The " duplicate the top value of the stack, that is, it pops a value and pushes itself twice. The ' pops three values x, y, z and pushes x, z, y. In other word, it rotates the top three values of the stack from xyz to yzx. The ~ pops a value and calculate NOT and pushes the result. The only command to push constant values into the stack is the `. It pushes 13 values 33, 100, 108, 114, 111, 119, 32, 44, 111, 108, 108, 101, 72 in this order. The | prints all values in the stack to standard output. It is strongly recommended to use it only once at the tail of your code.

To print "Hello, world!", you just type two keys as below. It is the shortest code to print "Hello, world!" all over the world, except for HQ9+.

`|

KEMURI has many advantages. It can print any character, which HQ9+ can't. It has only 6 commands so it is easier to learn Brainf*ck.

"l"(small L) and "*"(asterisk) are reserved for possibility to use as a command "Execute the stack as Brainf*ck" in future.

I wrote a KEMURI interpreter in Python.

続きを読む "KEMURI" »

2006年09月19日

KEMURI

第39回情報科学若手の会の夜のセッションでのディスカッションから生まれた言語「KEMURI」を紹介します。

これはKEMURIで「Just Another Python Hacker,」と表示するコードです。

KEMURIはスタックマシンです。 スタックには0~255の値が積まれます。 1つの文字が1つの命令になっています。命令は全部で6つです。 「^」はスタックから2つの値を取り出し、XORを計算してスタックに積みます。「"」はスタックから1つの値を取り出し、その値を2つ積みます。つまりスタックの頭からxyzの順で並んでいたらxxyzにします。 「'」はスタックから3つの値x, y, zを取り出し、x, z, yの順で積みます。つまりスタックの頭からxyzの順で並んでいたらyzxの順に並び替えます。 「~」はスタックから1つの値を取り出し、NOTを計算してスタックに積みます。 定数値をスタックに積む唯一の命令は「`」で、これを実行するとスタックの上から順に「72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33」という値が入ります。 「|」はスタックの中身を標準出力に出力します。基本的にプログラムの最後で1度だけ使うことを推奨します。

「Hello, world!」と出力するコードは下のように簡潔な物になります。KEMURIはHQ9+に次いで2番目に「Hello, world!」を短く書ける言語です。

`|

NOTを使わずに、0~127の任意の文字を出力できることが知られています。

コーディングのコツ。 「"^^」でスタックの頭の1つの値を捨てることが出来ます。「^"^^」で2つ、「^^"^^」で3つ捨てることが出来ます。「'^"^^」で1番上の値を捨てずに2番目、3番目の値を捨てることが出来ます。

「l」(小文字L)と「*」(アスタリスク)は将来的に「スタックの中身をBrainf*ckとして実行」という命令に割り当てる可能性のために予約されています。(KEMURI_PLUS)

Pythonで書かれたKEMURIのインタープリタを下に掲載します。

続きを読む "KEMURI" »

古い記事タイトル一覧

凡例{ ●: 単一エントリーへのリンク, □: そこから最新記事までを一覧表示, ■: そこから最新記事までをwindow.openで開く}(comming soon)