Hatena::ブログ(Diary)

エンジニアのソフトウェア的愛情 このページをアンテナに追加 RSSフィード Twitter

2015-03-30

RailsとHamlでSVG

ふつうに

ブラウザがインラインのSVG表示に対応していれば、HamlSVGを書くとそのまま表示してくれます。

app/controllers/circles_controller.rb

class CirclesController < ApplicationController
  def index
    @circles = 30.times.map { {x: rand(500), y: rand(500), r: rand(100)} }
  end
end

app/views/circles/index.html.haml

%svg{'xmlns' => 'http://www.w3.org/2000/svg', 'xmlns:xlink' => 'http://www.w3.org/1999/xlink', 'viewBox' => '0 0 500 500', 'width' => '100%', 'height' => '100%'}
  - @circles.each do |circle|
    %circle{cx: circle[:x], cy: circle[:y], r: circle[:r], stroke: 'red', fill: 'none'}

Rails を起動して http://localhost:3000/circles を開くとこんな感じ。

f:id:E_Mattsan:20150330225649p:image


フォーマットにSVGを指定して

フォーマットを指定して、http://localhost:3000/circles.svg とアクセスしたい場合には、いくつか設定が必要になります。


MIME タイプを設定する

config/initializers/mime_types.rb に SVG を設定します。

次の1行を追加します。

Mime::Type.register 'image/svg+xml', :svg

Haml のフォーマットを設定する

Haml のフォーマットを XHTML にします。これをしておかないとXMLのDOCTYPE宣言が出力されません。

具体的には config/initializers/haml.rb を作成して次の内容を記述します。

Haml::Template.options[:format] = :xhtml

ビューを書く

拡張子SVGにしたHamlを書きます。

内容はインラインで書いたものにDOCTYPE宣言を追加したものになります。

app/views/circles/index.svg.haml

!!! XML
%svg{'xmlns' => 'http://www.w3.org/2000/svg', 'xmlns:xlink' => 'http://www.w3.org/1999/xlink', 'viewBox' => '0 0 500 500', 'width' => '100%', 'height' => '100%'}
  - @circles.each do |circle|
    %circle{cx: circle[:x], cy: circle[:y], r: circle[:r], stroke: 'red', fill: 'none'}

img タグで表示する

MIME タイプを指定しているので、img タグを使っても表示することができます。

index.html.haml を次のように書き換えて http://localhost:3000/circles にアクセスすると通常の画像と同じように表示されます。

app/views/circles/index.html.haml

= image_tag circles_path(format: :svg)

2015-03-07

有限オートマトン Finite Automaton はPrologで書くと簡単だった件

引き続き、アンダースタンディング コンピュテーションを読んでいます。


先日はいろんな言語でDFAを書いてみたわけですが、よくよくコードを読み直してみると、Prologのばあいはもっと簡単にDFAを書けることに気がつきました。

というか、Prologのコードは、ほとんどルールそのものだった、というお話。


決定性有限オートマトン: Deterministic Finite Automaton (DFA)

DFAでは、ある状態である入力があったときの遷移先が1つに決定しています。

次のような状態遷移表で表されるDFAを書いてみます。

状態\入力ab
121
223
333

DFAのコード。

rule(1, 0'a, 2). rule(1, 0'b, 1).
rule(2, 0'a, 2). rule(2, 0'b, 3).
rule(3, 0'a, 3). rule(3, 0'b, 3).

dfa(State, [], State).
dfa(StartState, [C|CS], LastState) :-
  rule(StartState, C, NextState),
  dfa(NextState, CS, LastState).

isAccepts(StartState, AcceptStates, String, true) :-
  dfa(StartState, String, LastState),
  member(LastState, AcceptStates).
isAccepts(_, _, _, false).

DFAをテストするコード。開始状態は 1 、受理状態は 3 です。

test(StartState, AcceptStates, String) :-
  isAccepts(StartState, AcceptStates, String, Result),
  format("~s => ~p~n", [String, Result]).

main :-
  test(1, [3], "a"),
  test(1, [3], "baa"),
  test(1, [3], "baba").

実行結果。

