Windowsのデスクトップアプリ試作

Windowsのデスクトップアプリ作成の入門として、WPFマインスイーパを作った。
https://github.com/mob-a/Minebeginner

目的感

ネイティブなデスクトップアプリを作ってみる
題材はなんとなくゲームがいい
C#に触ってみる
Visual Studioに触ってみる
GitHubにアップロードしてみる

雑感

C#からxamlへの変数バインディングを使いたかったが断念した。ユーザの操作に応じて動的に画面を書き換えるのが難しい。結局手続き的に書き換えを行った
適当にvarで変数を指定してもだいたいいい感じ型を決めてくれて楽
Visual Studioの動作がすごい軽快
雑に全部小文字で書いても、いい感じで正しい変数名・メソッド名を指示してくれるのが快適
Emacsキーバインドが恋しい

備忘録2017

去年私が触れて楽しかったコンテンツを振り返る。

なろう小説

ずっとなろうを読んでいた。書籍版・コミカライズも大量に。
小説媒体はなろう発のものしか読んでない可能性がある。
今年読み始めた中で面白かったものをいくつか挙げる。

勇者召喚に巻き込まれたけど、異世界は平和でした

モーニングスター大賞の大賞受賞作。なろうのトップに常にバナーが出ている。
ひたすらに主人公がモテまくるハーレムラブコメ
基本的に全てのエピソードがヒロインの魅力を描写するために存在する(と感じる)。
舞台となる世界では神や魔族や人の有力者がほぼ全員美少女で、全員が主人公に好感を持つ。なので世界設定を描写するとそれが必然的にヒロインの描写になる。
ヒロインがかなり多いんだけど、その全員の魅力が妥協なく描かれる。あとがき専用ヒロイン(なにそれ)までかわいい。

齢5000年の草食ドラゴン、いわれなき邪竜認定〜やだこの生贄、人の話を聞いてくれない〜

2018年2月に角川スニーカー文庫で書籍化。ガンガンJOKERで2018年1月にコミカライズ開始。なんとコミカライズのほうが早い。
タイトル通りの勘違いコメディ。暴走する生贄少女とそれに振り回される邪竜様(他称)のやりとりがコントのようで楽しい。
あとポンコツ邪竜様とポンコツ聖女様がかわいい。

生き残り錬金術師は街で静かに暮らしたい

書籍版発売中・コミカライズも連載中。
https://comic-walker.com/contents/detail/KDCW_EB03200101010000_68/
薬品チートで薬屋兼喫茶店のようなものの経営、ゲーム的描写(アトリエ風)となろう的スローライフの王道っぽい。
その一方で舞台となる迷宮都市の悲壮感や迷宮攻略も描かれ、多面的に楽しめる。

はぐるまどらいぶ。

ヒーロー文庫から2018年2月に出版予定。
なろう×少年漫画×変身ヒーロー×童話って感じでロマンが詰まったよくわからない作品。
文体のアクが強くてやみつきになる。
さーしーえー(挿絵)も作者自ら描いていて雰囲気が良い。

漫画

読んだものを読書メーターで管理している。https://bookmeter.com/users/463730
面白かったものをいくつか。

三者三葉

おととしアニメ見て楽しかったので既刊を一気に読んだ。
仲が良いんだけど個々人は好き勝手に行動しているバランス感覚が好み。
奇人変人がdisりあう光景に心地よさを感じる。
原作は葉子様の主人公感が強かった。

ゆるキャン△

(5巻まだ読んでない)
冬の夜の描写が良い。
3巻で、リンちゃんがソロキャンをしつつ野クルメンバーと常に繋がっている描写があり、携帯通信端末の潜在能力に気付いた。気付くのがたぶん15年ぐらい遅い。
アニメも楽しみ。

ふつうの恋子ちゃん

表情が変わらないけど内面ではすっごいわたわたしてる、みたいなツンデレチョロインの脳内が読める。すごくいい。
男の子のほうもかわいい。

おはよう、いばら姫

最高にエモい漫画だった。
伏線がよく回収されて、6巻で過不足なく完結していた。

どうでもいいけど、最近ニコニコは漫画と大百科がメインコンテンツだと思うようになってきた。
コメント込みで楽しい http://seiga.nicovideo.jp/comic/29092

JRPG

「無名IPの萌豚向けオフラインJRPG」がやりたいという欲求が高まっていた。
クリアしたものが以下の通り(プレイ順)。

イメージエポック5、コンパイルハート2、ガスト1。偏っている。
アトリエは無名じゃないけど、まあ自分がやったことがないものということで。

ステラグロウが非常に楽しかった。ものすごく私向きだった。

STELLA GLOW - 3DS

STELLA GLOW - 3DS

