ochalog

RubyとMediaWikiとIRCが好き。

SICP: Exercise 1.27

問題 1.27

脚注 47 の Carmichael 数が本当に Fermat テストをだますことを示せ。それには整数 \( n \) をとり、\( a < n \) なるすべての \( a \) で、\( a^n \) が \( n \) を法として \( a \) の合同になるかどうか見る手続きを書き、Carmichael 数でその手続きを使ってみる。

計算機プログラムの構造と解釈 第二版 1.2.6 例:素数性のテスト」より

脚注 47 で挙げられている Carmichael 数は 561, 1105, 1729, 2465, 2821 および 6601。問題文に従ってテストする。

Fermat テストをだます数(Fermat の小定理より素数もそのような数になる)かどうかを調べる手続きは

(define (fool-fermat-test? n)
  (define (iter a)
    (cond ((= a 0) #t)
          ((= (expmod a n n) a) (iter (- a 1)))
          (else #f)))
  (iter (- n 1)))

それではまとめて調べてしまおう。

(define carmichael-numbers '(561 1105 1729 2465 2821 6601))
(map fool-fermat-test? carmichael-numbers)
; (#t #t #t #t #t #t)

確かに脚注 47 の Carmichael 数はすべて Fermat テストをだますようだ。

SICP: Exercise 1.26

問題 1.26

Louis Reasoner は問題 1.24 がなかなかうまくいかなかった. 彼の fast-prime?prime? よりずっと遅いようであった。Louis は友人の Eva Lu Ator に助けを求めた。Louis のプログラムを見ていると、square を呼び出す代りに乗算を陽に使うように変っていた。

(define (expmod base exp m)
  (cond ((= exp 0) 1)
        ((even? exp)
         (remainder (* (expmod base (/ exp 2) m)
                       (expmod base (/ exp 2) m))
                    m))
        (else
         (remainder (* base (expmod base (- exp 1) m))
                    m))))

「違いが分らん」と Louis、「分る」と Eva がいった。「手続きをこのように書き替えたので、\( \Theta(\log n) \) のプロセスを \( \Theta(n) \) のプロセスにしてしまった。」説明せよ。

計算機プログラムの構造と解釈 第二版 1.2.6 例:素数性のテスト」より

expmodexp に注目し、どのように手続きが呼び出されるか考える。

square を使う

(define (expmod base exp m)
  (cond ((= exp 0) 1)
        ((even? exp)
         (remainder (square (expmod base (/ exp 2) m))
                    m))
        (else
         (remainder (* base (expmod base (- exp 1) m))
                    m))))

の場合、以下の図のように呼び出される。段階数は \( \Theta(\log n) \)。

f:id:ochaochaocha3:20150414000555p:plain

一方、Louis の

(define (expmod base exp m)
  (cond ((= exp 0) 1)
        ((even? exp)
         (remainder (* (expmod base (/ exp 2) m)  ; (*)
                       (expmod base (/ exp 2) m))
                    m))
        (else
         (remainder (* base (expmod base (- exp 1) m))
                    m))))

の場合、(*) の部分に到達するたびに expmod が 2 回呼び出されるので、以下の図のようになる。

f:id:ochaochaocha3:20150414001305p:plain

図で比較すると、Louis の書き方のまずさがよく分かる。(*) に到達する回数は \( \Theta(\log_2 n) \) であり、そこで経路が 2 つに分かれるので、全体の段階数は

$$ \Theta\left( 2^{\log_2 n} \right) = \Theta(n) $$

となる。

SICP: Exercise 1.25

問題 1.25

Alyssa P. Hacker は expmod を書くのに多くの余分なことをやったと不満で、結局べき乗の計算法は知っているから

(define (expmod base exp m)
  (remainder (fast-expt base exp) m))

と単純に書ける筈だといった。これは正しいか。これも高速素数テストと同じに使えるか、説明せよ。

計算機プログラムの構造と解釈 第二版 1.2.6 例:素数性のテスト」より

正しくない。

これが正しくないことは、expmod を大きな exp に対して行うことで分かる。試しに \( 5^{10000000} \mod 10000000 \) を計算してみよう。まず問題 1.16 から繰り返しプロセスの fast-expt を持ってきて、Alyssa が示した expmod を実行する。

(define (fast-expt b n)
  (define (fast-expt-iter a b n)
    (cond ((= n 0) a)
          ((even? n) (fast-expt-iter a
                                     (square b)
                                     (/ n 2)))
          (else (fast-expt-iter (* a b)
                                b
                                (- n 1)))))
  (fast-expt-iter 1 b n))

(define (expmod base exp m)
  (remainder (fast-expt base exp) m))

(expmod 5 10000000 10000000)
; なかなか終わらない…

一方、本文で示された expmod を使えば

(define (expmod base exp m)
  (cond ((= exp 0) 1)
        ((even? exp)
         (remainder (square (expmod base (/ exp 2) m))
                    m))
        (else
         (remainder (* base (expmod base (- exp 1) m))
                    m))))

(expmod 5 10000000 10000000)
; 2890625(一瞬で出る)

となり、計算時間に大きな差がある。

fast-expt を使うと最終的にとても大きい数を \( m \) で割った余りを求めることになるので、計算が遅くなったり桁あふれが起こる可能性がある。各段階で remainder を適用すれば、脚注 46 で

指数 \( e \) が 1 より大きい場合の簡約化では、任意の整数 \( x \)、\( y \) と \( m \) について、\( x \times y \mod m \) の剰余は、\( x \mod m \) と \( y \mod m \) の剰余を別々に計算し、その結果を掛け合せ、更に積の \( \mod m \) の剰余をとればよいという事実に基づいている。\( e \) が偶数の場合は、\( b^{e/2} \mod m \) の剰余を計算し、二乗し、その \( \mod m \) の剰余をとる。これは \( m \) より遥かに大きい数値を扱わずに計算が実行出来、有用である。

と指摘されているように、\( m^2 \) 以上の数を扱わずに計算できるので、上記の問題が生じない。

SICP: Exercise 1.24

問題 1.24

問題 1.22 の timed-prime-test 手続きを fast-prime?(Fermat 法参照)を使うように変え、その問題で見つけた 12 個の素数をテストせよ。Fermat テストは \( \Theta(\log n) \) の増加なので、1,000,000 近くの素数をテストする時間を 1,000 近くの素数をテストする時間と比べ、どの位と予想するか。データはその予想を支持するか。違いがあれば説明出来るか。

計算機プログラムの構造と解釈 第二版 1.2.6 例:素数性のテスト」より

下準備

Gaucherandom を使えるようにするために、srfi-27 モジュールを利用する。以下、本文の手続きも含めてまとめて定義する。

(use srfi-27)

(define (random n) (random-integer n))
(random-source-randomize! default-random-source)

(define (square x) (* x x))

(define (expmod base exp m)
  (cond ((= exp 0) 1)
        ((even? exp)
         (remainder (square (expmod base (/ exp 2) m))
                    m))
        (else
         (remainder (* base (expmod base (- exp 1) m))
                    m))))

(define (fermat-test n)
  (define (try-it a)
    (= (expmod a n n) a))
  (try-it (+ 1 (random (- n 1)))))

(define (fast-prime? n times)
  (cond ((= times 0) #t)
        ((fermat-test n) (fast-prime? n (- times 1)))
        (else #t)))

(load "./ex-1-22.scm")

解答

とりあえず 10 回 Fermat テストを行うように書いた。

(define (start-prime-test n start-time)
  (if (fast-prime? n 10)
    (report-prime n (- (runtime) start-time))))

1,000,000 近くの素数をテストする時間と 1,000 近くの素数をテストする時間の比は、段階数が \( \Theta(\log n) \) なので

$$ \frac{\log 1000000}{\log 1000} = \frac{\log (10^6)}{\log (10^3)} = \frac{6 \log 10}{3 \log 10} = 2 $$

と予想した。

結果

問題 1.23 と同様に 12 個の素数について、素数テストに要する時間を測った。

ochaochaocha3.hateblo.jp

結果を以下に示す。

素数:\( p \) 問題 1.23:\( t_\mathrm{A}/\mathrm{\mu s} \) 問題 1.24:\( t_\mathrm{B}/\mathrm{\mu s} \) \( t_\mathrm{A} / t_\mathrm{B} \)
1,009 7 47 0.1
1,013 4 39 0.1
1,019 5 42 0.1
10,007 13 48 0.3
10,009 13 46 0.3
10,037 13 47 0.3
100,003 38 54 0.7
100,019 39 55 0.7
100,043 39 55 0.7
1,000,003 122 60 2.0
1,000,033 122 62 2.0
1,000,037 122 62 2.0

毎回 10 回ずつ Fermat テストを行っているためか \( p \) が小さいうちは遅いけれど、\( p \approx 1,000,000 \) ではかなり速くなっている。

追加でもう一桁大きいときの時間を計測。

素数:\( p \) 問題 1.23:\( t_\mathrm{A}/\mathrm{\mu s} \) 問題 1.24:\( t_\mathrm{B}/\mathrm{\mu s} \) \( t_\mathrm{A} / t_\mathrm{B} \)
10,000,019 395 74 5.3
10,000,079 395 75 5.3
10,000,103 394 74 5.3

さらに差が広がった。桁数が大きくなるにつれて \( \Theta(\sqrt{n}) \) と \( \Theta(\log n) \) の差は大きくなっていくのだろう。

1,000,000 近くの素数をテストする時間と 1,000 近くの素数をテストする時間の比は、平均をとって計算してみると

$$ \frac{61.3}{42.7} \approx 1.44 $$

おや、思ったより大きくない。改めて fermat-test を見てみる。

(define (fermat-test n)
  (define (try-it a)
    (= (expmod a n n) a))
  (try-it (+ 1 (random (- n 1)))))

expmod は \( \Theta(\log n) \) だがその寄与が大きくないということは、random で時間がかかっているということだろう。

そこで、p を大きくして expmod に要する時間を長くしてみる。

(search-for-primes 100000000 100000100)
; 100000007 *** 92
; 100000037 *** 86
; 100000039 *** 88
; 平均 88.7

(search-for-primes 100000000000 100000000100)
; 100000000003 *** 180
; 100000000019 *** 176
; 100000000057 *** 173
; 平均 176.3

$$ \frac{176.3}{88.7} \approx 1.99 $$

このようにすると 1,000 倍したときとの時間の比が 2 に近づいた。したがって、\( p \) が十分に大きければ予想の通りになると考えられる。

SICP: Exercise 1.23

問題 1.23

本節の初めの smallest-divisor は多くの不要な計算をする:数が 2 で割り切れるか調べた後は、より大きい偶数で割り切れるか調べるのは無意味である。test-divisor が使う値は、2, 3, 4, 5, 6, ... ではなく、2, 3, 5, 7, 9, ... であるべきだ。この変更を実装するため、入力が 2 なら 3 を返し、そうでなければ入力に 2 足したものを返す手続き next を定義せよ。smallest-divisor(+ test-divisor 1) ではなく、(next test-divisor) を使うように修正せよ。このように修正した smallest-divisor にした timed-prime-test で、問題 1.22 で見つけた 12 個の素数をテストせよ。この修正はテストのステップを半分にしたので、二倍速く走ることが期待される。この期待は確められるか。そうでなければ、得られた二つのアルゴリズムの速度の比はどうであったか。それが 2 と違う事情を説明せよ。

計算機プログラムの構造と解釈 第二版 1.2.6 例:素数性のテスト」より

解答

問題文の通りに next を定義し、(next test-divisor) を使うように修正する。

(define (next n)
  (if (= n 2)
    3
    (+ n 2)))

(define (find-divisor n test-divisor)
  (cond ((> (square test-divisor) n) n)
        ((divides? test-divisor n) test-divisor)
        (else (find-divisor n (next test-divisor)))))

結果

問題 1.22 で調べた 12 個の素数について、改良した素数テストに要する時間を測った。

ochaochaocha3.hateblo.jp

(for-each timed-prime-test
          '(1009 1013 1019
            10007 10009 10037
            100003 100019 100043
            1000003 1000033 1000037))

結果を以下に示す。

素数:\( p \) 問題 1.22:\( t_\mathrm{A}/\mathrm{\mu s} \) 問題 1.23:\( t_\mathrm{B}/\mathrm{\mu s} \) \( t_\mathrm{A} / t_\mathrm{B} \)
1,009 7 7 1.0
1,013 7 4 1.8
1,019 7 5 1.4
10,007 20 13 1.5
10,009 20 13 1.5
10,037 21 13 1.6
100,003 64 38 1.7
100,019 64 39 1.6
100,043 65 39 1.7
1,000,003 199 122 1.6
1,000,033 209 122 1.7
1,000,037 198 122 1.6

\( p \) が大きくなると \( t_\mathrm{A} / t_\mathrm{B} \) は 1.6〜1.7 で安定した。2 には届いていないが、ほぼ期待通りといえるだろう。遅くなった原因は、next 内の if や、next の呼び出し自体に時間を要するためと考えられる。

SICP: Exercise 1.22

2 年ぶりに SICP の記事を書くことになった。1 年半前の鬱々としていた日々がなければもっと早く進んだんだけどなぁ…

問題 1.22

殆んどの Lisp の実装には基本手続き runtime があり、システムが走った時間を(例えばマイクロ秒で)示す整数を返す。次の timed-prime-test 手続きは整数 \( n \) で呼び出されると、\( n \) を印字し、\( n \) が素数かどうかを調べ、\( n \) が素数なら手続きは三つの星印とテストに要した時間を印字する。

(define (timed-prime-test n)
  (newline)
  (display n)
  (start-prime-test n (runtime)))

(define (start-prime-test n start-time)
  (if (prime? n)
      (report-prime (- (runtime) start-time))))

(define (report-prime elapsed-time)
  (display " *** ")
  (display elapsed-time))

この手続きを使い、指定範囲の連続する奇整数について素数性を調べる手続き search-for-primes を書け。その手続きで、それぞれ 1,000、10,000、100,000、1,000,000 より大きい、小さい方から三つの素数を見つけよ。素数をテストする時間に注意せよ。テストのアルゴリズムは \( \Theta(\sqrt{n}) \) の増加の程度だから、10,000 前後の素数のテストは 1,000 前後の素数のテストの \( \sqrt{10} \) 倍かかると考えよ。時間のデータはこれを支持するか。100,000、1,000,000 のデータは \( \sqrt{n} \) 予想のとおりだろうか。結果はプログラムが計算に必要なステップ数に比例した時間で走るという考えに合っているか。

計算機プログラムの構造と解釈 第二版 1.2.6 例:素数性のテスト」より

Gaucheruntime を使えるようにする

「殆んどの Lisp の実装には基本手続き runtime があり」とあるが、Gauche にはなかった。「実行時間の計測 - n-oohiraのSICP日記」より以下を拝借。

(define (runtime)
  (use srfi-11)
  (let-values (((a b) (sys-gettimeofday)))
              (+ (* a 1000000) b)))

解答

素数は 2 以上なので、一応その範囲だけ調べるようにした。また、3 以上の素数は必ず奇数なので、奇数を調べた後は一つ置きに調べるようにした。

(define (search-for-primes start end)
  (cond ((> start end) 'finished)
        ((< start 2) (search-for-primes 2 end))
        (else (timed-prime-test start)
              (search-for-primes
                (+ start
                   (if (even? start) 1 2))
                end))))

結果

search-for-primes を評価すると以下のように出力される。

(search-for-primes 20 30)
; 20
; 21
; 23 *** 1
; 25
; 27
; 29 *** 2

長いので、1,000 以上のものは最初の素数 3 つのみ書くことにする。

(search-for-primes 1000 1050)
; 1009 *** 7
; 1013 *** 7
; 1019 *** 7

(search-for-primes 10000 10050)
; 10007 *** 20
; 10009 *** 20
; 10037 *** 21

(search-for-primes 100000 100050)
; 100003 *** 64
; 100019 *** 64
; 100043 *** 65

(search-for-primes 1000000 1000050)
; 1000003 *** 199
; 1000033 *** 209
; 1000037 *** 198

素数テストに要した時間 [μsec] は、オーダーが同じならばほぼ等しいので、上記の結果の平均をとってみると

  • \( n \approx 1000 \):7.0
  • \( n \approx 10000 \):20.7
  • \( n \approx 100000 \):64.7
  • \( n \approx 1000000 \):202.0

となった。一つ上のオーダーでの値との比を調べると

  • \( \frac{20.7}{7.0} \approx 3.0 \)
  • \( \frac{64.7}{20.7} \approx 3.13 \)
  • \( \frac{202.7}{64.7} \approx 3.13 \)

となり、すべて \( \sqrt{10} \approx 3.16 \) に近い値だった。したがって \( \sqrt{n} \) 予想は成立しており、プログラムが計算に必要なステップ数に比例した時間で走るという考えに合っているといえる。

今回の範囲では \( \sqrt{n} \) 予想はずっと成立していたけれど、今より速度が出ない昔のコンピュータを使っていたらどうなるのだろうか。問題の最後の文を見ると、ちょっと引っかかる。

DSSP で求めた二次構造を反映させる PyMOL スクリプトの生成(マイナーアップデート版)

2013 年に書いたものを更新。当時と Ruby の書き方が少し変わった。

PyMOLを使ってみよう/クックブック - BioKids Wiki」で書かれているように、タンパク質の構造を詳しく議論したくて DSSP で計算した結果と図の二次構造を合わせたい場合がある。ただ、この PyMOL スクリプトを 1(または数)残基ずつ打ち込むのも面倒なので、自動でスクリプトを生成するプログラムを Ruby で書いてみた。ちなみに、研究室にいる間に研究に使える汎用的なプログラムを書いたのは初めて(笑)

プログラム

やっていることは、入力された DSSP ファイルを本体まで読み飛ばし、鎖と残基番号で指定した残基の二次構造を前述の BioKids Wiki のページの表に従って変換するスクリプトを出力する、という流れ。DSSP ファイルの固定幅の性質を使っているので、バージョンアップで出力形式が変わったら動かなくなるかも。一応手元の DSSP ファイルではすべて正常に動いた。

使い方

foo.pdb と対応する foo.dssp を用意して

ruby gen-alter-ss-dssp.rb foo.dssp > alter-ss.pml

で PyMOL スクリプトファイル alter-ss.pml が生成される。

あとは PyMOL で foo.pdb を読み込んだ後、「File」→「Run...」で alter-ss.pml を読み込むか、

@ alter-ss.pml

とコマンドを打ち込めば、二次構造が DSSP で計算されたものに一気に変わる。

手元で試してみた限りでは、PyMOL の DSS と DSSP では意外と二次構造の割り当てが異なるので、やはり気をつけた方が良いのかも。

2015-03-24 の日記

今日も学校で Linux 環境構築。

結局 PC には Lubuntu、Raspberry Pi には(Raspbian をやめて)Arch Linux を入れた。どちらも軽さ重視。特に Raspberry Pi の方はサーバーとして動けばよいので、GUI とか余分なソフトはいらないということで。以下つらつらと。

Lubuntu

  • ノーマル Ubuntu ではなく Lubuntu を入れてみたら大違い。グラボのせいか前者ではカクカクだったのが、とても滑らかに動くように。
  • ところが ARandR でデュアルディスプレイにしたらカクカクに戻った。あきらめて広めの 1 台ディスプレイに替えたら軽快な動作に戻った。Lubuntu でもこんなに重くなるものなのだろうか。
  • プロキシサーバーの設定は /etc/profile.d にファイルを作って置くのが手っ取り早そう。$http_proxy$https_proxy$ftp_proxy をそれぞれ一緒にしておけばよかった。
  • 今日の新発見は「Terminator」。OS XiTerm2 のように端末の画面分割ができる。作業効率がかなりアップ。
  • あとは Vim とか、いつもの設定。
  • 途中から、Raspberry Pi はこの端末からの SSH 操作で設定した。

Raspberry Pi

  • 以前書いた Arch Linux 関係の記事が役に立った(笑)
  • Arch Linux メモリーカードの準備の仕方が以前と異なるdd でイメージを焼き込むのではなく、パーティションを事前に準備してそこに tar で展開する Gentoo と似た方法になっていた。大容量メモリーカードでも途中でパーティションを広げなくてよくなり、楽になった。
  • ネットワークの設定を systemd-networkd で行うようになっていた。設定がより簡潔になっていて、個人的には好印象。
  • 以前と少し手順を変えた。ストレスが溜まるので、ロケールkeymap 関係は真っ先にやった方が良い印象。
  • 勢いで、研究室ローカルドメインDNS 設定を BIND でやってみた。先生が今年度の先輩に出されていた課題では dnsmasq を使っていたけれど、ゾーンファイルを書いたことがあったのと「DNS & BIND」の本があったので、BIND でいいや、と。
  • GitBucket を起動させてみたら数分もかかった。Raspberry Pi ではハードなのか… GitLab だともっと重そうな気がするが、どうか(インストールが面倒そうだったので今日はあきらめた)。

DNS & BIND 第5版

DNS & BIND 第5版

2015-03-23 の日記

午後から学校の研究室の準備(まだ研究室に正式に入っているわけではないけれど)。

4 月から何人かで先輩が書いたプログラムをいじっていくことになる。クリエイターズネットワークでそこそこうまくいっていることから、Git を使った履歴管理を試してみたいと思っている。研究室に Raspberry Pi がいくつかあるので、実家と同じように Model B のひとつを使ってサーバーを立て、GitLab あたりを動かしたい。あと、それと別に仮想マシンでない Linux 環境が欲しかった。

そういうわけで、Raspberry Pi ともう 1 台の PC の Linux の設定を行った。

学校だとプロキシサーバーを通してインターネットにアクセスすることになるのだけれど、思いの外そのために引っかかることがあった。ブラウザとか apt とかソフト単位で設定しなければ接続できないし、Raspbian のイメージを落とすときにウィルスチェックがかかるせいで想像以上に時間がかかったことも。この辺りは実家で勝手に動かすときとは違う。そういうことで予想外に時間が取られたので、本設定は明日へ持ち越し。

もう 1 台の PC の方は、CPU は i5 でメモリが 16 GB も載っていた(贅沢!)ので、ノーマル Ubuntu でいいかと思ったのだけれど、いざ入れてみると意外とカクカク。ビデオカードの性能不足なのかなぁ。重すぎで作業にならなそうだったので、明日 Lubuntu を改めて入れてみようと思う。

flex の練習:Hiki の見出し抽出

flex の練習その 2。昔 Hiki2MediaWiki でやったような Hiki の見出し抽出。

  • Hiki ソースを標準入力から読み込み、見出しのみを抽出して標準出力に出力する。
  • MediaWiki のように見出し番号を表示する。
  • 見出しレベルが連続していなくても、親子関係を正しく認識する。

hiki-outliner.l

/*
 * Hiki の見出し抽出
 * MediaWiki のように見出し番号をつけて表示する
 */
%{
  int n_bang_stack[7] = {0};
  int n_bang_stack_pos = 0;
  int heading_counter[7] = {0};

  // 見出しを表示する
  void print_heading(int* counter, const char* content);
%}

HEADING ^!{1,6}.+

%option main

%%

{HEADING} {
  int n_bang = 0;
  char* text_ptr = yytext;

  // '!' の数を調べる
  while (n_bang < 6 && *text_ptr == '!') {
    ++n_bang;
    ++text_ptr;
  }

  if (n_bang > n_bang_stack[n_bang_stack_pos]) {
    // 小見出しなので push する
    ++n_bang_stack_pos;
  } else {
    // 同レベルの見出しが見つかるまで pop する
    while (n_bang <= n_bang_stack[n_bang_stack_pos - 1]) {
      --n_bang_stack_pos;

      // 1 つ下のレベルの見出し番号をリセットする
      heading_counter[n_bang_stack_pos] = 0;
    }
  }

  n_bang_stack[n_bang_stack_pos] = n_bang;
  ++heading_counter[n_bang_stack_pos - 1];

  print_heading(heading_counter, text_ptr);
}

.  { }
\n { }

%%

void print_heading(int* counter, const char* content) {
  int i;

  putchar('0' + counter[0]);
  for (i = 1; i < 6 && counter[i] > 0; ++i) {
    putchar('.');
    putchar('0' + counter[i]);
  }
  putchar(' ');

  puts(content);
}

入力例 1

見出しレベルが連続している例。スパロボ Wikiサイバスター」より。

hiki-outliner-sample1-in.txt

!サイバスター
地球内部にひろがる異相世界ラ・ギアスに広大な版図をもつ「神聖ラングラン王国」で作られた風系魔装機。

!!サイバスター・ポゼッション
風の精霊王サイフィスと完全な同調を果たしたサイバスターの姿。

!登場作品と操縦者
基本的にマサキ・アンドー専用機として登場する。

バンプレストオリジナルロボットの元祖。

!!旧シリーズ

!!!第2次スーパーロボット大戦
初登場。

!!αシリーズ

!!!スーパーロボット大戦α
バランス調整で「サイフラッシュ」に加え「コスモノヴァ」を故障で使用不可という状態で参戦する。

!!!スーパーロボット大戦α外伝
マサキより遅れてセニアが地上に運び込み、次のマップからマサキがジャオームから乗り換える。

!装備・機能
高い運動性を誇り、機体とパイロットの回避力を高めれば避けて当てるリアルロボット的な運用法ができる。

入力例 2

見出しレベルが連続していない例。

hiki-outliner-sample2-in.txt

!サイバスター
地球内部にひろがる異相世界ラ・ギアスに広大な版図をもつ「神聖ラングラン王国」で作られた風系魔装機。

!!!サイバスター・ポゼッション
風の精霊王サイフィスと完全な同調を果たしたサイバスターの姿。

!登場作品と操縦者
基本的にマサキ・アンドー専用機として登場する。

バンプレストオリジナルロボットの元祖。

!!!旧シリーズ

!!!!第2次スーパーロボット大戦
初登場。

!!αシリーズ

!!!!スーパーロボット大戦α
バランス調整で「サイフラッシュ」に加え「コスモノヴァ」を故障で使用不可という状態で参戦する。

!!!!スーパーロボット大戦α外伝
マサキより遅れてセニアが地上に運び込み、次のマップからマサキがジャオームから乗り換える。

!装備・機能
高い運動性を誇り、機体とパイロットの回避力を高めれば避けて当てるリアルロボット的な運用法ができる。

出力例

./hiki-outliner < hiki-outliner-sample1-in.txt
./hiki-outliner < hiki-outliner-sample2-in.txt

どちらの例でも、以下のように正しい親子関係で MediaWiki の目次っぽく出力される。

1 サイバスター
1.1 サイバスター・ポゼッション
2 登場作品と操縦者
2.1 旧シリーズ
2.1.1 第2次スーパーロボット大戦
2.2 αシリーズ
2.2.1 スーパーロボット大戦α
2.2.2 スーパーロボット大戦α外伝
3 装備・機能