$ swipl -s dfa.prolog -g 'main, halt'
a => false
baa => false
baba => true

非決定性有限オートマトン: Nondeterministic Finite Automaton (NFA)

NFAでは遷移先が1つに決まりません。そのため、各々の遷移先に対する挙動を考慮しなければなりません。

状態\入力ab
111 or 2
233
344

…なのですが。Prologにはバックトラックというしくみが言語自体に組み込まれています。これによって、複数の遷移先があるばあいでも各々の挙動を網羅することができるようになっています。


ja.wikipedia.org に解説がありますので、詳しくはこちらへ:


NFAのコード。

実際のところ、rule の内容を変更し、名前を dfa から nfa に変えただけで、処理自体は DFA と同じです。

rule(1, 0'a, 1). rule(1, 0'b, 1). rule(1, 0'b, 2).
rule(2, 0'a, 3). rule(2, 0'b, 3).
rule(3, 0'a, 4). rule(3, 0'b, 4).

nfa(State, [], State).
nfa(StartState, [C|CS], LastState) :-
  rule(StartState, C, NextState),
  nfa(NextState, CS, LastState).

isAccepts(StartState, AcceptStates, String, true) :-
  nfa(StartState, String, LastState),
  member(LastState, AcceptStates).
isAccepts(_, _, _, false).

ちょっと挙動を確認してみます。

$ swipl -s nfa.prolog
?- nfa(1, "b", R).
R = 1 ;
R = 2 ;
false.

swipl -s nfa.prolog でファイルを読み込んでいます。

プロンプトが表示されるので nfa(1, "b", R). と入力します。

実行結果として R = 1 と表示され、一旦入力待ちになります。ここでセミコロンを入力すると別の解を求めるため実行を再開します。

別の結果として R = 2 と表示されます。これらはそれぞれ、状態1のときに入力Bがあったばあいの結果(遷移先の状態)の状態1と状態2に対応します。

2つ目の解を表示したあと再び入力待ちになっているのでセミコロンを入力すると、これ以上解がないので false. を表示して処理を終了します。


NFAをテストするコード。開始状態は 1 、受理状態は 4 です。

test(StartState, AcceptStates, String) :-
  isAccepts(StartState, AcceptStates, String, Result),
  format("~s => ~p~n", [String, Result]).

main :-
  test(1, [4], "bab"),
  test(1, [4], "bbbbb"),
  test(1, [4], "bbabb").

実行結果。

$ swipl -s nfa.prolog -g 'main, halt'
bab => true
bbbbb => true
bbabb => false

自由移動のある NFA

入力がなくても自発的に遷移する自由移動があるばあいを考えます。

下記の状態遷移表ではεで書いた列が自由移動です。遷移先が - となっている部分は、そのような遷移のルールがないことを表しています。

状態\入力aε
1-2 or 4
23-
32-
45-
56-
64-

状態1は自由移動で、つまり入力がなくても自発的に、状態2か状態4に遷移します。


自由移動のあるNFAのコード。

入力を必要としない rule/2 と、 rule/2 を利用して入力をとらずに遷移するコードを追加しています。

rule(1, 2).
rule(1, 4).
rule(2, 0'a, 3).
rule(3, 0'a, 2).
rule(4, 0'a, 5).
rule(5, 0'a, 6).
rule(6, 0'a, 4).

nfa(State, [], State).
nfa(StartState, CS, LastState) :-
  rule(StartState, NextState),
  nfa(NextState, CS, LastState).
nfa(StartState, [C|CS], LastState) :-
  rule(StartState, C, NextState),
  nfa(NextState, CS, LastState).

isAccepts(StartState, AcceptStates, String, true) :-
  nfa(StartState, String, LastState),
  member(LastState, AcceptStates).
isAccepts(_, _, _, false).

ちょっと挙動を確認してみます。

?- nfa(1, "", R).
R = 1 ;
R = 2 ;
R = 4 ;
false.

状態1のばあい、入力がなくても状態2か状態4に遷移することがわかります。遷移せず状態1のままという場合もあるので、このばあいは状態1,2,4のいずれかが取りうる値となります。


自由移動のあるNFAをテストするコード。開始状態は 1 、受理状態は 2 か 4 です。

test(StartState, AcceptStates, String) :-
  isAccepts(StartState, AcceptStates, String, Result),
  format("~s => ~p~n", [String, Result]).

main :-
  test(1, [2, 4], "aa"),
  test(1, [2, 4], "aaa"),
  test(1, [2, 4], "aaaa"),
  test(1, [2, 4], "aaaaa").

実行結果。

$ swipl -s nfa.prolog -g 'main, halt'
aa => true
aaa => true
aaaa => true
aaaaa => false

いつか読むはずっと読まない:人工知能つながりということで…

久々に小説を読んでいます。これがなかなか進みません。小説を読む体力が低下しているようです。

2015-02-22

修練がてら DFA をいろんな言語で書いてみた

アンダースタンディング コンピュテーションを読んでいます。


その「3章 最も単純なコンピュータ」の「3.1 決定性有限オートマトン」に登場する決定性有限オートマトンDFA)のRubyでの実装を他の言語でも実装してみました。


実装

GitHubに収めました。

Ruby写経https://github.com/mattsan/dfa-samples/blob/master/dfa.rb
CoffeeScripthttps://github.com/mattsan/dfa-samples/blob/master/dfa.coffee
C++https://github.com/mattsan/dfa-samples/blob/master/dfa.cpp
Haskellhttps://github.com/mattsan/dfa-samples/blob/master/dfa.hs
Io languagehttps://github.com/mattsan/dfa-samples/blob/master/dfa.io
Prologhttps://github.com/mattsan/dfa-samples/blob/master/dfa.prolog
Erlanghttps://github.com/mattsan/dfa-samples/blob/master/dfa.erl
C++ Template Meta Programminghttps://github.com/mattsan/dfa-samples/blob/master/dfa_template.cpp
SQLhttps://github.com/mattsan/dfa-samples/blob/master/dfa.sql

実行結果

実装の詳細は GitHub を見ていただくとして。

実行の手順と結果だけ載せておきます。

実行環境は Mac Yosemite です。


Ruby
$ ruby dfa.rb
a => false
baa => false
baba => true

CoffeeScript
$ coffee dfa.coffee
a => false
baa => false
baba => true

C++
$ g++ --std=c++11 -o dfa dfa.cpp
$ ./dfa
a => false
baa => false
baba => true

Haskell
$ ghc --make dfa
$ ./dfa
a => False
baa => False
baba => True

Io language
$ io dfa.io
a => false
baa => false
baba => true

Prolog
swipl -s dfa.prolog -g 'main'
a => false
baa => false
baba => true

Erlang
$ erlc dfa.erl
$ erl -noshell -s dfa start -s init stop
a => false
baa => false
baba => true

C++ Template Meta Programming
$ g++ --std=c++11 -c dfa_template.cpp 
dfa_template.cpp:107:1: error: static_assert failed "'a' was not accepted"
dfa_template.cpp:110:1: error: static_assert failed "'baa' was not accepted"
dfa_template.cpp:114:1: error: static_assert failed "'baba' was accepted"

static_assert を利用して結果を表示しているので error と出ていますが、実行エラー(コンパイルエラー)なわけではないです。

後ろのメッセージの方を見てやってください。


SQL
$ psql some-database < dfa.sql
CREATE TABLE
INSERT 0 6
CREATE TABLE
INSERT 0 3
CREATE TABLE
INSERT 0 3
 source | accepted 
--------+----------
 baa    | f
 a      | f
 baba   | t
(3 rows)

DROP TABLE
DROP TABLE
DROP TABLE

今回の収穫

2015-01-31

Re:VIEWでドキュメントを書いた・コネタ:画像をインラインで挿入する

間が空いてしまいましたが。前回の続き

画像の挿入の話。


md2review を使って Markdown から Re:VIEW の形式に変換した場合、画像のタグは次のように変換されます。


これが、

![とある画像](images/picture.png)

こうなる。

//image[picture][とある画像]{
//}

このように Re:VIEW の形式ではタグに改行が入るため、このままではたとえば画像を表の中に配置するということができません。


実は Re:VEIW 形式には画像をインラインで挿入する方法があります。

インラインで挿入する場合は次のように記述します。

@<icon>{picture} 

この場合は画像にキャプションを付けることができませんが、インラインですのでもともとキャプションが不要な場合が多く、問題にはならないかと思います。



これらを踏まえて。

キャプションが使われないのならば、その部分をインラインか否かの識別に利用して、たとえば inline と書かれていたらインラインに変換するように変換してみます。

たとえば sed を使うとこんな感じ。


$ sed -E 's/\!\[inline\]\(images\/(.+)\.[^.]+\)/@<icon>{\1}/g' source.md | md2review > source.re

Markdown でこのように書くと、

インラインの ![inline](images/picture.png) 画像

このように変換されます。

インラインの @<icon>{picture} 画像

2014-12-23

Re:VIEWでドキュメントを書いた

XP祭り2014で、Re:VIEWを使った書物の作成について話を聞きました。

Re:VIEWとその周辺とツールを使うことで、markdownマークアップしたテキストをPDFePubにできるとのこと。

今回、仕事の納品物のドキュメントを作成するにあって、ツールに何を使ってもOKという許可がおりたこともあって、試しにRe:VIEWでドキュメントを書くことにしました。

その記録を兼ねた記事です。

まずは細かい話を省略して、準備からPDF生成まで。


ツールの用意

ツール説明参照サイト
md2review markdownからRe:VIEWへ変換するのに利用しますtakahashim/md2review ? GitHub
Re:VIEWRe:VIEW本体kmuto/review ? GitHub
LaTeXPDF化の場合、Re:VIEWはLaTeXファイルを生成するので、それをPDF化するためにLaTeXが必要になりますTeX Wiki

ツールインストール

md2reviewとRe:VIEWはgemインストールします。

$ gem install md2review
$ gem install review

LaTeXTeX Wiki の記事を参考にインストールします。

OS X (Mac) Mac - TeX Wiki
Linux Linux - TeX Wiki
Microsoft Windows Microsoft Windows - TeX Wiki

ひな形を生成する

review-init コマンドを利用してドキュメントのひな形を生成します。

$ review-init sample-document

コマンドを実行すると sample-document というディレクトリが生成されます。

ディレクトリの構成は次のようになっています。

sample-document.re Re:VIEW ファイル。このファイルに本文を記述します。
catalog.yml 章立てを記述します。
config.yml 作成するドキュメントの設定を記述します。
images/ Re:VIEW から参照するイメージファイルはこのディレクトリに格納します。
layouts/layout.html.erb HTML ファイルや ePub ファイルのレイアウトの設定を記述します。
Rakefile ビルドのためのタスクを定義した rake ファイルです。
sty/reviewmacro.sty LaTeX のスタイルファイルです。
style.css HTML ファイルや ePub ファイルのスタイルファイルです。

markdown でテキストを書き md2review で Re:VIEW にする

markdownd で記述したファイルを用意します。ここでは sample-document.md というファイル名とします。

このファイルを md2review コマンドで Re:VIEW のファイルに変換します。

$ md2review sample-document.md > sample-document.re

Re:VIEW から PDF

PDF化には review-pdfmaker というコマンドを使います。

パラメータとして、設定ファイルを指定します。

$ review-pdfmaker config.yml

config.ymlの内容がreview-init コマンドで生成したままの場合、PDF化に成功すると book.pdf というファイルが生成されます。

PDFファイルを開くと markdown で記述した以外に表紙や目次や奥付が挿入されています。これらの設定や PDF ファイルのファイル名などは設定ファイルで設定することになります。


ちなみに。わたしが使用した環境では、すでに PDF ファイルやPDFファイルを生成する際に作成されるワークディレクトリ(book-pdf/ というディレクトリ)がすでに存在していると、review-pdfmaker の実行が失敗しました。そのため、PDF ファイルを再生成するときは、それらを削除する必要がありました。

$ rm -rf *pdf

参考記事

書籍制作ワークフロー | シリーズ | Developers.IO



次回に続く(たぶん)。