キャラと演出と歌とBGMとシナリオが良い。
それビジュアルノベルでいいんじゃない?って感じではあるが、ゲーム部分とシナリオ部分をうまく融合させた演出ができていた。
例えば「敵のステータスが高くて明らかに勝てないピンチな状況で、魔女が新たな歌魔法を習得し、その歌魔法をコマンド入力して使用、逆転して勝利」みたいな感じでシナリオ上の展開がゲームシステムで表現される。
RPG(SRPG)ならではの体験であり、ビジュアルノベルでは表現が困難なものだと思う。

事実上の前作といわれるルミナスアーク1,2,3もやった。
さすがに今DSのゲームをやると音質が厳しい。ステラグロウのような演出の良さはあまりない。
とはいうものの、キャラは好みなので楽しめた。特に2の魔女勢が好き。結局のところ美少女がいればそれだけで満足です。


あとどうでもいいことだけど、魔女というか魔女帽大好きという性癖に目覚めた。
完全にルミナスアークのせい。

(自分用メモ)ベイズ統計モデリング入門中

説明変数x、目的変数yとして、3つのパラメータ(a,b,s)を持つ以下のようなモデルを作ってみる (簡単のため全て正規分布にしている)。

  y~Normal(ax+b, s)
  a(prior)~Normal(Hma, Hsa)
  b(prior)~Normal(Hmb, Hsb)
  s(prior)~Normal(Hms, Hss)

※Hma〜Hssはハイパーパラメータで、人手で恣意的に決めていい。

                  • -

以下、p(Y|M,S)を正規分布の確率密度の値とする。
  p(Y|M,S)=\frac{1}{\sqrt{2 \pi S^2}} \exp (-\frac{(Y-M)^2}{2S^2})
y~Normal(M,S)のモデルにおいて、y=Yとなる確率またはlikelihoodは、p(Y|M,S)である。
(確率の計算と尤度の計算に何の違いがあるのかわからん)。

                  • -

サンプルが3つ得られたとする。
(X1,Y1),(X2,Y2),(X3,Y3)

                  • -

パラメータが特定の値 (a,b,s)=(A,B,S) をとるとき、posteriorを求めたい。
ベイズ統計で一般的な以下の関係を使う。
  posterior ∝ prior * likelihood
priorとlikelihoodをそれぞれ求めれば、posterior(の相対比率)が求められる。

prior

モデルのところに記述した(a,b,s)の各パラメータのpriorを使う。
aのpriorであるNormal(Hma, Hsa)からa=Aとなる確率を求めると、p(A|Hma, Hsa)になる。
b,sでも同様にB,Sとなる確率を求めていき、それらの確率の積をとる。
  prior(A,B,S) = p(A|Hma, Hsa) * p(B|Hmb, Hsb) * p(S|Hms, Hss)

likelihood

(a,b,s)=(A,B,S)のとき、モデルは下記のものになる。
  y~Normal(Ax+B, S)

まず1つのサンプル(X1,Y1)について考える。
単純にモデルの変数を置き換えると以下のようになる。
  Y1~Normal(AX1+B, S)
すなわち「Y1は『平均がAX1+B, 標準偏差がSの正規分布』から生成された」ことを意味する。

この1サンプルでのlikelihoodは、
  p(Y1|AX1+B, S)
となる。X1,Y1,A,B,Sはすでに分かっている値なので、1つのスカラー値を導出可能である。

3つのサンプルでのlikelihoodは、それぞれのサンプルのlikelihooodの積をとる。
  likelihood(A,B,S,X,Y) = p(Y1|AX1+B,S)*p(Y2|AX2+B,S)*p(Y3|AX3+B,S)
ここで、サンプルごとに正規分布の平均が違うことに注意する。
すなわち、Y1,Y2,Y3はそれぞれ別の正規分布から生成されている。

                  • -

というわけでposteriorに戻ってくると、さっき求めたpriorとlikelihoodを使って以下のようになる。

  posterior ∝ prior * likelihood
  posterior ∝ {p(A|Hma, Hsa) * p(B|Hmb, Hsb) * p(S|Hms, Hss)} * {p(Y1|AX1+B, S) * p(Y2|AX2+B, S) * p(Y3|AX3+B, S)}

この式を見ると、サンプルの数が増えるごとにlikelihoodの影響力が強くなっていくことが分かる。

なお、posteriorの比例係数(ベイズの法則での分母)はモデルの形式とハイパーパラメータのみによって決まり、A,B,Sに依存しない値である。

  • MAP推定をする場合、posteriorの大小比較ができればいいだけなので、分母は気にしなくていい。
  • サンプリングをする場合、posteriorの比がとれればいいだけなので、分母は気にしなくていい。

一迅社のジャンル特化アンソロジー

一迅社のジャンル特化アンソロジーコミックが流行りに乗っかっていて面白かったのでタイトルを列挙してみる。
http://data.ichijinsha.co.jp/book/booksearch/booksearch_list.php?WRITER=%BD%BD%CA%B8%BB%FA%C0%C4&ISBN=&CATEGORY=&SALEY=&SALEM=&WORD=&TYPE=and&WI=1752


選定基準としては

  • 特定作品の二次創作でないもの
  • タイトルで内容が推測できるもの

