にせねこメモ

はてなダイアリーがUTF-8じゃないので移ってきました。

Scrapbox記法→はてな記法 変換器をつくった

私はアイデアや下書きをまとめるのにScrapboxを利用している。一方で、情報をまとめて公開するのにははてなブログを主に利用している。
はてなブログでは「見たまま」「はてな記法」「Markdown」の三種類の編集方法が選べるが、私は「はてな記法」を使っている。理由は慣れである。

Scrapboxの記法もはてな記法も、簡潔な軽量マークアップ言語であり、プレーンテキストで編集できるので便利ではあるものの、それぞれに互換性がないので、相互に情報を移動させたければ変換が必要である。

しかしながら、そのような変換器が探しても見つからなかったため、Scrapboxからはてな記法へと変換するコンバータ(変換器)を作成した。

コンバータのページ

制限

  • 完全な一対一の変換はできないので、変換結果をはてなブログに投稿する前に確認して修正する必要があると思う。たたき台としての利用を想定している。
  • コードブロックのシンタックスハイライトは、とりあえず私が使う言語だけ対応させた。Scrapboxはてなブログが対応している他の言語が必要なら教えてください。
  • 画像も変換に未対応な種類があるかもしれません。
  • 本コンバータの逆変換、つまりはてな記法Scrapboxへの変換は作る気はない。それを私は必要としていないので。

