Scalaで制作した、遺伝的アルゴリズムによる日本語かな配列生成プログラム

# Scalaで制作した、遺伝的アルゴリズムによる日本語かな配列生成プログラム

[https://github.com/Harsiharsi/scala-ga-layout](https://github.com/Harsiharsi/scala-ga-layout)

日本語ngramデータとあらかじめ作成した運指データにより、最適な運指性の日本語配列を探索する遺伝的アルゴリズムを開発しました。

[各種配列を評価したデータ。](https://github.com/Harsiharsi/scala-ga-layout/tree/main/src/main/resources/layout_analyzed)

以下は本プログラムで生成された配列、コンポジション#3です。ngramデータはmentaiko氏の4gramデータを使用しました。  
[月見草開発に用いた文章サンプル](https://w.atwiki.jp/keylay/pages/16.html)


```
みねたゅつ  ・゜にちな
さ Sかあょ  ゃえ゛てら
しりるくい  うんまはす
ともほれそ  っのをおせ
----------------------
・・ぬゆ・  ・゜・・ひ
・ S・ぁよ  やぇ゛こふ
き・けむぃ  ぅろへ。・
、・・ー・  ・め・ぉわ
```

::: note alert
この配列は負担の多い運指が多いと感じたため、評価関数を修正し、配列を生成し直しました。  
この配列を使うことは推奨できませんので、興味を持った方は以下の記事の配列をご検討ください。
:::
https://qiita.com/harsi/items/5937c1520a79af6ee639


本配列における評価方針は、第一に速く打鍵できること、第二にそのさいに負担をなるべく減らすことです。速い打鍵の定義とは、一度打鍵した指を離さない、あるいはなるべくそのキーから指が離れないようにしつつ、別の指で打鍵できることです。負担が少ないということは、一度打鍵した指を離さないようにするときに問題になります。なお、本配列に使用した評価関数の本分は、複数キーからなる打鍵列のキー相互の関係性を適切に評価するというものであって、速度の評価に特化したものではありません。

文字配置の方針は、覚えやすさと精神的負担の少なさを優先してあります。配列の形式は、`6`キーを除いた39キーの範囲にかな文字、濁点キー、半濁点キー、シフトキーを配置し、そこに収まらない文字はシフト操作によって入力します。シフト操作は、ある文字を入力したあとにシフトキーを押す後置シフト方式で、ある文字にシフト文字が割り当てられていれば、それを入力します。小文字になる文字は必ず大文字と同じキーに配置されるようにしてあり、あ行は表面で大文字、シフト面に小文字を割り当て、や行は表面に小文字を配置してあります。39キーという広い範囲を使っているのは、シフトキーの数を一つに収め、打鍵の際にどのシフトキーを使うべきかという迷いをなくすことを目的にしています。また、後置シフトも打鍵の迷いを減らす効果があります。

以下のローマ字定義ファイルをGoogle IMEに読み込ませることで実際に使うことができます。
```
1	み
2	ね
3		た
たi	だ
たw	ぬ
4		ゅ
ゅw	ゆ
5		つ
つi	づ
7	゜
8	に
9		ち
ちi	ぢ
0		な
なw		ひ
ひi	び
ひ7	ぴ
q		さ
さi	ざ
e		か
かi	が
r		あ
あw	ぁ
t		ょ
ょw	よ
y		ゃ
ゃw	や
u		え
えw	ぇ
i	゛
o		て
てi	で
てw		こ
こi	ご
p		ら
らw		ふ
ふi	ぶ
ふ7	ぷ
a		し
しi	じ
しw		き
きi	ぎ
s	り
d		る
るw		け
けi	げ
f		く
くi	ぐ
くw	む
g		い
いw	ぃ
h		う
うw	ぅ
うi	ゔ
j		ん
んw	ろ
k		ま
まw		へ
へi	べ
へ7	ぺ
l		は
はi	ば
は7	ぱ
はw	。
;		す
すi	ず
z		と
とi	ど
とw	、
x	も
c		ほ
ほi	ぼ
ほ7	ぽ
v		れ
れw	ー
b		そ
そi	ぞ
n	っ
m		の
のw	め
,	を
.		お
おw	ぉ
/		せ
せi	ぜ
せw	わ

[	「
]	」
'	・
```


## 評価関数

配列を評価するにあたって、かな文字のngramを打鍵列に変換し、キーをアクションという単位に分割し、アクションの少なさ(一つのアクションに含まれるキーの多さ)を打鍵の速さの指標とします。アクションはチャンクという単位からなり、打鍵列を評価するには、全体の打鍵列をチャンク列に変換し、それをアクション列に変換するという手順を踏みます。チャンクとは、片手のキーのみを含み、最低一つのキーからなり、簡単にひとまとめにして打てるような打鍵列で、いわゆるアルペジオに近い概念です。ただし、打鍵困難と設定した指の順番を除いて、打鍵順は考慮しません。どのような打鍵列がチャンクに分類されるかは後述します。アクションとは、左右のチャンクを含み、全体のキーがひとまとめに中断されることなく打てる打鍵列です。

### 打鍵列からアクション列への変換

#### 打鍵列からチャンク列への変換

ngramから変換された打鍵列は、まず手の変わり目によって区切られたリストにします。

例
```
sdlkfj -> sd lk f j
```

そして、個々の打鍵列をチャンクという単位に分割し、最終的にチャンクのリストにします。

チャンクの例
```
asdf -> asdf (打鍵列全体が一つのチャンク)
```
```
de -> d e (同じ指を連続して使う同指打鍵であるため、1チャンク1キーの二つのチャンクになる)
```

#### チャンク列からアクション列への変換

変換されたチャンク列を先頭から新しいリストに入れていき、左右のそれぞれの手にわけて打鍵列を足し合わせていきます。

例
```
アクション左手総打鍵列:
アクション右手総打鍵列:
 <- df j s k

アクション左手総打鍵列: df
アクション右手総打鍵列:
df <- j s k

アクション左手総打鍵列: df
アクション右手総打鍵列: j
df j <- s k

アクション左手総打鍵列: dfs
アクション右手総打鍵列: j
df j s <- k

アクション左手総打鍵列: dfs
アクション右手総打鍵列: jk
df j s k <-
```

新しく入るチャンクの打鍵列が、同じ手の総打鍵列と足し合わせたときにチャンクとして成立すればアクションに入れることができ、そうでなければ新しいアクションを始めます。アクションとは、左右の両方が協調して、悪運指に中断されることなく一気に打鍵できるキーをまとめるものなので、どちらかの手で悪運指が発生し新しいアクションが始まれば、もう片方の手で次に打鍵するチャンクがその手の前のチャンクに対して悪運指か良運指かは関係なくなります。


### 打鍵列のチャンク判定

打鍵列がチャンクかどうかを判定するには、良運指が満たすべき三種類の性質をすべて満たしているかどうかを調べます。

それにあたって、あらかじめ1500項目ほど定義しておくものがありますが、これによってどのような打鍵列が与えられても、良運指かどうかを判定することができます。片手を使って打てるあらゆる打鍵列を評価しようとして、その分だけ打鍵パターンを生成し評価しようとすれば、20^4\*2種類の打鍵列を直接確かめなければなりません。しかし、打鍵列を三つの性質に抽象化することで、少ないパターンだけを見るだけでありうるすべての打鍵列を適切に評価できるようになります。

#### 基本打鍵列

まずあらかじめ、片手の1打鍵から2打鍵までのありうるすべてのパターンに対して、すべてのキーを快適に同時に押せるかどうかを定義します。これは基本打鍵列と言います。同指打鍵や、異なる指を使う打鍵でもキーが遠い場合や、手に負担のかかる形を強いる場合は、同時に押せないと判定します。なお、fjのような、左右の手のキーが混ざったものは含まないことに注意してください。

[定義ファイル抜粋](https://github.com/Harsiharsi/scala-ga-layout/blob/main/src/main/resources/typingdatas/stroke_patterns.txt)  
``` stroke_patterns.txt
e	BasicStrokeEasilyChunkable	0
ee	BasicStrokeSameFinger	0
er	BasicStrokeEasilyChunkable	0
re	BasicStrokeEasilyChunkable	0
et	BasicStrokeEasilyChunkable	0
te	BasicStrokeEasilyChunkable	0
ea	BasicStrokeEasilyChunkable	0
ae	BasicStrokeEasilyChunkable	0
es	BasicStrokeEasilyChunkable	0
se	BasicStrokeEasilyChunkable	0
ed	BasicStrokeSameFinger	1
de	BasicStrokeSameFinger	1
ef	BasicStrokeEasilyChunkable	0
fe	BasicStrokeEasilyChunkable	0
eg	BasicStrokeEasilyChunkable	0
ge	BasicStrokeEasilyChunkable	0
ez	BasicStrokeEasilyChunkable	0
ze	BasicStrokeEasilyChunkable	0
ex	BasicStrokeDifferentFinger	1
xe	BasicStrokeDifferentFinger	1
ec	BasicStrokeSameFinger	2
ce	BasicStrokeSameFinger	2
ev	BasicStrokeDifferentFinger	0
ve	BasicStrokeDifferentFinger	0
eb	BasicStrokeDifferentFinger	1
be	BasicStrokeDifferentFinger	1
```

なお、行末の数字は悪運指の場合の打鍵距離です。良運指の場合は打鍵距離は0になります。同指打鍵の場合は単純に列の距離で、異指打鍵の場合、その打鍵列に近いチャンク打鍵列からどれだけ離れているかで決まります。(`eb`の場合、`b`キーが、`eg`を打つ場合の`g`キーから一つ離れているため、距離は1としています。ただし、異指打鍵悪運指でも距離が0のものがありますが、これは良運指と悪運指の中間としています。)

そして、打鍵列から、二打鍵列からなる直積集合を作り、その中のすべての要素が同時押しできるものとして定義されており、同指打鍵などが含まれていないかどうかを見ます。

例
```
sdf -> sd sf df // 良運指
sdfe -> sd sf se df de fe // deが同指打鍵であるため良運指ではない
```

#### 手の形

「手の形」とは、打鍵列を打つそれぞれの指が、打鍵に使われる隣のほかの指に対して相対的にどの高さにあるかを示すものです。あらかじめ、ありうる手の形をすべて定義しておきます。

手の形の表記は、指の名前を記号にし、小指を左にして使用される指を揃え、ある指が左隣の指に対する高さを表記します。手の形の表記の指の順番と実際の打鍵列の順番は無関係であることに注意してください。指の順番まで考慮して手の形のパターンの網羅すれば、膨大な数にのぼるためです。

[定義ファイル抜粋](https://github.com/Harsiharsi/scala-ga-layout/blob/main/src/main/resources/typingdatas/hand_shapes.txt)  
``` hand_shapes.txt
Ps-Rs	HandShapeEasilyChunkable
Ps-Rs-Ms	HandShapeEasilyChunkable
Ps-Rs-Ms-Is	HandShapeEasilyChunkable
Ps-Rs-Ms-Il	HandShapeEasilyChunkable
Ps-Rs-Ms-Iu	HandShapeEasilyChunkable
Ps-Rs-Ms-SIs	HandShapeEasilyChunkable
Ps-Rs-Ms-SIl	HandShapeEasilyChunkable
Ps-Rs-Ms-SIu	HandShapeHardChunkable
Ps-Rs-Ml	HandShapeEasilyChunkable
Ps-Rs-Ml-Is	HandShapeEasilyChunkable
Ps-Rs-Ml-Il	HandShapeEasilyChunkable
Ps-Rs-Ml-Iu	HandShapeHardChunkable
Ps-Rs-Ml-SIs	HandShapeHardChunkable
Ps-Rs-Ml-SIl	HandShapeHardChunkable
Ps-Rs-Ml-SIu	HandShapeHardChunkable
Ps-Rs-Mu	HandShapeEasilyChunkable
Ps-Rs-Mu-Is	HandShapeEasilyChunkable
Ps-Rs-Mu-Il	HandShapeEasilyChunkable
Ps-Rs-Mu-Iu	HandShapeHardChunkable
Ps-Rs-Mu-SIs	HandShapeEasilyChunkable
Ps-Rs-Mu-SIl	HandShapeEasilyChunkable
Ps-Rs-Mu-SIu	HandShapeHardChunkable
Ps-Rs-Is	HandShapeEasilyChunkable
Ps-Rs-Il	HandShapeEasilyChunkable
Ps-Rs-Iu	HandShapeEasilyChunkable
Ps-Rs-SIs	HandShapeEasilyChunkable
Ps-Rs-SIl	HandShapeEasilyChunkable
Ps-Rs-SIu	HandShapeEasilyChunkable
```

パターンに対応する具体的な打鍵列
```
Ps // 該当打鍵列 a
Ps-Rs-Ms-Is // 該当打鍵列 asdf fdsa dfsa zxcv
Rs-Mu-Il // 該当打鍵列 sef w3f zef
Ps-Ml-SIu // 該当打鍵列 acg
```

大文字は指を示しており、小文字は高さ(same、upper、lower)を示します。

それぞれの手の形に対し、同時押しができるかどうかを定義します。基本的に、直線か山なりになっているものが快適に打鍵でき、谷型になっているものは打鍵できないとしています。

#### 指の順番

これは、すでに押された指の次に、まだ押されていない指が一つ使われるとき、同時に押せるかどうかを規定するものです。

[定義ファイル抜粋](https://github.com/Harsiharsi/scala-ga-layout/blob/main/src/main/resources/typingdatas/finger_orders.txt)  
``` finger_orders.txt
P	R	FingerOrderEasilyChunkable
P	M	FingerOrderEasilyChunkable
P	I	FingerOrderEasilyChunkable
P	SI	FingerOrderEasilyChunkable
P-R	M	FingerOrderEasilyChunkable
P-R	I	FingerOrderEasilyChunkable
P-R	SI	FingerOrderEasilyChunkable
P-M	R	FingerOrderHardChunkable
P-M	I	FingerOrderEasilyChunkable
P-M	SI	FingerOrderEasilyChunkable
P-I	R	FingerOrderEasilyChunkable
P-I	M	FingerOrderEasilyChunkable
P-SI	R	FingerOrderEasilyChunkable
P-SI	M	FingerOrderEasilyChunkable
P-R-M	I	FingerOrderEasilyChunkable
P-R-M	SI	FingerOrderEasilyChunkable
P-R-I	M	FingerOrderEasilyChunkable
P-R-SI	M	FingerOrderEasilyChunkable
P-M-I	R	FingerOrderHardChunkable
P-M-SI	R	FingerOrderEasilyChunkable
```

すでに押された指に関しては順番を考慮しません。たとえば`P-R-M I`は小指、薬指、中指が押されている状態で人差し指を押すときという意味ですが、小指、薬指、中指がどの順番で押されたかは関係ありません。

この性質は、特に折り返しという種類の悪運指を見つけるためにあります。折り返しとは、小指と中指で打鍵したあとに薬指を使うというような、指の進行方向が反転する打鍵のことです。

打鍵列をこの方法で検査する場合、以下のような手順を踏みます。
```
dfas // 与えられた打鍵列
M I R P // 指の記号に変換する
- M <- I R P // 何も押されていない状態で中指を打鍵する場合の評価
M I <- R P
R-M I <- P
R-M-I P <-
```

#### 打鍵性質の項目のクラス

三種類の打鍵性質が持つそれぞれの定義項目は、実数で細かく序列づけられているわけではなく、大まかなクラスを与えられることで評価されます。

それぞれの打鍵性質のそれぞれの項目は、以下のように分類されます。
- 基本打鍵列
  - BasicStrokeEasilyChunkable
  - BasicStrokeSameFinger
  - BasicStrokeDifferentFinger
- 手の形
  - HandShapeEasilyChunkable
  - HandShapeHardChunkable
- 指の順番
  - FingerOrderEasilyChunkable
  - FingerOrderHardChunkable

そして、打鍵列がすべての性質においてEasilyChunkableのクラスを持っている場合に、チャンクと判定されます。

基本的に、打鍵列は良運指でなければ悪運指と判定され、個々の良運指はどれだけいいのか、悪運指はどれだけ悪いのかといったアナログな評価はしていません。したがって、「一応同時押しできるがいくらか負担がある」「簡単に同時押しできるが弱い指を使う」といった打鍵列にもペナルティはありません。なぜそうしているのかというと、手にかかる負担のほとんどは同時押しできない打鍵列を無理に押そうとするときに発生し、同時押しできないものをはじけば差し支えない、と考えているからです。また、打鍵速度を計測して評価数値とするという方法も取っていません。そのような方法を使う場合、弱い指を使った打鍵は遅くなることが期待され、強い指で高い頻度の文字列を打つ配列が高く評価されることが期待されるわけですが、実際に打鍵を計測してみると、弱い指を使うアルペジオでもあまり数値に大きな差はつきません。また、計測のたびに時間がばらつき、それをどう補正するべきかという問題が出てきます。そうであればむしろ、弱い指を使いたくない場合には明示的に恣意的に評価する方がいいでしょう。

打鍵を序列づけないのはあくまで実験的な試みで、この方針を貫徹し続けるかどうかはわかりませんが、数値によって運指を細かく評価することにあまり有効性がない、とは十分言えるように思います。なぜなら、特定の運指だけを高く評価したところで、それがいくらでも多く出現するようになるわけではないからです。

### 評価関数総論

本評価関数の特徴を総括すると、複数キー相互の適切な関係性を評価することを目的とし、最適なキー数の範囲でそれを評価する、というものです。アクションという概念はそのキー範囲を示すものです。本配列生成時にはアクション数を減らすことを優先しましたが、アクション数ではなくアクションの中でどのような運指が現れるかや、隣り合ったアクションの運指性を積極的に評価するという方針も考えられます。また、打鍵性質項目に割り当てるクラスを変えたり、新しい性質クラスを作ることで自由に、本配列から連想しにくいような評価基準を作ることもできるでしょう。


## 従来の評価方法との違い

この評価の方法が取っている根本的な前提は、打鍵速度や疲労は、片手で複数のキーを打つ際の手の形、あるいは指同士の関係性に大きく依存しており、それ以外の指標は副次的なものにしかなりえない、というものです。

比較的広く行き渡っている、配列の評価方法はこのようなものでしょう。
1. 運指距離の短さ
2. 交互打鍵率の高さ
3. 特定のキーの使用頻度
4. 特定の指の使用頻度

これらに概ね共通して言えるのは、複数のキーをひとまとめにして押すことや、指同士の関係性を重視しておらず、片手の一本の指で一つのキーを押すことしか考えていないということです。これらが実際に効用のある評価方法であるとすれば、遅く打つ場合のみだと思われます。たとえ速度を重視しない人であっても、タイピングに慣れていけば速く打てる文字列が増えます。すると、たとえ少数であってもその慣れた複数キーの文字列が負担のかかる手の形や打鍵順である可能性を考慮しなければなりません。そして、この問題を上述の評価方法で取り除くことは難しいでしょう。

### 運指距離

速く、かつ無意識に打鍵できるようになった場合の手の動かし方というのは、頭の中に複数のキーを思い浮かべ、それらを複数の指でまとめて打つために指と手の形を決め、腕と手の平の位置を調整して一度に手を下ろす、というものであり、チャンクという単位で打鍵を考えています。あるキーから遠いキーを打つ場合は打鍵に関わるあらゆる個々の関節の変化量は大きくなりますが、変化量が大きければそれだけ素早く関節を動かせばいいだけであり、手の動き自体は時間に大きな影響は及ぼさないように思えます。

また、運指距離が長くなるほど疲れが大きいと言われますが、運指距離というのはあるキーを打つ指が別のキーの真上に移るまでの距離ということであり、その移動の間、指には空を切る以上の負担がかかるはずはないのだから、指の疲労の原因にはなりえないでしょう。ただし、指を動かすことは負担にならなくても、重い腕を頻繁に動かすことは疲労になります。距離の長い打鍵にも、指と手の平の動きだけで済むもの(`c3`など)と、腕の動きを必要とするもの(`y`キーなど)があります。腕を動かす必要のないものは、あまり考慮しなくてもいいかもしれません。それから、関節の変化量が大きくなればそれだけミスしやすくなり、結果的に打鍵が遅くなるとは言えるかもしれません。またそれだけ正確に関節を動かさなければならないため、精神的負担も増えるでしょう。

実感として、運指距離は評価事項としてはそれほど重く見るべきものではないように思えます。チャンクに含まれる打鍵数を多く(アルペジオを多く)すれば自然と運指距離は少なくなるため、チャンク性能(運指性)をよくするだけで十分に感じます。

### 交互打鍵率

交互打鍵がいいとされるのは、片手で連続して打鍵するさいの悪運指を避けるためであり、一方の手で打鍵し、次に片手で打鍵し、その次に最初の手で打鍵すれば、最初の手の形を変える余裕が生まれるために負担がかからない、という発想でしょう。しかしこれは、どんな悪運指でも遅く打てば負担にならないと言っているのと同じであり、根本的な解決にはなっていません。打鍵が速くなれば交互打鍵が挟まっても手の形を変える余裕はなくなり、たとえ交互打鍵率が100パーセントになっても同手打鍵を続けているのと同じになります。やるべきことは片手で連続して打鍵する場合でも交互打鍵の場合でも悪運指を避けるように文字を配置することであって、交互打鍵率という指標は運指性を評価するのに不十分に思えます。

### キーの使用頻度と指の使用頻度

これらはキー同士の関係性を考慮していないので十分に有効な指標ではないと言えます。

キーの使用頻度という指標が評価しようとしているのは運指距離と、手の姿勢の快適さだと思われますが、これらは複数キーの打鍵列を押すときの手の姿勢変化を見るほうがはるかに有効でしょう。

指の使用頻度は主に疲労の尺度になりますが、弱い指の疲労はチャンク打鍵時の手の姿勢にも大きく影響されるため頻度を見るだけでは不十分でしょう。


## タイピングゲーム用AHKスクリプト

[mentaiko氏による月見草配列AHKスクリプト](http://mentaiko.syoyu.net/未選択/ahk版alpha)を改造したものです。

``` compositionno3_script.ahk
#Persistent
#SingleInstance, Force
#NoEnv
#UseHook
#InstallKeybdHook
#InstallMouseHook
#HotkeyInterval, 2000
#MaxHotkeysPerInterval, 200
Critical			;基本的に割り込みは避ける
Process, Priority,, Realtime
SendMode, Input
SetWorkingDir %A_ScriptDir%
SetTitleMatchMode, 2
g_escape_char := {sc01A: "@", sc027: "`;", sc028: "`:", sc033: "`,", sc034: ".", sc035: "/", Q: "Q", W: "W", E: "E", R: "R", T: "T", Y: "Y", U: "U", I: "I", O: "O", P: "P", A: "A", S: "S", D: "D", F: "F", G: "G", H: "H", J: "J", K: "K", L: "L", Z: "Z", X: "X", C: "C", V: "V", B: "B", N: "N", M: "M", 1: "1", 2: "2", 3: "3", 4: "4", 5: "5", 6: "6", 7: "7", 8: "8", 9: "9", sc00B: "-"}
g_keys2sym :=[]
g_keys2sym[1] :={}
g_keys2sym[2] :={}
g_keys2sym[3] :={}
g_mikakutei :=""
Loop, Read, compositionno3_kana.txt
{
	global g_keys2sym
	Array := StrSplit(A_LoopReadLine, "`t")
	keys := Array[1]
	StringUpper, keys, keys
	g_keys2sym[StrLen(keys)][keys] :=Array[2]
}

fn_send(keys){
	global g_key2sym
	k :=""
	While (keys){
		l := StrLen(keys)
		If(l>3)
			l := 3
		While (l){
			StringRight, t, keys, l
			s := g_keys2sym[l][t]
			if(s)
				break
			l--
		}
		If(!l){
			s := t
			l := 1
		}
		k := s . k
		StringTrimRight, keys, keys, l
	}
	send, %k%
	Return
}
fn_noJP(){
	global g_mikakutei
	global g_windowskey
	If !(GetKeyState(Crtl,"P")||GetKeyState(Alt,"P")||GetKeyState(Shift,"P")||GetKeyState(RWin,"P"))
		Return 0
	fn_send(g_mikakutei)
	g_mikakutei := ""
	send, {Blind}{%A_ThisHotkey%}
	Return 1
}

Return

;シフト非対象キー
1::
2::
5::
6::
7::
8::
9::
Q::
E::
I::
S::
sc027::
X::
C::
B::
N::
sc033::

;シフト対象キー
3::
4::
sc00B::
R::
T::
Y::
U::
O::
P::
sc01A::
A::
D::
F::
G::
H::
J::
K::
L::
Z::
V::
M::
sc034::
sc035::
sc028::
{
	If(fn_noJP())
		Return
	global g_mikakutei
	global g_escape_char
	fn_send(g_mikakutei)
	g_mikakutei := g_escape_char[A_ThisHotkey]
	Return
}

;後置シフトキー
W::
{
	If(fn_noJP())
		Return
	global g_mikakutei
	global g_escape_char
	StringRight, t, g_mikakutei, 1
	If(t!=A_ThisHotkey)
		g_mikakutei .= A_ThisHotkey
	else{
		fn_send(g_mikakutei)
		g_mikakutei := A_ThisHotkey
	}
	Return
}

sc00C::
sc00D::
sc01B::
sc02A::
sc02B::
sc073::
Space::
Tab::
Enter::
BS::
Del::
Ins::
Left::
Right::
Up::
Down::
Home::
End::
PgUp::
PgDn::
sc029::
sc079::
sc07B::
sc070::
sc03A::
F1::
F2::
F3::
F4::
F5::
F6::
F7::
F8::
F9::
F10::
F11::
F12::
F13::
F14::
F15::
F16::
F17::
F18::
F19::
F20::
F21::
F22::
F23::
F24::
Esc::
AppsKey::
PrintScreen::
Pause::
Break::
Sleep::
Help::
CtrlBreak::
CapsLock::
ScrollLock::
NumLock::
Ctrl::
;LCtrl::
;RCtrl::
Alt::
;LAlt::
;RAlt::
Shift::
;LShift::
;RShift::
Numpad0::
Numpad1::
Numpad2::
Numpad3::
Numpad4::
Numpad5::
Numpad6::
Numpad7::
Numpad8::
Numpad9::
NumpadDot::
NumpadDel::
NumpadIns::
NumpadClear::
NumpadUp::
NumpadDown::
NumpadLeft::
NumpadRight::
NumpadHome::
NumpadEnd::
NumpadPgUp::
NumpadPgDn::
NumpadDiv::
NumpadMult::
NumpadAdd::
NumpadSub::
NumpadEnter::
Browser_Back::
Browser_Forward::
Browser_Refresh::
Browser_Stop::
Browser_Search::
Browser_Favorites::
Browser_Home::
;Volume_Mute::
;Volume_Down::
;Volume_Up::
;Media_Next::
;Media_Prev::
;Media_Stop::
;Media_Play_Pause::
Launch_Mail::
Launch_Media::
Launch_App1::
Launch_App2::
;LButton::
;RButton::
;MButton::
;XButton1::
;XButton2::
;WheelDown::
;WheelUp::
;WheelLeft::
;WheelRight::
{
	fn_send(g_mikakutei)
	g_mikakutei :=""
	send, {Blind}{%A_ThisHotkey%}
	Return
}

LWin::
RWin::
{
	fn_send(g_mikakutei)
	g_mikakutei :=""
	send, {Blind}{RWin Down}
	Return
}
LWin Up::
RWin Up::
{ 
	send, {Blind}{RWin Up}
	Return
}

```

``` compositionno3_kana.txt
1	n
2	,
3	q
4	+8
5	z
7	[
8	i
9	a
-	u
q	x
e	t
r	3
t	+9
y	+7
u	5
i	`
o	w
p	o
a	d
s	l
d	.
f	h
g	e
h	4
j	y
k	j
l	f
;	r
z	s
x	m
c	-
v	=
b	c
n	+z
m	k
,	+{sc00B}
.	6
/	p
3w	1
4w	8
-w	v
rw	+3
tw	9
yw	7
uw	+5
ow	b
pw	2
aw	g
dw	;
fw	]
gw	+e
hw	+4
jw	{vkE2}
kw	'
lw	+.
zw	+,
vw	\
mw	/
.w	+6
/w	{sc00B}
```


## 参考文献

- [遺伝的アルゴリズムで求めた口語に適した中指シフトかな配列 ○配列](http://rage2050.g1.xrea.com/GeneKana/_ReadMe.html)  
  日本語配列において個々のキー関係を重視する評価方針を取った最初の計算配列。
- [最適化計算によるかな配列『月見草』](https://w.atwiki.jp/keylay/)  
  おそらく上述配列の評価方法を発展させて作られた配列。  
  本プログラムは1800万字4gramデータを借用。  
- [最適化をもっと頑張る - paraphrohn’s diary](https://paraphrohn.hateblo.jp/entry/2018/12/08/220808)  
  チャンクという用語や打鍵理論の原理はこの記事を全面的に参照。  
- [Google IMEで携帯入力 - 「次の入力」の秘密 - - tomoemonの日記](https://tomoemon.hateblo.jp/entry/20101024/p1)  
  Google IMEによって配列を実装する方法。