とする。
また、シリーズのものは最初のみ載せる。
例えば女装少年アンソロジーコミックはシリーズで16冊出版されているが、最初の「女装少年アンソロジーコミック 白組」が出た2010年のみ記載。

                                                                            • -

2007年

2009年

  • 戦国主従異聞録

2010年

2011年

  • Girls Love
  • 拘束少女アンソロジーコミック

2012年

  • スポブラ少女アンソロジーコミック
  • ゾンビアンソロジーコミック

2013年

2014年

2015年

  • キマシ!

2016年

  • 異種恋愛物語集
  • オネエと私
  • 人外さんと恋する方法
  • 異世界転生して勇者になろう! アンソロジーコミック
  • 童貞を殺すアンソロジーコミック
  • 人外の嫁といちゃいちゃする アンソロジーコミック
  • 女の子の身体に入れ替わっちゃう アンソロジーコミック

2017年

  • 年下の男の子と恋するアンソロジー
  • ひょっとしてギャルは俺らに優しいのでは? アンソロジーコミック
  • 少年と私の事情アンソロジー
  • ボーイズ on ICE フィギュア男子アンソロジー
  • JCの魅力を楽しむ健全なアンソロジーコミック
  • 異世界に転生して愛されるアンソロジー
  • スキだらけJKといちゃエロできちゃう アンソロジーコミック
  • 年下の女子にマウントされる アンソロジーコミック

可読性を保ちつつデータをエスケープするおもちゃ

概要

CSVデータをsort,uniq,cut,awkだけで分析したいがそうはいかないことがある。
データ中にカンマが入っている場合、cutでは取得する列がずれる場合がある。さらに改行文字が入っている場合もうまくいかない。
その場合には、適切なCSVライブラリを使ってエスケープ・エスケープ解除をしなければならない。

データを全てbase64などでエンコードしてしまえば、改行文字やカンマなどの危険な文字は入ってこない。
ただし、その場合は人の可読性が全くなくなる。
可読性をそこそこに保ったまま危険な文字をエスケープする方法として、Quoted-printableを使うことが考えられる。
https://ja.wikipedia.org/wiki/Quoted-printable
これは、asciiの英数字と一部の記号はそのまま表示するが、ascii制御記号などの1バイトを、"=0A"などイコールの後に十六進数2文字で符号化する方式である。

ただし、日本語文を含むデータでQuoted-printableによるエスケープを行うと、日本語部分は符号化されて読めなくなる。
UTF8の漢字・ひらがな・カタカナはデータ中に含まれていてもsort,uniq,cut,awkするのにほとんど問題はない(※1,2)ため、できれば漢字・ひらがな・カタカナもそのまま残しておきたい。
符号化しない漢字の基準としては、とりあえずJIS第1第2水準漢字とする。
まとめると、以下のようなエスケープルールを作りたい。

  • 符号化しないバイト
    • アルファベット
    • 数字
    • 比較的安全なascii記号
    • JIS X 0208に含まれる文字に該当するバイト列 (UTF-8であることが前提)
  • 符号化するバイト
    • 「符号化しないバイト」以外全て
    • 例:ascii制御記号(タブ、CR、LFなど)、比較的危険なascii記号(カンマ、ダブルクオートなど) 、私が読めない文字(アラビア文字など)

試しにpythonで作った。パフォーマンスはかなり悪い。

(※1)UTF8では、asciiの1バイト文字では先頭ビットが0、漢字のようなマルチバイト文字では各バイトの先頭ビットが全て1になる。そのためマルチバイト文字中の1バイトをascii文字として解釈することはない。
https://ja.wikipedia.org/wiki/UTF-8

(※2)マルチバイト文字を含むテキストでsort,uniqするときには環境変数LC_ALL=Cを設定しておいたほうがいい.
https://linuxjm.osdn.jp/html/GNU_coreutils/man1/sort.1.html
google:sort uniq LC_ALL=C

実行サンプル

準備

まずはunicode.orgからJIS X 0208の文字一覧を取得してきて、扱いやすくするためJSONに変換する。

$ wget http://unicode.org/Public/MAPPINGS/OBSOLETE/EASTASIA/JIS/JIS0208.TXT
$ python create_jisx_json.py > jisx0208.json