やや特殊な変換

  • 次の3つは単体で表示するためにはコードブロックに入れる必要があるため、その一文字だけがインラインコードになっている場合、<code>~</code>で囲むことはしない。
    • `[`[
    • `]`]
    • `\```
  • 見出し。Scrapboxには見出し専用の記法はないので、行頭にインデントがなく、かつ文字を大きく表示する記法単体しか行に存在しない場合に見出しとした。
    • [**** 大見出し]* 大見出し
    • [*** 中見出し]** 中見出し
    • [** 小見出し]*** 小見出し

ソース

何をやってるのかはソース読むと分かるかもしれません。行きあたりばったりで書いてるので読んでわかるかは分かりませんが…。
github.com

テスト用データ

テストに使ったScrapboxページのコードを載せておく。変換結果を表示して見比べてみると良いかと思う。

Scrapbox表記テストページ
変換器のテストに使います。
[**** 大見出し]
[*** 中見出し]
[** 小見出し]
[* 太字1][[太字2]]ふつうの文字[** ややでかい文字][*** もっとでかい文字][**** 更にでかい文字][***** もっとでかい文字]
[/ italic] [- stroke] [*/* bold bigger italic] [**-/ bold bigger stroke italic] [_ underline] [_/* underline italic bold]

数式[$ \sin (x)]
> 引用1
>引用2
 >引用3
$ cat commandline1
% cat commandline2
	$ cat commandline3
`コード`記法。`[`~`]` バッククォート自体は`\`` と入力する。`\` \` 
code:js
	function test(){
   console.log("Hello World!");
 }
code:py
 import numpy as np
 np.array([])
code:test.md
	# hoge
	## hogehoge
table:tabletitle0
	1111	2222	3333	4444
	aaaa	bbbb	cccc	dddd

	箇条書きlevel1
		箇条書きlevel2
			箇条書きlevel3
				箇条書きlevel4
	1. 数字
		2. 数字
			3. 数字
画像
[https://nixeneko.sakura.ne.jp/hatenablog/20180316_oembed/simarin.jpg]画像1
[[https://nixeneko.sakura.ne.jp/hatenablog/20180316_oembed/simarin.jpg]]画像2
URL
https://www.yahoo.co.jp
[Yahoo https://www.yahoo.co.jp]
[https://www.yahoo.co.jp Yahoo]
[hoge https://www.yahoo.co.jp https://www.google.co.jp]
[https://www.google.co.jp https://www.yahoo.co.jp https://www.yahoo.co.jp]
ほかどうでもいいやつ
[ページタイトル]
[ページタイトル.icon]

アニメキャラの目のハイライト消しデータセット

次のブログ記事で利用したデータセットを公開します。

データセットの内容の一部。左側に目にハイライトのある画像、右側にハイライトのない画像を並べている。

説明

アニメの顔の画像で、目にハイライトがあるもの(オリジナル)と、そこから目のハイライトを塗りつぶして消したものとのペアです。500ペアあります。
ペアは完全に1対1の対応がとれているので、pix2pixなどの1対1で対応がとれた教師データを必要とする画像変換モデルに適しています。

画像は2017年冬(1月~3月)に放送されていたアニメのスクリーンショットから顔を切り出したもので、正方形で一辺が256px以上720px未満となっています。


このデータセットは、日本国の著作権法(2021年1月1日施行)の第三十条の四に基づいて公開します。
参考: 進化する機械学習パラダイス ~改正著作権法が日本のAI開発をさらに加速する~ | STORIA法律事務所

Twitterハッシュタグの仕様について

f:id:nixeneko:20210130202127p:plain

まえがき

Twitterに"#᥋ᵍᶜₒ𝖛ịⅆ"というハッシュタグが流れているのを見た。内部的には"#5GCovid"と等しいようだった。タグの指す内容は置くとして、これが同一視されてるのは奇妙な気もした。
これは、他にも同様のハッシュタグを作れるのでは?と思って、変換器を作った。
nixeneko.github.io

作成する際にTwitterハッシュタグの仕様を探って、なんとなく雰囲気がつかめたのでメモとして残しておく。

Twitterハッシュタグの仕様

(2021/01/29現在のため、変更される可能性がある)

  • #で始まり、ハッシュタグに使えない文字が来るかツイート末まで続く
  • #はツイート頭にあるか、#の前にはスペースが必要
  • $で始まるハッシュタグもあるが、ASCII英字6文字まで、とか制限が強い

ハッシュタグに使える文字

  • Unicode 7.0に存在する文字である必要がある(8.0以降で追加された文字はハッシュタグにならない)
  • ハッシュタグに使える文字は、基本的には、次のgeneral categoryをもつUnicode文字であるようだ:
    • Lu……Letter, Uppercase
    • Ll……Letter, Lowercase
    • Lt……Letter, Titlecase
    • Lm……Letter, Modifier
    • Lo……Letter, Other
    • Mn……Mark, Non-Spacing
    • Mc……Mark, Spacing Combining
    • Me……Mark, Enclosing
    • Nd……Number, Decimal Digit
  • このほかにも個別に追加されていると思われる記号がある。例:
    • 「_」アンダースコア
    • 「・」中黒
    • 「゠」ダブルハイフン U+30A0 Katakana-Hiragana Double Hyphen
  • 数字やマークだけではハッシュタグにはならない

ハッシュタグの同値判定の仕様の推測

  • 大文字小文字を区別しない
  • 文字を同一視するためにUnicode正規化が施されているはず。
    • 結合文字を処理する都合から互換分解(NFKD)だと思われる。ツイート自体にはNFCが使われている
    • あるいは、Unicode Collation Algorithmとかを一部利用しているのかも?
  • ある種の結合文字(combining marks)は無視される
    • "#天デ部"と"#天テ\u0301\u3099部"(Combining Acute + Combining濁点)が同一視される(\uXXXXはUnicode文字を表す)
    • 無視される結合文字の一覧は後掲する
    • ダイアクリティカルマーク合成済み文字の場合は、正規化により分解されて、マークが除去される
  • ひらがな・カタカナの、濁点・半濁点つきの文字とそうでない清音の文字は区別される。また、ひらがなとカタカナも区別される
    • (Unicode正規化の互換分解で半角濁点U+FF9Eは合成用濁点U+3099に変換されるので以下の話は当然なので消した)
    • 清音の仮名の次に半角濁点・半濁点が来ると、濁音・半濁音の仮名として扱われる(ひらがなカタカナともに) (間に一部の結合文字が挟まっててもよさそう)
    • 仮名と(半角または合成用)濁点・半濁点の間にU+034F COMBINING GRAPHEME JOINERが入ると濁点が消えるっぽい。なぜ…?
  • ある種の10進数字は、ASCII文字の数字と同一視される
    • Decimal digit valueの示す数字と同一視されるのかと思ったが、Decimal digit valueが存在していてもASCII数字と同一視されないものがあった。次である:
      • U+0de6-U+0def SINHALA LITH DIGIT
      • U+a9f0-U+a9f9 MYANMAR TAI LAING DIGIT
      • U+104a0-U+104a9 OSMANYA DIGIT
      • U+11066-U+1106f BRAHMI DIGIT
      • U+110f0-U+110f9 SORA SOMPENG DIGIT
      • U+11136-U+1113f CHAKMA DIGIT
      • U+111d0-111d9 SHARADA DIGIT
      • U+112f0-U+112f9 KHUDAWADI DIGIT
      • U+114d0-U+114d9 TIRHUTA DIGIT
      • U+11650-U+11659 MODI DIGIT
      • U+116c0-U+116c9 TAKRI DIGIT
      • U+118e0-U+118e9 WARANG CITI DIGIT
      • U+16a60-U+16a69 MRO DIGIT
      • U+16b50-U+16b59 PAHAWH HMONG DIGIT
  • 他にも、特殊な同一視がみられる。個別にルールが追加されているのかもしれない。次のような例を見つけた:
    • "Å"→"AA", "å"→"aa"
    • "Ä"→"AE", "ä"→"ae"
    • "Æ"→"AE", "æ"→"ae"
    • "ı"→“i" (トルコ語用のdotless iの対応)
    • "Ö"→"OE", "ö"→"oe"
    • "Ø"→"OE", "ø"→"oe"
    • "Ü"→"UE", "ü"→"ue"
    • "ß"→"ss" (エスツェット)

無視されるCombining Marksの一覧を次に挙げる。抜けがあるかもしれない。

ignorable_marks = [
    "\u0300", "\u0301", "\u0302", "\u0303", "\u0304", "\u0305", "\u0306", "\u0307", 
    "\u0308", "\u0309", "\u030a", "\u030b", "\u030c", "\u030d", "\u030e", "\u030f", 
    "\u0310", "\u0311", "\u0312", "\u0313", "\u0314", "\u0315", "\u0316", "\u0317", 
    "\u0318", "\u0319", "\u031a", "\u031b", "\u031c", "\u031d", "\u031e", "\u031f", 
    "\u0320", "\u0321", "\u0322", "\u0323", "\u0324", "\u0325", "\u0326", "\u0327", 
    "\u0328", "\u0329", "\u032a", "\u032b", "\u032c", "\u032d", "\u032e", "\u032f", 
    "\u0330", "\u0331", "\u0332", "\u0333", "\u0334", "\u0335", "\u0336", "\u0337", 
    "\u0338", "\u0339", "\u033a", "\u033b", "\u033c", "\u033d", "\u033e", "\u033f", 
    "\u0340", "\u0341", "\u0342", "\u0343", "\u0344", "\u0345", "\u0346", "\u0347", 
    "\u0348", "\u0349", "\u034a", "\u034b", "\u034c", "\u034d", "\u034e", "\u034f", 
    "\u0350", "\u0351", "\u0352", "\u0353", "\u0354", "\u0355", "\u0356", "\u0357", 
    "\u0358", "\u0359", "\u035a", "\u035b", "\u035c", "\u035d", "\u035e", "\u035f", 
    "\u0360", "\u0361", "\u0362", "\u0363", "\u0364", "\u0365", "\u0366", "\u0367", 
    "\u0368", "\u0369", "\u036a", "\u036b", "\u036c", "\u036d", "\u036e", "\u036f", 
    "\u0610", "\u0611", "\u0612", "\u0613", "\u0614", "\u0615", "\u0616", "\u0617", 
    "\u0618", "\u0619", "\u061a", "\u064b", "\u064c", "\u064d", "\u064e", "\u064f", 
    "\u0650", "\u0651", "\u0652", "\u0653", "\u0654", "\u0655", "\u0656", "\u0657", 
    "\u0658", "\u0659", "\u065a", "\u065b", "\u065c", "\u065d", "\u065e", "\u065f", 
    "\u0670", "\u06d6", "\u06d7", "\u06d8", "\u06d9", "\u06da", "\u06db", "\u06dc", 
    "\u06df", "\u06e0", "\u06e1", "\u06e2", "\u06e3", "\u06e4", "\u06e7", "\u06e8", 
    "\u06ea", "\u06eb", "\u06ec", "\u06ed", "\u093c", "\u094d", "\u3099", "\u309a"]

ひらがな・カタカナ用の合成用濁点・半濁点も無視される。ものの、濁点や半濁点がついた仮名をそうでない仮名と同一視することはしない(「テ」と「デ」は別)。そのため、濁点や半濁点のつき得る仮名についた合成用濁点・半濁点は削除しない。
これは、Unicode正規化により、NFCなどで合成してから合成用濁点・半濁点を除去するという順序にするとうまくいくと思う。

考えられるハッシュタグの同値判定の流れ

同値判定と書いてるけど、collation (照合)とよぶものらしい。そのために、Unicode Collation Algorithm (UCA)という標準が存在し、それをカスタマイズして使っている可能性もあるが、よくわからない。

さて、ともかく次のような流れで再現できると思われる:

  • 仮名+半角濁点・半濁点→濁音・半濁音の仮名に変換(Unicode正規化(NFKD/NFKC)で普通の仮名になるので略)
  1. Unicode正規化(NFKD)による互換分解
  2. 取り除けるマークを取り除く(合成用濁点・半濁点についてはここでは残す)
  3. 小文字にする
  4. "ı"→“i"、"ß"→"ss"などのルールの適用
  5. Unicode正規化(NFC?)による合成
  6. 合成されなかった合成用濁点・半濁点を除去

難読化の仕組み

上記したハッシュタグの同値判定を逆方向にたどっていくと、最終的に同値に判定される様々なバリエーションが得られる。詳しくはソースでも見てください。行きあたりばったりなので見てわかるかは知らないですが…
github.com

Python3で文字列をUTF-16のコード列(整数のリスト)に変換

Unicode基本多言語面(BMP)外、つまりUnicodeスカラ値がU+10000以降の文字について、(UTF-16の)サロゲートペアのコードを求めたい、という需要があった。これは、Win32 APIのSendInput関数がUnicodeの値をUnsigned Shortでしか指定できないので、BMP外の文字はサロゲートペアで入力しないといけないということに対応するため。

Python3においては文字列(str型)はUnicode文字列を指すので、以下文字列とだけ書く。Python2の場合は適当に読み替えてください。

文字列をUTF-16のコードポイント(整数)のリストに変換

def str_to_utf16codepoints(s):
    bs = s.encode(encoding='utf_16_be')
    return [int.from_bytes(bs[n:n+2], 'big') for n in range(0, len(bs), 2)]

文字列をUTF-16-BE(ビッグエンディアン)の文字列に変換し、2バイトごとに区切って整数に変換する。

str_to_utf16codepoints("sushi寿司🍣")

[115, 117, 115, 104, 105, 23551, 21496, 55356, 57187]

を返す。

UTF-16のコードポイントのリスト→文字列

上の逆変換。

def utf16codepoints_to_str(ns):
    bs = b''.join([n.to_bytes(2, byteorder="big") for n in ns])
    s = bs.decode('utf_16_be')
    return s

要素技術

文字列→UTF-16-BE (bytes型)に変換

str.encode()を使う。返り値はバイト列(bytes型)になる。

"🍣".encode(encoding='utf_16_be')

バイト列を整数(int型)に変換

Python 3.2以降だとint.from_bytes()が使えるらしい。1つ目の引数はバイト列、2つめはエンディアンの指定。

int.from_bytes(b'\xff\xf0', 'big')

みたいな感じでいける。

さもなくば

def int_from_bytes(bytes):
    val = 0
    for b in bytes:
        val = (val << 8) + b
    return val

とかでできると思う。

2020年よかったもの: 買ったもの, 映画, アニメ, 音楽, ソシャゲ

2020年が終わった。他の人がやってるのを見て自分もやってみようと思ったので、2020年に経験したよかったものをまとめておく。

買ったもの

Pixel 4a

今まで使ってたスマホHuawei P10 Liteだったというのもあり、めっちゃ体感が良い。きびきび動く、本体スピーカーがステレオで中低音がそこそこ出る、記憶容量128GBあるのでスマホゲームそれなりにインストールしても余裕ある。など。MicroSDカード刺さんないのを除けば不満はない。
たぶんハイエンドのものと比較したらそれなりなんだろうけど、今まで使ってたものと比べると非常に快適。まじで買ってよかった。

Xiaomi Mi Smart Band 4

活動量計。時計+心拍数計+歩数計+睡眠計とかそんな感じ。自分の使い方だと満充電で1か月弱電池が持つので楽。風呂入るとき以外はずっとつけてる。防水なので風呂入るときもつけたままでいいんだけど。
睡眠がスコアで出るのが面白いし、心拍数変化のグラフがでるの面白い。最近見てないが。
最近はもっぱら腕時計として使っている。ボタン押さないと時間表示されないので腕時計としては微妙。

王 海清『蒙日辞典』


2020年はモンゴル語(特にモンゴル文字)関連書籍をぼちぼち集めていた。
この辞書はモンゴル文字で引ける日本語のモンゴル語辞典として貴重である。しかし、出版されたのがちょっと前というのもあって中古で結構値段した。メルカリで不用品を売って作った金で買った。
いつでも引けるというのが有難くて買ったのではあるけど、実際はそこまで使わないかもしれない。しかし満足感というか安心感がある。

映画

ジョゼと虎と魚たち

アニメ映画。2020年に見た中で一番良かった。本当に90分か??って思うくらい内容が詰めこまれててすごかった。
映画によっては見終わると内容が思い出せないものもあるけれど、この作品は後からいいシーンがいろいろ浮かんできたので、それだけ印象に残ったのかなと思う。
ネタバレになるので多くは語らないが、『聲の形』や『心が叫びたがってるんだ。』が好きならきっと気に入ると思う。(1/8現在)まだやってるのでぜひ見に行ってほしい。

テレビアニメ

春夏は放送される新作がすごく少なかった。

『映像研には手を出すな!』

もともと原作漫画読んでて、アニメ化ということで、期待半分不安半分で見てみた。監督は湯浅政明さん。
アニメを作る話(どちらかというとアニメ作るより設定を作ってる描写のが多いけど)なので、アニメという媒体はすごい合ってるというか、映像による説得力は強かった。作中作であるアニメが映像として動いてるのは漫画では表現できないので。
アニメという媒体の中でアニメを作っているというのを活かした演出が結構あり、全体として湯浅監督の作風と合ってたと思う。創作のワクワク感が感じられるいい作品だった。
FOD独占配信ってことで結構損してると思う。

『ガルパピコ大盛り』

バンドリ見てないけどガルパピコ(1期)は見てたので見た。一話3分なのでサッと見れていい。
バンドリとは異なる独自の世界が展開してる気がする。原作のスピンオフなのに好き勝手やってるのが最高。ギャグ作品にありがちな投げっぱなしな話が多くて好き。
話数によって面白いかどうかはまちまちだけど、実験的な話も多く、すごく面白い話や頭おかしい話があり、そういうのが良くて見てた。

『アサルトリリィ BOUQUET』

アクションドールを原作とするメディアミックス作品。主役の名前が「ゆゆ(夢結)」と「りり(梨璃)」、戦う女の子を「リリィ」と呼び、学校が「百合ヶ丘女学院」などとにかく百合のモチーフぶっこんどきゃいいだろ感を感じる。
「ヒュージ」という怪物が襲ってくる世界で「リリィ」と呼ばれる少女たちが戦う、というのが大筋。まどマギと多少雰囲気近いかも、制作シャフトだし。
百合か?と言われるとそこまで百合要素が中心というわけではない気もする。謎は残しつつも綺麗に終わったのでよかった。ふとももがすごい。

ラブライブ!虹ヶ咲学園スクールアイドル同好会

一話見て百合か!?って感じで見始めた。ラブライブシリーズだけど、ラブライブのある世界というだけで他のシリーズの内容はほぼ出てこないのでこれだけ見て問題なく楽しめる。
スクールアイドル同好会をつくるけど、個性的なメンバーみんなが意見違うのでグループ組まずにソロアイドルで活動していこう!という話。嫌な奴が全く出てこないのですごく視聴感がよい。
最初は一話ごと一人のキャラを掘り下げていったんだけど、5~7話の畳みかけがすごくよかった。りなりー と かすかすがすき。

聞いてた音楽

別に2020年のものではないものが多いが、主にレンタルで聞いてるので一昔前のものになりがち。今年はラジオをよく聞いてたのであんまり音楽を聞いてなかった。
基本的にアニソンが多い。見事に女性ボーカルしかないな。

(~)はCD発売年のはず。配信はもっと早いのあるかも。公式で上がってる奴だけYoutube映像を載せておく。

DIALOGUE+「はじめてのかくめい!」(2019)


アニメ『超人高校生たちは異世界でも余裕で生き抜くようです!』OP。
作詞作曲は田淵智也さん、編曲は田中秀和さん。完全に田淵節。畳みかけるようなめっちゃ忙しい曲で、ハイテンションで愉快。

鹿乃「罰と罰」(2020)

YouTube検索: 鹿乃 罰と罰
作詞が鹿乃さん、作曲が田中秀和さん、編曲が佐高陵平(y0c1e)さん。田中さんいつも強い曲作るな~~~と思っている。ジャジーでダウナーで耳に残る曲。

ネクライトーキー「オシャレ大作戦」(2018)


作詞作曲: 朝日廉さん。
歌詞の「お金もない、努力もしない二十五を過ぎたら死ぬしかない」がすごく印象に残っている。
J-Waveで流れてたライブのCMで知ったバンド。声が特徴的ですき。メジャーデビューアルバム『ZOO!!』(2020)も聞いたけど、インディーズ時代の方が曲が(というか歌詞が)尖ってる感じがした。
アニメ『秘密結社 鷹の爪 ~ゴールデン・スペル~』でOPを担当したのもありどんどん有名になってきてる感じがする。いいですね。

ReoNa「SWEET HURT」(2018)


アニメ『ハッピーシュガーライフ』ED。作詞作曲 ハヤシケイさん、編曲 PRIMAGIC。
歌詞の「腫れた背中のかさぶた 千切れた羽根が生えていた跡 傷を抉っては確かめた まだ血は赤いこと」が印象的すぎる。

一ノ瀬志希宮本フレデリカ「クレイジークレイジー」(2019)


アイドルマスターシンデレラガールズ』の曲。作詞作曲がTaku Inoueさん。
イノタク~~~~~~~!!!!! 曲が強すぎる。Future BassとかJersey Clubとかいうジャンルらしい。クラブっぽい音にメロディで殴ってくる。強い。

shami momo「町かどタンジェント」(2019)


アニメ「まちカドまぞく」OP。
曲がいい。落ちついてるのに疾走感があって、サビの盛り上がりに恍惚感があって最高。
作詞作曲は辻林美穂さん。この曲で辻林美穂さんを知ったのでアルバム『Clarté』を聞いてみたけど、落ち付いててすごいいい感じだった。他のアルバムも聞いてみたい。

にゅーろん☆くりぃむそふと「むにゃむにゃゲッチュー恋吹雪!」(2018)

Youtube検索: むにゃむにゃゲッチュー恋吹雪
ガールフレンド(仮)の曲。ガールフレンド(仮)のキャラソンが結構好きで、キャラクターソングシリーズのVol. 1~7を度々聞き返してるんだけど、新曲が出てたのを遅ればせながら知ったので聞いた。
作詞作曲: 田淵智也さん、編曲: やしきんさん。もろ田淵節って感じで笑う。何食ってたらこんな歌詞でてくんのか謎。

ソシャゲ

D4DJ Groovy Mix

通称グルミク。ブシロードが満を持して送り出してきた、DJをテーマとしたスマホ向けの音ゲー(開発はDonuts)。
とにかく収録曲の選定が謎。スーパーでよく聞く「呼び込み君 No.4」が追加されたというのを見て爆笑しながらインストールすることにした。(編曲がチップチューンとかやってたSEXY-SYNTHESIZERさんでびっくり)
youtu.be

幅広い年齢層に遊んでもらいたいということらしくそれなりに懐メロのカバーも入っている。古いところだと山本リンダ「どうにもとまらない」とか、50年前の曲がバリバリのEDMっぽいミックスになっててすごい好き。他にもクラブ映えするサウンドの曲が多く、プレイしてて楽しい。結構MOGRAとかによくいる人が参加してる。
他にも懐かしのゲーム音楽も多い。あんまり分からないが…
最近もどんどん曲が追加されている。ゲームのテーマがDJなので、カバー曲のみならず原曲とかインストをどんどん追加できるのは強み。

12月初めに始めて毎日やってたらだんだん高レベルの曲もクリアできる様になってきた。たのしい。少なくとも全曲開放するまではやると思う。
ユーザーIDがたぶん oDgpcYR9 なのでフレンド申請ください。nxnkという名前です。


以上。

Windows 10 (バージョン2004)のMS-IMEでやまぶきRの「,」等の入力がうまくいかない

2023-01-14追記

Windows 11では解決してるので上げられる場合はWindows 11に上げましょう。

症状

なんか最近、たぶん12月のWindows Update以来、Windows 10上のMS-IME (Microsoft IME 日本語) + やまぶきR(ローマ字入力用)で「,」「.」などが上手く入力できない時がある。
設定ファイルで、シングルクォート('~')で囲んだ文字は、文字直接入力としてIMEの未確定文字として入力されるはずであるが、それが未確定文字として入力されなくなった。

具体的には次のような症状を示す。(「,」は設定ファイルで「','」として書かれるものを指す。シングルクォート「'」で囲まれた文字直接入力設定の文字ならなんでもよい)

  • 未確定文字列があるときに「,」を入力しようとすると入力されない。または…
    • 未確定文字列の前か後に確定状態で入力される
    • 未確定文字列が消えて「,」だけが残る
    • 未確定文字列が確定される、など
  • 未確定文字列がない場合に「,」を入力しようすると確定した状態で入力される

この動作はアプリによって様々であるが、「,」がIMEの未確定文字として入力できないのは共通している。
Firefox, Chrome, Explorer, Notepad++, Slack, Discord, メモ帳などで試したが、どのプログラムでも再現した。

ATOKではそうならないという情報を得たので、MS-IMEの問題っぽい。実際、以前はMS-IMEを使っていても問題は起きていなかった。Windows 10 May 2020 Update (バージョン2004)によってMS-IMEが新しいものに置き替わり、これに不具合が残っているようだ。結局、MS-IMEの古いバージョンを使うようにしたら解決した。

ソフトウェアのバージョン

  • OS: Windows 10 バージョン 2004 (OS ビルド 19041.685)
  • Microsoft IME 日本語(バージョン 10.0.19041.1*1?)
    • IMJPTIP.DLL, IMJPAPI.DLL, imjpcus.dll, IMJPRANKER.DLL, imjputyc.dllの更新日付がより新しく、12/17に新しくなっている (バージョン 10.0.19041.662)
  • やまぶきR Ver 1.11.1

ワークアラウンド

とりあえずの回避策として、MS-IMEの設定から「以前のバージョンのMicrosoft IMEを使う」をオンにしたら解消される。(とはいえ、互換用に残してるだけだと思うのでいつまで使えるかは分からないが…)

手順は、

  1. 言語バーのMS-IMEの「あ」または「A」の表示となっている部分を右クリック→「設定(S)」
  2. 「全般」
  3. 「以前のバージョンのMicrosoft IMEを使う」をオンにする

あるいは、

  1. Win+X (または画面左下端のWindowsロゴを右クリック)→「設定(N)」
  2. 「時刻と言語」
  3. 「言語」
  4. (「優先する言語」の下から)「日本語」→「オプション」
  5. (「キーボード」の下から)「Microsoft IME」→「オプション」
  6. 「全般」
  7. 「以前のバージョンのMicrosoft IMEを使う」をオンにする

とする。

もしくは、ATOKGoogle日本語入力などほかのIMEを使うと良さそう。

検証

すべてMS-IME (Microsoft IME 日本語)による。

やまぶきR

上記の通り。省略。

DvorakJ

「,」「.」等、その他Unicode文字として出力するように設定されたものについて、同様の症状が発生する。

紅皿

README.pdfによると「Ver. 0.1.4.2 … Windows 10 May 2020 Updateに対応するため、アプリに出力する文字をすべて半角または制御記号とした。」とあり、Windows 10 May 2020 Update (バージョン2004 (20H1))に問題があることが示唆されている。文字直接入力に相当する機能がないため、問題なく動く。

Win32 API

というか、DvorakJや紅皿が使っているプログラミング言語AutoHotKeyがどうやってUnicode文字列を送ってるかを調べたら、どうやらWin32 APIのSendInput()を使ってるっぽい。やまぶきRも同じかもしれない。

SendInput関数の引数の一つとしてINPUT構造体を与えるが、その内部に設定されるKEYBDINPUT構造体に wScan=ユニコードスカラ値, dwFlags=KEYEVENTF_UNICODE を設定して、その状態でSendInput関数を実行すると、例えば漢字などのUnicode文字が入力できる*2

Python3用のコードを置いておく。コードは、

にあるものに変更を加えたものである。

# https://stackoverflow.com/questions/62189991/how-to-wrap-the-sendinput-function-to-python-using-ctypes
import ctypes
import ctypes.wintypes
import time

KEYEVENTF_UNICODE = 0x4
KEYEVENTF_KEYUP = 0x2
INPUT_KEYBOARD = 1

# not defined by wintypes
ULONG_PTR = ctypes.c_ulong if ctypes.sizeof(ctypes.c_void_p) == 4 else ctypes.c_ulonglong

class KEYBDINPUT(ctypes.Structure):
    _fields_ = [('wVk' ,ctypes.wintypes.WORD),
                ('wScan',ctypes.wintypes.WORD),
                ('dwFlags',ctypes.wintypes.DWORD),
                ('time',ctypes.wintypes.DWORD),
                ('dwExtraInfo',ULONG_PTR)]

class MOUSEINPUT(ctypes.Structure):
    _fields_ = [('dx' ,ctypes.wintypes.LONG),
                ('dy',ctypes.wintypes.LONG),
                ('mouseData',ctypes.wintypes.DWORD),
                ('dwFlags',ctypes.wintypes.DWORD),
                ('time',ctypes.wintypes.DWORD),
                ('dwExtraInfo',ULONG_PTR)]

class HARDWAREINPUT(ctypes.Structure):
    _fields_ = [('uMsg' ,ctypes.wintypes.DWORD),
                ('wParamL',ctypes.wintypes.WORD),
                ('wParamH',ctypes.wintypes.WORD)]

class DUMMYUNIONNAME(ctypes.Union):
    _fields_ = [('mi',MOUSEINPUT),
                ('ki',KEYBDINPUT),
                ('hi',HARDWAREINPUT)] 

class INPUT(ctypes.Structure):
    _anonymous_ = ['u']
    _fields_ = [('type',ctypes.wintypes.DWORD),
                ('u',DUMMYUNIONNAME)]

#print(sizeof(INPUT))

SendInput = ctypes.windll.user32.SendInput
SendInput.argtypes = ctypes.wintypes.UINT,ctypes.POINTER(INPUT),ctypes.c_int
SendInput.restype = ctypes.wintypes.UINT

def send_unicode(s):
    i = INPUT()
    i.type = INPUT_KEYBOARD
    for c in s:
        i.ki = KEYBDINPUT(0,ord(c),KEYEVENTF_UNICODE,0,0)
        SendInput(1,ctypes.byref(i),ctypes.sizeof(INPUT))
        i.ki.dwFlags |= KEYEVENTF_KEYUP
        SendInput(1,ctypes.byref(i),ctypes.sizeof(INPUT))

if __name__ == '__main__':
    time.sleep(3)
    send_unicode('漢')

これを試してみたところ、日本語入力がオンのときにSendInputで入力された「漢」は、MS-IMEでは確定状態になったが、Google日本語入力では未確定状態となった。それっぽい。

*1:C:\Windows\System32\IME\IMEJP\imjpuexc.exe の右クリックメニューからプロパティ→詳細→製品バージョン。関係ないけどWindows 8の時はMS-IMEのバージョンが15.いくつになっていたようで、番号が若返ってる気がする、というかOSバージョンと統一されたっぽい

*2:Unicodeスカラ値を指定するwScanはUnsigned Short (16bit)である。16ビットで表せない基本多言語面(BMP)以外のコード値はどう入力するかというと、サロゲートペアを使うといいらしい。 https://stackoverrun.com/ja/q/6096887 を参照。

郵便番号を何桁読めば都道府県が判定できるのか?

はじめに

郵便番号から都道府県を取得したい。理由は、宅配便の配送料*1を計算するのに使いたかったので。


郵便番号と自治体等の住所の対応が調べられるデータを日本郵便が配布している。

これを使って郵便番号→都道府県を辞書引きすれば、郵便番号から都道府県を調べることができるはずである。
しかし、このデータは約12.5万行ある。47の都道府県を調べるために12.5万行のルックアップテーブルを持つのはちょっと大げさな気がする。もし郵便番号を7桁見なくても都道府県を判定できるのなら、それがいいのでは?

そう思って、郵便番号を何桁見れば都道府県を特定できるのかを調べてみた。

結論

結論を書くと、郵便番号から都道府県を完全に特定することはできない。理由は、同一の郵便番号が府県が異なる2つの市町村に紐づけられている例が3つあるからである。

郵便番号 住所
4980000 三重県桑名郡木曽岬町(掲載がない場合)
愛知県弥富市(掲載がない場合)
6180000 京都府乙訓郡大山崎町(掲載がない場合)
大阪府三島郡島本町(掲載がない場合)
8710000 大分県中津市(掲載がない場合)
福岡県築上郡吉富町(掲載がない場合)
町域の「掲載がない場合」というのは、「その他」に近い意味で、他の町域には郵便番号が割り当てられているが、それに該当しないところである、ということを指すようだ。数は少ないだろうとは思うが、正確性が必要な場合は無視できない。

他にも複数の市町村に同一の郵便番号が割り当てられている例があるが、都道府県が異なるのはこの3つのみである。

府県が異なる2つの市町村に結び付いた郵便番号を除いたとしても、7桁目だけが異なって都道府県が変わる例が多数あるため、結局7桁全部みないと都道府県判定はできない。

とはいえ

郵便番号の最初の2桁目までで大体の都道府県が特定できるらしい。近い番号と都道府県が異なっているものを例外として除外すると傾向が見えてくる。

郵便番号データは2020年11月30日更新のものを利用した。

00: 北海道
01: 秋田県
	「018550 ⻘森県」を除く
02: 岩手県
03: 青森県
04-09: 北海道
10-20: 東京都
21-25: 神奈川県
26-29: 千葉県
30-31: 茨城県
	「311441 栃⽊県」を除く
32: 栃木県
33-36: 埼玉県
	「349122 栃⽊県」を除く
37: 群馬県
	「3701507 埼⽟県」を除く
38-39: 長野県
	「3840097 群⾺県」を除く
	「389012 群⾺県」を除く
	「389226 新潟県」を除く
40: 山梨県
41-43: 静岡県
	「431412 愛知県」を除く
44-49: 愛知県
	「4980000 三重県または愛知県」を除く
	「49808 三重県」を除く
50: 岐阜県
51: 三重県
52: 滋賀県
	「520046 京都府」を除く
53-59: 大阪府
	「56308 兵庫県」を除く
60-62: 京都府
	「6180000 京都府または⼤阪府」を除く
	「6180001〜6180004 ⼤阪府」を除く
	「618001〜618002 ⼤阪府」を除く
63: 奈良県
	「630027 ⼤阪府」を除く
64: 和歌山県
	「647127 奈良県」を除く
	「64713 三重県」を除く
	「64715 奈良県」を除く
	「64803 奈良県」を除く
65-67: 兵庫県
68: 鳥取県
	「68401〜68404 島根県」を除く
	「685 島根県」を除く
69: 島根県
70-71: 岡山県
72-73: 広島県
74-75: 山口県
76: 香川県
77: 徳島県
78: 高知県
79: 愛媛県
80-83: 福岡県
	「8115 ⻑崎県」を除く
	「817 ⻑崎県」を除く
	「839142 大分県」を除く
84: 佐賀県
	「84804 長崎県」を除く
85: 長崎県
86: 熊本県
87: 大分県
	「8710000 ⼤分県または福岡県」を除く
	「871022 福岡県」を除く
	「87108〜87109 福岡県」を除く
88: 宮崎県
89: 鹿児島県
90: 沖縄県
91: 福井県
92: 石川県
	「9220679 福井県」を除く
93: 富山県
	「939017 ⽯川県」を除く
94-95: 新潟県
	「949832 ⻑野県」を除く
96-97: 福島県
98: 宮城県
99: 山形県

都道府県が異なる例外として除外した番号を調べると、郵便番号の最初の2桁の示す都道府県に隣り合う市町村であることがわかる(全て調べたわけではないので例外はあるかもしれない)。配送上の都合だったりするのだろうか?

という訳で、配送料には同一都道府県内の料金が設定されているため、都道府県が特定できないと送料を決定することができない。そのため、送料の判定に郵便番号を使うことは基本的にできない*2。目論見が外れた結果となった。残念。

コード

使ったPython 3コードを置いておく。日本郵便からダウンロードした全国一括の郵便番号データKEN_ALL.CSVと同じディレクトリに入れて使う。

ここまで読めば都道府県が一意に定まるよ、というのを調べるためのもの

#coding: utf-8
import csv, codecs

CSVFILENAME = "KEN_ALL.CSV"

#0 全国地方公共団体コード(JIS X0401、X0402)……… 半角数字
#1 (旧)郵便番号(5桁)……………………………………… 半角数字
#2 郵便番号(7桁)……………………………………… 半角数字
#3 都道府県名 ………… 半角カタカナ(コード順に掲載) (注1)
#4 市区町村名 ………… 半角カタカナ(コード順に掲載) (注1)
#5 町域名 ……………… 半角カタカナ(五十音順に掲載) (注1)
#6 都道府県名 ………… 漢字(コード順に掲載) (注1,2)
#7 市区町村名 ………… 漢字(コード順に掲載) (注1,2)
#8 町域名 ……………… 漢字(五十音順に掲載) (注1,2)
#9 一町域が二以上の郵便番号で表される場合の表示 (注3) (「1」は該当、「0」は該当せず)
#10 小字毎に番地が起番されている町域の表示 (注4) (「1」は該当、「0」は該当せず)
#11 丁目を有する町域の場合の表示 (「1」は該当、「0」は該当せず)
#12 一つの郵便番号で二以上の町域を表す場合の表示 (注5) (「1」は該当、「0」は該当せず)
#13 更新の表示(注6)(「0」は変更なし、「1」は変更あり、「2」廃止(廃止データのみ使用))
#14 変更理由 (「0」は変更なし、「1」市政・区政・町政・分区・政令指定都市施行、「2」住居表示の実施、「3」区画整理、「4」郵便区調整等、「5」訂正、「6」廃止(廃止データのみ使用))

postal2pref = dict()
with codecs.open(CSVFILENAME, 'r', encoding="shift_jis") as csvfile:
    reader = csv.reader(csvfile)
    for row in reader:
        postalcode = row[2]
        prefecture = row[6]
        if postalcode in postal2pref:
            postal2pref[postalcode].add(prefecture)
        else:
            postal2pref[postalcode] = set([prefecture])

# headで始まる郵便番号の示す都道府県の種類の数を数える
def check_pref_startswith_rec(head):
    if len(head) > 7: return #8文字以上の郵便番号はありえない
    
    keys_startswith_head = [key for key in postal2pref.keys() if key.startswith(head)]
    values_startswith_head = set(map(lambda k: ", ".join(postal2pref.get(k)), keys_startswith_head))
    #print(len(values_startswith_head))
    if len(values_startswith_head) == 0: #郵便番号が存在しない場合
        return
    elif len(values_startswith_head) == 1: #都道府県が一つに定まった場合
        print("{}: {}".format(head, "".join(values_startswith_head))) #出力
        return
    else: #都道府県が一つに定まらない
        for n in range(0, 10):
            check_pref_startswith_rec(head + str(n))
            
if __name__ == '__main__':
    check_pref_startswith_rec('')

例外の番号を除外して同様のことをするもの

#coding: utf-8
import csv, codecs

CSVFILENAME = "KEN_ALL.CSV"

#2 郵便番号(7桁)……………………………………… 半角数字
#6 都道府県名 ………… 漢字(コード順に掲載) (注1,2)

startswith_exclude_list = ["018550", "311441", "349122", "3701507", "3840097", "389012", "389226",
                           "431412", "4980000", "49808", "520046", "56308", "6180000", 
                           "6180001", "6180002", "6180003", "6180004", "618001", "618002",
                           "630027", "647127", "64713", "64715", "64803", "68401", "68402", "68403", 
                           "68404", "685", "8115", "817", "839142", "84804", "8710000", "871022", 
                           "87108", "87109", "9220679", "939017", "949832"]
def check_exclude(postalcode):
    excludeflag = list(map(postalcode.startswith, startswith_exclude_list)).count(True)
    if excludeflag > 0:
        return True
    else:
        return False

postal2pref = dict()
with codecs.open(CSVFILENAME, 'r', encoding="shift_jis") as csvfile:
    reader = csv.reader(csvfile)
    for row in reader:
        postalcode = row[2]
        prefecture = row[6]
        if check_exclude(postalcode):
            continue
        if postalcode in postal2pref:
            postal2pref[postalcode].add(prefecture)
        else:
            postal2pref[postalcode] = set([prefecture])

# headで始まる郵便番号の示す都道府県の種類の数を数える
def check_pref_startswith_rec(head):
    if len(head) > 7: return #8文字以上の郵便番号はありえない
    
    keys_startswith_head = [key for key in postal2pref.keys() if key.startswith(head)]
    values_startswith_head = set(map(lambda k: ", ".join(postal2pref.get(k)), keys_startswith_head))
    #print(len(values_startswith_head))
    if len(values_startswith_head) == 0: #郵便番号が存在しない場合
        return
    elif len(values_startswith_head) == 1: #都道府県が一つに定まった場合
        print("{}: {}".format(head, "".join(values_startswith_head))) #出力
        return
    else: #都道府県が一つに定まらない
        for n in range(0, 10):
            check_pref_startswith_rec(head + str(n))
            
if __name__ == '__main__':
    check_pref_startswith_rec('')

*1:ヤマト、ゆうパックは離島料金がかからないので都道府県が分かれば配送料が計算できる。佐川は離島は別料金。

*2:東京は郵便番号上2桁のみで完全に特定できるので、東京発の荷物の送料は送り先郵便番号上2桁のみで決定できる。北海道等も同様。