サンプルデータとして、wikipediaのHyperText_Markup_Languageのページ( https://ja.wikipedia.org/wiki/HyperText_Markup_Language )からHTMLを用意。

<!DOCTYPE html>
<html lang="ja">
 <head>
  <meta charset="UTF-8">
  <link rel="author" href="mailto:mail@example.com">
  <title lang="en">HyperText Markup Language - Wikipedia</title>
 </head>
 <body>
  <article>
   <h1 lang="en">HyperText Markup Language</h1>
   <p>HTMLは、<a href="http://ja.wikipedia.org/wiki/SGML">SGML</a>
      アプリケーションの一つで、ハイパーテキストを利用してワールド
      ワイドウェブ上で情報を発信するために作られ、
      ワールドワイドウェブの<strong>基幹的役割</strong>をなしている。
      情報を発信するための文書構造を定義するために使われ、
      ある程度機械が理解可能な言語で、
      写真の埋め込みや、フォームの作成、
      ハイパーテキストによるHTML間の連携が可能である。</p>
  </article>
 </body>
</html>
エンコード

改行文字を含むascii制御文字は全てエスケープするため、確実に一行で表すことができる。

$ cat sample.html |python jisquopri_encode.py
<!DOCTYPE=20html>=0A<html=20lang=3D=22ja=22>=0A=20<head>=0A=20=20<meta=20charset=3D=22UTF-8=22>=0A=20=20<link=20rel=3D=22author=22=20href=3D=22mailto:mail@example.com=22>=0A=20=20<title=20lang=3D=22en=22>HyperText=20Markup=20Language=20-=20Wikipedia</title>=0A=20</head>=0A=20<body>=0A=20=20<article>=0A=20=20=20<h1=20lang=3D=22en=22>HyperText=20Markup=20Language</h1>=0A=20=20=20<p>HTMLは、<a=20href=3D=22http://ja.wikipedia.org/wiki/SGML=22>SGML</a>=0A=20=20=20=20=20=20アプリケーションの一つで、ハイパーテキストを利用してワールド=0A=20=20=20=20=20=20ワイドウェブ上で情報を発信するために作られ、=0A=20=20=20=20=20=20ワールドワイド ウェブの<strong>基幹的役割</strong>をなしている。=0A=20=20=20=20=20=20情報を発信するための文書構造を定義するために使われ、=0A=20=20=20=20=20=20ある程度機械が理解可能な言語で、=0A=20=20=20=20=20=20写真の埋め込み や、フォームの作成、=0A=20=20=20=20=20=20ハイパーテキストによるHTML間の連携が可能である。</p>=0A=20=20</article>=0A=20</body>=0A</html>=0A
エンコード(改行あり)

適当な位置で改行するオプションを使用(元データの改行位置とは関係ない)。
"DOCTYPE"や"アプリケーション"などの文字列はエスケープされないが、スペースやダブルクオートはエスケープされる。

$ cat sample.html |python jisquopri_encode.py --newline 40
<!DOCTYPE=20html>=0A<html=20lang=3D=22ja=22>=0A=20<head>
=0A=20=20<meta=20charset=3D=22UTF-8=22>=0A=20=20<link=20rel=3D=22a
uthor=22=20href=3D=22mailto:mail@example.com=22>=0A=20
=20<title=20lang=3D=22en=22>HyperText=20Markup=20Langu
age=20-=20Wikipedia</title>=0A=20</head>=0A=20<body>
=0A=20=20<article>=0A=20=20=20<h1=20lang=3D=22en=22>HyperText=20
Markup=20Language</h1>=0A=20=20=20<p>HTMLは、<a=20href
=3D=22http://ja.wikipedia.org/wiki/SGML=22>SGM
L</a>=0A=20=20=20=20=20=20アプリケーションの一つで、ハイパーテキストを利用してワー
ルド=0A=20=20=20=20=20=20ワイドウェブ上で情報を発信するために作られ、=0A=20=20=20=20=20=20ワー
ルドワイドウェブの<strong>基幹的役割</strong>をなしている。=0A=20
=20=20=20=20=20情報を発信するための文書構造を定義するために使われ、=0A=20=20=20=20=20=20ある
程度機械が理解可能な言語で、=0A=20=20=20=20=20=20写真の埋め込みや、フォームの作成、=0A=20
=20=20=20=20=20ハイパーテキストによるHTML間の連携が可能である。</p>=0A=20=20</
article>=0A=20</body>=0A</html>=0A
適当な圧縮データをエンコード

任意のバイト列をエンコードすることが可能。
ほとんどマルチバイト文字は現れないが、6行目にキリル文字のшが出現している。

$ cat create_jisx_json.py| gzip | python jisquopri_encode.py --newline 40
=1F=8B=08=00=22=C7=0DY=00=03=85R=B1n=C3=20=10=DD=F9=0AD=17=90=1C=E4z=A8=A2H^+=B5k3TJ=AC=88=1A=DC
=5CD=C0=82s=AB=AA=EA=BF=17l=27n=A6=DEb=B8w=EF=DD=F1=CEp=EE}@z=8A=DE=91=13D=0B=11iMw=0D=F9=04
<R=DF=1B=C7=D9=F3=D3KY=95k=B9}=DD=B2=82=B2=C0=04U=91v=1BBSt>P=0B=CEPp=B4=93=C1(=9Do=91
=8B=09=CE1=C2=F5=F8=91!b=80=9E=8B+=08=DD=08=EC=CA=86=D65ewl=E1=E5h=BDCp=83=B9&=B5=C2=8BV=EC-
=20g{d=8B=DC=E0=A0=F5=DA=1C@=A7*=F3=A1=2CO=84]=D5=FC=A9h=8F*$p=A9=94=E8=0Fo_=98=86=AE=0A=96$
=D1=1A&=A46=19=E6l=C0nu=FF=B0=CA=B9E=03=BB=F5D=C9f=8D=A7ш=E9=94=9C=18=9BH=E3=16=895=13=CD-=DF=1A
=F7=9E<N=8FI&/=8A7=D6=CC=C3&c=F6=FB=FF=9C=99=D7=27U=9F=B6=A6=F97=CB=5C=B6=994=D2=DEr=8B=B1C=CE
]=DB=CD=C04=CC=05=99n?=82=10=D2=07p=C8=F3=FF!=F5p=EE#=9F=DB=14=D4=B88=04sP=B1=05=A8=1F=95=8D=A6H
O=D7=C6a]=09A~=01*}=BF=A3Z=02=00=00
デコード

エンコードしたデータを元のバイト列に戻すことも可能

$ cat sample.html |python jisquopri_encode.py | python jisquopri_decode.py
<!DOCTYPE html>
<html lang="ja">
 <head>
  <meta charset="UTF-8">
  <link rel="author" href="mailto:mail@example.com">
  <title lang="en">HyperText Markup Language - Wikipedia</title>
 </head>
 <body>
  <article>
   <h1 lang="en">HyperText Markup Language</h1>
   <p>HTMLは、<a href="http://ja.wikipedia.org/wiki/SGML">SGML</a>
      アプリケーションの一つで、ハイパーテキストを利用してワールド
      ワイドウェブ上で情報を発信するために作られ、
      ワールドワイドウェブの<strong>基幹的役割</strong>をなしている。
      情報を発信するための文書構造を定義するために使われ、
      ある程度機械が理解可能な言語で、
      写真の埋め込みや、フォームの作成、
      ハイパーテキストによるHTML間の連携が可能である。</p>
  </article>
 </body>
</html>

コード(NYSLライセンス)

JISQuopri.py

import json

class JISQuopriEncode(object):
    CONTROLS = list(range(0x00, 0x20)) + [0x7f]
    MSBS = list(range(0x80, 0x100))
    ASCII_NOT_PRINTABLE = CONTROLS + MSBS

    BYTE_TO_BYTES = [i.to_bytes(1, "little") for i in range(256)]

    UTF8_3BYTE_MAX = 239
    UTF8_3BYTE_MIN = 224

    UTF8_2BYTE_MAX = 223
    UTF8_2BYTE_MIN = 192

    TO_HEXBYTES = [
        b"0",b"1",b"2",b"3",b"4",b"5",b"6",b"7",b"8",b"9",b"A",b"B",b"C",b"D",b"E",b"F"
    ]
    JISX_JSON_PATH = "jisx0208.json"

    def __init__(self, escape_char=b'=', escape_ascii_chars=[], escape_multibyte_chars=[], newline=None, ascii_only=False):
        self.escape_char = escape_char
        self.escape_byte = escape_char[0]
        self.ascii_only = ascii_only

        if isinstance(newline, int) and newline > 0:
            self.newline = newline
        else:
            self.newline = None

        self.create_table(escape_ascii_chars)
        if not ascii_only:
            self.create_multibyte_table(escape_multibyte_chars)


    def create_table(self, escape_target_src):
        escape_target = []
        for t in escape_target_src:
            if isinstance(t, str):
                escape_target.append(t.encode("ascii")[0])
            elif isinstance(t, bytes):
                escape_target.append(t[0])
            else:
                escape_target.append(t)
        escape_target.append(self.escape_byte)

        byte_to_encoded = [b""]*256
        for i in range(256):
            if i in escape_target:
                first = int(i / 16)
                second = i % 16
                val = self.escape_char + self.TO_HEXBYTES[first] + self.TO_HEXBYTES[second]
            else:
                val = self.BYTE_TO_BYTES[i]
            byte_to_encoded[i] = val

        self._tbl = byte_to_encoded

    def create_multibyte_table(self, escape_target_src):
        with open(self.JISX_JSON_PATH, "r") as f:
            jisx_list = json.load(f)

        self._mtbl = {}
        for char in jisx_list:
            if char["char"] in escape_target_src:
                continue

            if char["utf8length"] == 3:
                idx = char["utf8bytes"][0] * 65536 + char["utf8bytes"][1] * 256 + char["utf8bytes"][2]
            elif char["utf8length"] == 2:
                idx = char["utf8bytes"][0] * 65536 + char["utf8bytes"][1] * 256
            else:
                raise Exception

            self._mtbl[idx] = b"".join([self.BYTE_TO_BYTES[b] for b in char["utf8bytes"]])


    def encode(self, bytes_data):
        if self.ascii_only:
            return self._encode_onebyte(bytes_data)
        else:
            return self._encode_multibyte(bytes_data)

    def _encode_onebyte(self, bytes_data):
        result = []
        line_length = 0
        for b in bytes_data:
            result.append(self._tbl[b])
            if self.newline:
                line_length += 1
                if line_length >= self.newline:
                    result.append(b"\n")
                    line_length = 0

        if len(result) > 0 and result[-1] == b"\n":
            result = result[:-1]

        return b"".join(result)

    def _encode_multibyte(self, bytes_data):
        result = []
        line_length = 0

        len_bytes = len(bytes_data)
        i = 0
        while(i < len_bytes):
            b = bytes_data[i]
            success = False
            if (b >= self.UTF8_3BYTE_MIN) and (b <= self.UTF8_3BYTE_MAX) and (i + 2 < len_bytes):
                idx = b * 65536 + bytes_data[i+1] * 256 + bytes_data[i+2]
                val = self._mtbl.get(idx)
                if val is not None:
                    success = True
                    result.append(val)
                    i += 3
            if (not success) and (b >= self.UTF8_2BYTE_MIN) and (b <= self.UTF8_2BYTE_MAX) and (i + 1 < len_bytes):
                idx = b * 65536 + bytes_data[i+1] * 256
                val = self._mtbl.get(idx)
                if val is not None:
                    success = True
                    result.append(val)
                    i += 2
            if (not success):
                result.append(self._tbl[b])
                i += 1

            if self.newline:
                line_length += 1
                if line_length >= self.newline:
                    result.append(b"\n")
                    line_length = 0

        if len(result) > 0 and result[-1] == b"\n":
            result = result[:-1]

        return b"".join(result)

class JISQuopriDecode(object):
    CONTROLS = list(range(0x00, 0x20)) + [0x7f]
    BYTE_TO_BYTES = [i.to_bytes(1, "little") for i in range(256)]
    FROM_HEXBYTES = [None]*48 + [0,1,2,3,4,5,6,7,8,9] + [None]*7 + [10,11,12,13,14,15]

    def __init__(self, escape_char=b'='):
        self.escape_char = escape_char
        self.escape_byte = escape_char[0]

        self.is_control_tbl = [(i in self.CONTROLS) for i in range(256)]
        self.decode_state = 0

    def decode(self, bytes_data):
        result = []
        for b in bytes_data:
            if self.is_control_tbl[b]:
                continue
            elif self.decode_state == 1:
                self.decode_first_4bit = self.FROM_HEXBYTES[b] * 16
                self.decode_state = 2
            elif self.decode_state == 2:
                result.append(
                    self.BYTE_TO_BYTES[self.decode_first_4bit + self.FROM_HEXBYTES[b]]
                )
                self.decode_state = 0
            elif b == self.escape_byte:
                self.decode_state = 1
            else:
                result.append(self.BYTE_TO_BYTES[b])
        return b"".join(result)

jisquopri_encode.py

import sys
import argparse
from JISQuopri import JISQuopriEncode

if __name__ == "__main__":
    BUFSIZE = 1024

    parser = argparse.ArgumentParser()
    parser.add_argument('--ascii_only', '-a', dest='ascii_only', action='store_true')
    parser.add_argument('--newline', '-n', type=int, default=None, dest='newline')
    parser.set_defaults(ascii_only=False)

    args = parser.parse_args()

    if args.ascii_only:
        output_encoding = "ascii"
        escape_ascii_chars = JISQuopriEncode.ASCII_NOT_PRINTABLE + ['\\', '"', "'", ",", " "] # 一部のascii記号を追加でエスケープする
        escape_multibyte_chars = []
    else:
        output_encoding = "utf-8"
        escape_ascii_chars = JISQuopriEncode.ASCII_NOT_PRINTABLE + ['\\', '"', "'", ",", " "]
        escape_multibyte_chars = [" "] # 全角スペースはエスケープする
    jqe = JISQuopriEncode(escape_ascii_chars=escape_ascii_chars,
                          escape_multibyte_chars=escape_multibyte_chars,
                          newline=args.newline,
                          ascii_only=args.ascii_only)

    while True:
        dat = sys.stdin.buffer.read(BUFSIZE)
        if args.newline:
            sys.stdout.write(jqe.encode(dat).decode(output_encoding) + "\n")
        else:
            sys.stdout.write(jqe.encode(dat).decode(output_encoding))
        if len(dat) < BUFSIZE:
            break

    if not args.newline:
        print("")

jisquopri_decode.py

import sys
from JISQuopri import JISQuopriDecode

if __name__ == "__main__":
    BUFSIZE = 1024

    jqd = JISQuopriDecode()
    while True:
        dat = sys.stdin.buffer.read(BUFSIZE)
        sys.stdout.buffer.write(jqd.decode(dat))
        if len(dat) < BUFSIZE:
            break

create_jisx_json.py

import json
jislist = []
with open("JIS0208.TXT", "r") as f:
    for line in f.readlines():
        line = line.rstrip()
        if line[0] == "#":
            continue
        dat = line.split("\t")
        unicode_id = eval(dat[2])
        uchar = unicode_id.to_bytes(2,"little").decode("utf-16-le")
        utf8_bytes = [_byte for _byte in uchar.encode("utf-8")]
        utf8_length = len(utf8_bytes)
        if uchar == "\\":
            continue
        jislist.append({"char": uchar, "utf8bytes": utf8_bytes, "utf8length": utf8_length})


print(json.dumps(jislist, ensure_ascii=False, indent=2))

最近なろうで読んで面白かったもの

私、能力は平均値でって言ったよね!

http://ncode.syosetu.com/n6475db/
ハンター(冒険者のようなもの)の少女4人によるコメディ。全体的にノリがコントっぽい。
メイン4人は客観的には不幸な境遇なのだが、その不幸に押しつぶされることなく力業でねじ伏せていくし、冒険の中で問題が起きても楽しいイベントに無理矢理変換して解決していく。メンタルの強さが感じられる。


その無限の先へ

http://ncode.syosetu.com/n6811ck/
冒険者達が終わりの見えないダンジョン「無限回廊」に挑むコメディ。
出落ちレベルで個性の強いキャラが大量に登場する。特に変態とパンダが多い。その個性に沿った行動をさせるだけでネタが尽きない。
変態が変態のまま熱血展開をやることもあり、笑っていいのか燃えていいのかわからなくなる。
舞台が中世風、現代日本風、ダンジョン、SF、実質なんでもありな世界観でごった煮感が強烈。


転生少女の履歴書

http://ncode.syosetu.com/n4269cp/
魔法使いに非魔法使いが依存しきっている、というシンプルな世界観が軸となっていて、その世界観に対するキャラの向き合い方がそのままキャラ立ちになっている。
非魔法使いを家畜扱いする魔法使い、ブラック労働に従事する現場の魔法使い、魔法使いとの力の差に絶望する騎士、革命家などなど。
そこに非魔法使いでありながら現代日本の知識を持っている主人公が放り込まれる。「履歴書」のタイトルの通り、主人公がコロコロ立場を変えながらその場その場で人々と交流して人々の意識に影響を与えていく。


勇者召喚に巻き込まれたけど、異世界は平和でした

http://ncode.syosetu.com/n2273dh/
ひたすらに主人公がモテるインフレの激しいハーレム。世界トップクラスの力をもつ魔族に初対面で挨拶しただけで求婚されるレベル。
タイトル通り世界が平和で優しく、癒される。
作者によって先の展開を全てネタバレされるので注意(そこがいいのだが)。


令嬢はまったりをご所望。

http://ncode.syosetu.com/n1159db/
獣人や精霊や魔導師に囲まれる逆ハーレム。主人公も獣人達もかわいい。獣人をもふもふするシーンがなんかえろい。


プリンセス・アライヴ―異世界のお姫様が彼の料理で魔力を補充する―

http://ncode.syosetu.com/n9395dm/
銀髪で残念美少女な姫様と金髪ツインテの女子中学生声優に絶食系男子が迫られる(語弊あり)。
姫様が幸せそうにご飯を食べるのがとてもよい。


成人すると塩になる世界で生き残る話 (R18. 厳密にはなろうではない)

http://novel18.syosetu.com/n4991de/
バイオレンスでサバイバルでジュブナイルでエロ。ヒロインが盲目で黒髪ロングの美少女。
エンタメとして全く隙がない。

任意のバイナリを安全な文字で表す

base64URIのパーセントエンコーディングなどに共通のアイデアが、「任意のバイナリを安全な文字で表す」ことである。

モチベーション

テキストエディタで画像などのバイナリファイルを開くとひどい表示になる。
これは、テキストエディタがファイルのバイト列を何らかの文字コードで解釈しようとして失敗しているためである。いわゆる文字化けのようなもの。

あらゆるバイト列をascii印字可能文字で表現できれば嬉しいかもしれない。
そうすれば文字化けは起きないし、クリップボードに保存できるし、例えば「HTMLにテキストで画像を埋め込む」という応用などができる。

バイト列とascii印字可能文字の相互変換の方式として様々なものが考えられている。
ここからは、バイト列をascii印字可能文字に変換することを「エンコード」と呼び、バイト列とascii印字可能文字に変換するルールを「エンコード方式」と呼ぶことにする。

冗長度

https://ja.wikipedia.org/wiki/ASCII
asciiの印字可能文字はアルファベット52種、数字10種、記号33種の計95種しかない。これは1バイト(=8ビット)での01の組み合わせ256通りを表現するには不十分である。
なので、1バイトを表現するために、ascii印字可能文字からなる2文字以上の文字列で表現せざるをえない。

一方、asciiコードのテキストファイルを保存すると、1文字が1バイトで保存される。
なので、元のバイト列をそのままファイルに保存するよりも、エンコードして保存するほうがサイズが大きくなる。
冗長度として、(エンコードしたときの文字数)/(元のバイト数)というものを定義できる。
エンコード方式としては、冗長度が小さい方が望ましい。

文字の安全度

個人的な感覚として、ascii印字可能文字の中でも、比較的安全度が高い文字と低い文字がある。(左ほど安全度が高い)
【数字・アルファベット】 > 【-_+.】 > 【!?#$%&@~^*:;=】 > 【()<>[]{}'"`|/,】 > 【\】 > 【半角スペース】

安全度の基準としては「shellのコマンドラインで特殊な意味を持つかどうか」「テキストベースのデータ表現方法(XMLなど)で特別な意味を持つかどうか」くらいの感覚である。

安全度の低い文字はできればエンコードに使いたくない。
例えば、HTMLにテキストで画像を埋め込むときに < > " (半角スペース) があるとエラーが起きそうで嫌である。
エンコード方式としては、安全度の高い文字のみを使う方が望ましい。

エンコード方式例

16進数

代表的なエンコード方式としては16進数がある。
これは0123456789abcdefの16文字を4ビットに割り当て、2文字で1バイトを表す方式で、バイナリとの相性が良い。

1バイトを表すのに2文字必要なので、冗長度は2になる。

バイナリとの相性が良いので、バイナリエディタの表示方式としても使われる。
http://forest.watch.impress.co.jp/library/software/binaryeditbz/
sha1のようなハッシュ値でも使われる。
http://ftp.riken.jp/Linux/centos/7/isos/x86_64/sha1sum.txt

2進数・4進数・8進数・32進数

2進数: バイト列をそのまま全て01で表示する方式。
1バイトを表現するのに8文字使うため、冗長度が8と非常に冗長。
デジタルであることを表すかっこいいイメージに使われる。
google:image:digital
あとは学習用くらいだろうか。

4進数: 0123の4文字を2ビットに割り当てる。
冗長度は8/2=4

8進数: 01234567の8文字を3ビットに割り当てる。
冗長度は8/3=2.666..

32進数:123456789abcdefghijklmnopqrstuvの32文字を5ビットに割り当てる。
冗長度は8/5=1.6
ポケモン個体値で見ることがある。

64進数 (base64)

https://ja.wikipedia.org/wiki/Base64
アルファベット・数字A~Za~z0~9に記号+/を加えた64文字を使って6ビットを表現する。冗長度は8/6=1.333...
また、パディング用の文字として=も使う。

記号+/を含むのが厄介で、例えばURLに/は使いたくない。
そのような問題を回避するため、記号部分を変更したbase64変種がいくつか提案されている。
例えばURLのためのbase64は、記号に+/ではなく-_を使い、さらにパディングがない。

パーセントエンコーディング

URLにおいてマルチバイト文字や記号を扱うための方法
パーセントエンコーディング - Wikipedia

基本は16進数で、%とその後の2文字(0123456789ABCDEFの16種)で1バイトを表す。
ただし、アルファベット・数字・一部のascii記号はエンコードしないまま表示できる。
パーセント(%)自身も%25でエンコードしなければならない。

冗長度は、アルファベットや数字のところでは1、それ以外のところでは3になる。

quoted-printable

https://ja.wikipedia.org/wiki/Quoted-printable
パーセントエンコーディングとほぼ同じ。
=とその後の2文字(0123456789ABCDEFの16種)で1バイトを表す。
ただし、アルファベット・数字・ascii印字可能文字はエンコードしないまま表示できる。
=自身はエンコードしなければならない。

メールで用いられることがある。

unsigned charの配列

0~255の数字の配列として書いてもいいかもしれない。

  • 例:
    • "[227,129,147,227,130,147,227,129,171,227,129,161,227,129,175]"
    • utf8の「こんにちは」をJSONの数字配列として解釈可能な文字列にしている

冗長度はおよそ3.6

JavascriptのUint8Arrayで読み込める。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array

用途

これまで書いてきたものも含む。

HTMLにテキストで画像を埋め込む

http://dev.classmethod.jp/ria/image-performance-tuning-03/

7bit文字しか認識しないメール経路でも安全に日本語メールや添付ファイルを送る

http://ascii.jp/elem/000/000/588/588971/

テキストベースのデータ構造で、エスケープを気にせず扱う

例えばtsvを作成するとき、データにタブ文字や改行があると、単にタブでjoinするだけでは正しいtsvにならず、tsv作成用のライブラリを使って適切にエスケープする必要がある。
読み込むときも同様で、タブでsplitするだけでは駄目で、tsv読み込み用のライブラリを使わなければならない。

「データは全てbase64に変換する」というルールを適用すると、データにタブ文字も改行も入らなくなり、タブでjoin・splitするだけでtsvの作成・読み込みができるようになる。
shellでsort,cutを行うときにも安心して行うことができる。

ただし、変換せずテキストとして読むことは不可能になるし、毎回base64エンコード・デコードを気にするよりtsvライブラリを使うほうがマシであることが多い。
一応こんな選択肢もあるよ、くらいの認識にしておきたい。