ロ−マ字入力方式を残したまま日本語入力を拡張する(かさね配列)

経緯と思想

これまでいろいろな日本語入力配列をちょこちょこ試す機会はあったものの、普及するかどうかわからないのに全く新しい配列をゼロから覚えて頑張るより、すでにそこそこ速く入力できるロ−マ字入力方式を素直に拡張していったほうがいいのではないかという結論に至った。そういう意味で AZIK と abj 配列は素晴らしい。ただし実際にはロ−マ字入力方式のままでは打てなくなるものがあるので、ロ−マ字入力方式は完全に残しつつ親指シフトで頻出表現のショ−トカットを実現したらよいのではないかということを考えている*1

かさね配列

現状ロ−マ字入力方式 + AZIK + abj 配列 のような感じなのだが、便宜上名前を「かさね配列」とでもつけておく。別にスタンダ−ドな仕様を目指すわけでもなく自分好みに頻繁にカスタマイズすれば良いと思う。ひとまずやまぶきの定義ファイルを下のほうにおいておく。
以下が基本的な配置ルール。母音を拡張したければ左親指シフトを、頻出文字を入力したければ右親指シフトを使用する。

  • 無シフト
    • 通常のロ−マ字入力を保持
    • 普段使わなさそうなキーは拗音入力などに拡張する*2
  • 親指シフト
    • 母音キ−を「母音 + 撥音」に割り当てる(母音 + ん)
    • 普段使わなさそうなキーを「二重母音」に割り当てる(ai, uu, ei, ou)
    • それ以外は無シフトの状態に対応させる
  • 親指シフト
    • 頻出文字を割り当て
無シフト

以下のキーのみ変更。

  • q → ky (きゃ行)
  • x → sy (しゃ行)
  • c → ty (ちゃ行)
  • v → hy (ひゃ行)
  • l → ry (りゃ行)
  • ; → ltu (っ)
親指シフト

以下のキーのみ無シフトのものから変更。

  • 母音 + 撥音 の拡張
    • a, i, u, e, o → axn, ixn, uxn, exn, oxn (あん, いん, うん, えん, おん)
  • 母音 + 母音 の拡張
    • q → ai (あい)
    • l → uu (うう)
    • w → ei (えい)
    • p → ou (おう)
  • / → xn (ん)

q, w, p などは AZIK にそろえてあるが、 'h' のみ使用頻度が高いので 'l' にしている。ちなみに左親指シフトにあまりたくさんの拡張を設けず母音の拡張のみにしているのは、母音キ−以外を含めてシフトしっぱなしを実現したほうがユーザの負担も少ないし入力も速そうだったから。左親指シフト時はこれらの割り当てキー(q, w, p, l)の挙動が無シフト時から変わるのだが、逆に言うと子音に限っては ky行, w行, p行, ry行 に出くわすまでは左親指シフトし続けることができる*3
あとは一応「ん」の独立キーを '/' に割り当て。

親指シフト
 QWERT Y UIOP@
  ASDFG H JKL+*
   ZXCVB N M<>?

 わをりれと よ ういおけら
  あすでるが は じきかっこ
   たてしなに の まもくん

上記のように頻出文字を割り当てていく。基本的に子音に対応するような頻出かな文字を割り当てているが、頻出度合いの都合上いくつか例外的な割り当てが存在する。

  • q → わ
  • e → り
  • f → る
  • z → た
  • x → て
  • c → し
  • v → な
  • b → に
  • p → け
  • @ → ら
  • l → か
  • : → こ
  • , → も
  • . → く

子音キー以外、たとえば以下は左親指シフトの時と同じ。

  • ; → っ
  • / → ん

やまぶき定義ファイル

; -*- coding: shift_jis -*-
[ローマ字シフト無し]
1,2,3,4,5,6,7,8,9,0,'ー',^,¥
ky,w,e,r,t,y,u,i,o,p,@,[
a,s,d,f,g,h,j,k,ry,ltu,:,]
z,sy,ty,hy,b,n,m,,,.,'・',¥

[ローマ字左親指シフト]
1,2,3,4,5,6,7,8,9,0,'ー',^,¥
ai,ei,exn,r,t,y,uxn,ixn,oxn,ou,@,「
axn,s,d,f,g,h,j,k,uu,ltu,:,]
z,sy,ty,hy,b,n,m,,.,'・',¥

[ローマ字右親指シフト]
1,2,3,4,5,6,7,8,9,0,'ー',^,¥
wa,wo,ri,re,to,yo,u,i,o,ke,ra,
a,su,de,ru,ga,ha,ji,ki,ka,ltu,ko,
ta,te,si,na,ni,no,ma,mo,ku,xn,¥

上記内容を kasane.yab としてメモ帳なんかで保存して、やまぶき(キーボード配列エミュレータ)から設定ファイルとして読んでください。

入力例

以下は日本語入力のお題、ローマ字入力方式、かさね配列の順で記載し、入力キーとそのキー数および割合を付記する。なお、親指シフトは人差し指〜小指とある程度独立に動かせる割合が高いと考えており、以下の比較ではこれを ON, OFF 時にそれぞれ0.5キー、0.25キーの入力とカウントする。ただしシフト OFF に関する入力コストは連続シフトからの OFF 時だけで、単発での入力時は ON のコストのみカウントする。

以下簡単に打てるもの - ローマ字入力の 5割近く〜7.5割ぐらいの入力量

  • りょうかい
    • ryoukai (6)
    • l#pkq (4.5)
  • です
    • desu (4)
    • $ds (2.5)
  • でした
    • desita (6)
    • $dcz (3.5)
  • します
    • simasu (6)
    • $cms (3.5)
  • しています
    • siteimasu (9)
    • $cxims (5.5)
  • することができます
    • surukotogadekimasu (18)
    • $sf:tgdkms (9.5)
  • 調整内容
    • tyouseinaiyou (13)
    • $cpswnqyp (8.5)
  • ウィンドウ
    • windou (6)
    • w#idp (3.5)
  • 機械学習
    • kikaigakushuu (13)
    • $klig.$#xl (8.25)
  • 簡単に打てるもの
    • kantannniuterumono (18)
    • #kata#$buxf,n (11.25)

以下あまり拡張効果がないもの*4 - ローマ字入力の 7.5割〜の入力量

  • ローマ字入力方式
    • ro-majinyuuryokuhousiki (23)
    • ro-$mj$ny#l#lo$.$h#p#$ck (17.25)
      • ro-$mjny#llo$.h#p$ck
      • (シフト OFF 表記を省略した場合)
  • プログラミング
    • puroguramingu (13)
    • purogu$@$m#i#gu (12)


大事なのは、無理に拡張を利用する必要はなく、これまでどおりローマ字でも入力ができるということ。

その他

難しいものは辞書に登録する。無理に入力側だけで頑張ろうとせず、適材適所を意識する。

  • よろ
    • よろしくお願いいたします。
  • にt
    • について

所感

今後は子音だけ入力していけば当てはまる日本語表現をバシバシ変換してくれる変換エンジンが実用レベルになると思うのだが、そうなると全く新しい日本語入力配列を考える必要もあまりない気がする*5。でもそれができるにはまだ時間がかかりそうなので、やっぱり一番慣れている入力方法をちょっとずつ拡張できればいいんじゃないかな−というのが今の心境。
※このペ−ジは頻繁に更新・追記するかもしれないのであしからず。

追記

  • 2013/06/22 表現を一部修正、参考サイトを追加
  • 2013/06/25 表現を一部修正、配列の定義を更新、節の構成を変更、入力例を追加

*1:意外とそういう共存方式のものがあまりないような気がしている

*2:人によっては使っていたというキーがあると思うが、その場合ローマ字入力方式との共存という本来のテーマを貫く意味で拡張しなくて良い

*3:ky, ry は k + y, r + y に分離させてもよい

*4:特に外来語には弱い?あまり拡張効果がないものは素直にローマ字入力したほうがよい気がする

*5:QWERTY配列でいいじゃん、ということ

点数付けの導入でタスク管理を楽しくする[Emacs拡張]

まず趣旨!この記事はタスク管理を楽しく行なって作業能率をあげようっていう内容です。
あえてEmacsを知らない人も想定して書いていますが、この記事はEmacs Advent Calendar jp: 2011の16日目になります。15日目は[twitter:@g177564]さんのmultiple-value-blog1: Emacs Lispで式単位のコメントアウトでした。

はじめに

僕は先送り癖がひどく、テスト勉強やレポート、論文なども締切りギリギリまで行動できないタイプ。対策本を読んだ所、計画をしっかり立ててやることを明確にすると、初めの一歩を踏み出す先がわかって行動できると書いてあり、それはそうだと思う。だが、言うは易しというところで、自分の場合はあまり効果的には習慣付けられなかった。
しかし、このタスク管理をゲーム感覚で楽しめたらどうだろう?
以下のちょっとしたカスタマイズを導入した所、毎日計画を立てたり見直しをしたりするようになり、その結果として先送り癖がわりと軽減された気がする(あくまで主観)ので記事にまとめる。

本題

TODO管理を行うソフトは数多く存在する。
例えばテキストエディタであるEmacsのorg-modeにはTODO管理の機能が備わっており、タスク管理にも使うことができる(org-modeでTODO管理 - handlename's blog)。org-modeにおけるTODO管理は、タスクの終了度合いとして[X%完了][全XX個中X個完了]のような表示ができるのだが、他のソフトでもままあるように、各タスクの難しさや重要さがそれほど重視されていない。
その日はどれだけ頑張ったかを示す絶対的な指標、つまりスコアが欲しいと思った。合わせて、他の日と比べられたり、自分がどれぐらい頑張ってるかなどがわかるとタスクをこなすのが楽しくなるかも、というのが基本的なアイディア*1

やったこと

よりよいTODO管理のために、Emacsのorg-modeを拡張することにした。
具体的には、各タスクに S,A,B,C,D,E の5段階の指標を設けてその日の合計スコアを出すようにした*2


ふだんの見た目としてはこんな感じ*3



ついでなので、その日のスコアが過去のデータ中何番目にあたるかという情報を出し、さらに直近のスコアに勝つにはあと何ポイント必要かという情報も表示できるようにした。
しかしこれだと一日が終わるまで他の日と正確に比較ができず、タスクへのモチベーションを高めるという意味では不十分だと感じる。
なので、とりあえず試験的に朝8:00から夜24:30までを一日の活動時間として、単純に今の時刻と達成具合からその日の達成度を予想することにした。


コード

記事の最後に載せておきます。

使い方

だいたいは図にあるような感じでツリーを構築していってもらえば使えます。スコア計算の対象となる書式は以下*4。重要度を示す文字はS,A,B,C,D,Eが対象。

+ [ ] A:書式はこんな感じで、これは重要度Aの場合
スコアリング自体は C-c C-c によるチェックボックスのトグル関数にくっつけているので、チェックボックスをトグルした際に自動的に計算を行う。もう少し細かく言うと、現在位置のレベルの最初から次のレベルまでをまとまった区間として計算する。計算時はネスト(入れ子)も特別な扱いはしない*5
結果は、 "タスク完了分の得点/全タスクの得点" でミニバッファおよび親のアウトライン行の末尾*6に表示。




過去のスコアとの比較は、コマンド C-c C-; で行う。比較のためには、同じバッファ内で過去の得点が見えるようにしておく必要がある。
結果はミニバッファに表示する。結果の読み方は以下の通り。
score:[現在点/最大点] rank:[現在順位/全体数] ~次のランクまで必要なスコア || expected:[このペースで予想されるスコア(予想順位)] || day:[活動時間に対する現在時刻%] time:[経過時間/残り時間]

期待している効果

毎日のタスク達成度をスコアリングすることで、タスク管理を楽しくできて色々捗るかも。期待している効果は以下。

  1. 計画を立てることが楽しくなる(点数配分を考えることが新たな楽しみ要素)
  2. タスクを達成することが楽しくなる(タスクを達成すると点数が増えるから)
  3. ランキングによりタスク達成へ頑張る気持ちが生まれる(他の日の得点と比べられるから)
  4. スコアリングによってタスクの重要度が顕著になる(上の例だとタスクSはタスクEの32倍大事)
  5. 予想スコアや現在時刻と残り時間を表示することで時間を意識し、先延ばしが軽減される

そもそも計画を立ててうれしいことに関しては、

  • 計画を立てることで自分の今の状態がはっきりする
  • タスクの手順を明らかにすることで心理的負担を軽減させる

計画の立て方

うまい計画の立て方というのはやはり存在する。一応個人的に注意していることは以下。

  • タスクは必ず一定の基準を持って細分化する(サクサク感を得るため)
  • やりたくないタスクは間に簡単なタスクを挟む(はじめの一歩を簡単に)
  • 計画にないものでもやったことは評価する(減点法でなく加点法を採用する)

まとめ

ゲーム化のデザインに関してはもっといいものがあると思うし、表現ももっと色々できそう。例えば日毎のスコアの推移のグラフを出してみるとか、あるいは一日のうちのタスクをどれぐらいのペースでやっているかとか、どのタスクにどれくらい時間がかかったかを可視化するとか。
Emacsを使ってなくてここまで読んでくださった方は、これを機に導入してみるのもいいんじゃないでしょうか。

コード

以下を.emacsに記述する。自分の使用スタイルでしかバグを見てないので、環境や使い方によっては動かないかもしれませんが、その場合はごめんなさい。
基本的にはバッファ内の規定の書式を正規表現で検索して計算しているので、ツリーを非表示にしていたり独立したバッファに分割して表示していたりするとうまく動きません。

(defun org-my-calculate-score-insert ()
  (interactive)
  ;; 現在位置を保存、終了位置を探す(空行がなければバッファの終わりを代入)
  (let ((pos (point)) (score 0) (max 0)
        (end (string-match "^\*" (buffer-substring (line-beginning-position) (point-max)))))
    (if (not end) (setq end (point-max))
      (setq end (+ (line-beginning-position) end)))
    ;; レベルの始めから、優先順位付きのチェックボックスを探し、scoreとmaxに格納
    (outline-backward-same-level 0)
    (while (and (search-forward-regexp "^\\W*\+ *\[[ X-]\]\\W*[SABCDE]:" nil t) (< (point) end))
      (backward-char 1)
      (let ((pt 0) (cchar (preceding-char)))
        (if (eq cchar (string-to-char "S"))
            (setq pt (expt 2 5))
          (setq pt (expt 2 (- 5 (- cchar (- (string-to-char "A") 1))))))
        (message (format "found: %s, %d" (string cchar) pt))
        (when (string-match "\\[X\\]" (buffer-substring (line-beginning-position) (line-end-position)))
          (setq score (+ score pt)))
        (setq max (+ max pt)) ))
    ;; 何らかの点数があれば更新する
    (goto-char pos)
    (unless (< max 1)
      (outline-backward-same-level 0)
      (search-forward-regexp "^.*\]" nil t)
      ;; 文字を消したらposの位置が保証されない
      (let ((delp (point)) (pofs (- (line-end-position) (point))) (nofs 0))
        (delete-region (point) (line-end-position))
        (tab-to-tab-stop)
        (insert (format "+{%3d/%3d}" score max))
        (message (format "score/max: %d/%d" score max))
        (setq nofs (- (point) delp))
        (goto-char (+ pos (- nofs pofs)))))
    (list score max)))
;; チェックボックスをトグルしたらスコアを更新する
(defadvice org-toggle-checkbox
  (after org-toggle-checkbox-advice activate)
  (org-my-calculate-score-insert))

;; ランキング計算時に時刻補間する際の時刻設定
(defvar org-my-get-ranking-day-split 4.0)
(defvar org-my-get-ranking-day-start 8.0)
(defvar org-my-get-ranking-day-end 24.5)

;; @myfunc ランキングを表示する
(defun org-my-get-ranking ()
  (interactive)
  (let ((pos (point)) (score 0) (max 0) (expect 0) (slist (list)) (rank 1) (mrank 1) (erank 1) (num 0))
    (let ((scores (org-my-calculate-score-insert)))
      (setq score (car scores))
      (setq max (car (cdr scores))))
    (goto-char (point-min))
    (while (and (search-forward-regexp "\\+{\\W*" nil t)
                (string-match "[0-9]+/\\W*[0-9]+}" (buffer-substring (point) (line-end-position))))
      (setq slist (cons (string-to-number (buffer-substring (point) (search-forward-regexp "[0-9]+" nil t))) slist)))
    ;; 朝のorg-my-get-ranking-day-start時から夜のorg-my-get-ranking-day-end時までが活動時間とする
    (let ((next 99999) (ratio 0.0) (time 0.0) (active (- org-my-get-ranking-day-end org-my-get-ranking-day-start)))
      (setq time (+ (nth 2 (decode-time (current-time))) (/ (nth 1 (decode-time (current-time))) 60.0)))
      ;; 時間を整形:org-my-get-ranking-day-split時を一日の境目とする
      (when (< time org-my-get-ranking-day-split) (setq time (+ time 24.0)))
      (setq time (- time org-my-get-ranking-day-start))
      (when (< time 0.0) (setq time 0.0))
      (when (> time active) (setq time active))
      (setq ratio (/ time active))
      (if t (setq expect (/ score ratio))
        (setq expect score))
      (when (> expect max) (setq expect max))
    ;; 何番目か調べる:score, max, expectを同時に調べる:直近のスコアを得る
      (while slist
        (let ((top (car slist)))
          (when (< score top) (setq rank (+ rank 1)))
          (when (< max top) (setq mrank (+ mrank 1)))
          (when (< expect top) (setq erank (+ erank 1)))
          (when (and (< score top) (> next top)) (setq next top)))
        (setq num (+ num 1))
        (setq slist (cdr slist)))
      ;; score: [現在点数/最大点数]  rank: {現在/数},
      ;; expected score: 予想スコア(予想ランク), day: [経過時刻h/活動時間h](比率%)
      (message (format "score:[%d/%d] rank:[%d/%d] ~%d || expected:[%d(%d)] || day:[%.1f%s] time:[%.1fh/%.1fh]"
                       score max rank num (- next score) expect erank (* 100.0 ratio) "%%" time (- active time)))
      (goto-char pos))))

;; org-mode自体の設定
(add-hook 'org-mode-hook
	  '(lambda ()
               ;; - ここあたりはその他の設定 -
               ;; ランキング計算をショートカットキーに登録(キーバインドは適当に調節)
               (org-defkey org-mode-map (kbd "C-c C-;") 'org-my-get-ranking)))
記事の修正

2011/12/16 23:50 説明文の間違いや表現を修正, コード中の無意味なコメント行を削除し,忘れていたキーの設定を追加

*1:ゲーミフィケーションを意識

*2:得点配分は人それぞれだと思うけど、自分は S:32, A:16, B:8, C:4, D:2, E:1 の配分を採用した

*3:ちなみに自分はorg-modeのタスク管理機能(時間とか)はそこまでフル活用できてない。めんどくさいので箇条書き程度がちょうどいい

*4:ところで自分はわざわざこういう書式を打ち込むのが面倒なので、yasnippetという拡張で優先度のアルファベット一文字からチェックボックスまで展開させている

*5:最初の画像では親の書式にセミコロン(;)を用いてわざと得点計算に入れないようにしていた。得点は細分化したタスクの分で十分で、単に優先順位だけ表示したかったから

*6:この末尾に表示したスコアは、あとでランキングを調べるために用いられる

第三回関西Emacs勉強会

出ました。

ポジションペーパー

自己紹介

情報系の院生をしています。
研究の興味があるのはコンピュータビジョンやユーザインタフェースなど。しかし逃げるようにEmacsのカスタマイズに没頭した時期がありました。最近はWindowsばっかりなのでUnixコマンドを忘れかけ。でもMeadowは使う。モノづくり系は大好きで、最近だと「不思議箱」というのを作った。ちなみに就活中。最近の趣味は語学学習。
カスタマイズ自体は、既存機能の拡張や調整をしたり、例えばvimのfsearchなど、ちょっとした機能を実装してみたりした程度。だいたいは.emacsの中に書いているので、一度ぐらいはモード作りに挑戦したい。

お気に入りのelisp拡張(デフォルト含む)
  • elscreen (とにかくすばらしい)
  • isearch (これがないと生きていけない)
  • ido (シェルでのパス指定にも使いたい)
これまでにやってみたこと
  • elscreenの拡張(番号詰めとかresumeとか)
  • compileの連携(コンパイル後自動実行とか)
  • その他いろんな調整
作りたいモノ
  • 何となく検索
  • 画面の絶対位置移動
  • 見栄えに特化したorg-mode
今回の勉強会で知りたいこととか
  • anything.elは若干食わず嫌いになっているかもしれないのでそのすばらしさ
  • メジャー/マイナーモードを作るにあたってのおさらい
  • 実装したい機能で必要な関数がわからないとき、普段どうやって調べているか
  • 初参加なので、とりあえずお話ししたいです

TOEIC

5度目。
先月受けた結果は820(L445/R375)でした。海外まで行ったけど、正直思ってたより伸びなかった(前回760:L390/R370)。

最近忙しいけど、身辺がおちついたら今後は読みとしゃべりを中心に、学習を再開したい。

あれ, TechCrunch って英語の勉強にいいんじゃないの?

経緯1

自分は結構情報を秘密にしたがる性格なので,そうした卑しい心を断ち切って*1サービス精神を少しでも身につけるためにも,ここ2,3日でふと気づいたお気に入りの英語学習を紹介.

経緯2

多読だ精読だというのを何度も目にしたが,個人的にはどちらも負荷が大きすぎて長続きしない,あるいはそこから生じる嫌気で実際にやる時間がとても少ないという事態に陥ってしまったように思う.いや,だって多読は優しい本だとあんまり種類がない上に面白くないし,難しい本だとストレスで長続きしなかったり,がんばって読んだとしても時間がかかってしまっては結局読む量が少なかったりする.精読もやっぱりそうなりがちでしんどい.
そんなこんなで,なんか自分には日本語訳付きで普通レベルの文を無理せずどんどん噛み砕いていくのが精神衛生上好ましいと思った次第.
ところで,有名な海外のブログは,日本版があったりする.詳しくは 日本版がある著名な海外ブログをまとめてみた - YAMDAS現更新履歴 や,英語の技術blog・ニュースサイトをまとめてみた! - 毛のはえたようなもの が参考になると思うが,たとえばTechCrunchなんかは日本語訳が結構忠実なので,同時に眺め読みするだけで勉強になる.

リスト

というわけで,個人的なおすすめ日本語版のリンクを以下にコメントと一緒に載せておこう.だいたいは記事の最後に原文へのリンクがある.ちなみにGizmodeEngadget 日本版 : 最新ガジェットと家電、テクノロジーのニュースとレビューlifehackerは訳が忠実じゃなかったり文が短かったりでこのスタイルには向かないと思った.

  • TechCrunch IT関係.記事の数が豊富.翻訳の質もよく*2,程よい長さなので個人的には一番好き
  • Wall Street Journal 金融関係.文章は固め.ここの記事も原文へのリンクがあったり特集コーナーがあったりする.
  • Wired Vision 本当に見つけたばっかでよくわからないがトピックが多岐にわたる.全センテンスを訳しているわけではなさそう

あと,以下も結構いいかもしれない.

なお,さらにvallog: プログラマが好きそうな読み物100 からいくつかよさげなものをリストアップ.

読み方

通常は文章を読むとき,subvocalizationといって無意識に頭の中で音声に変換していると思うが,最低限発音を練習した後ならこのsubvocalizationは積極的にすべきだと思う.というのは,単語認識の速度を上げる効果的な練習ができることや,音声で意味を検索(頭の中で)できたり記憶に留めやすかったりで読解の補助に繋がることなどが考えられるから.前回の記事で触れた速読云々でも,subvocalizationが十分でないうちからそれを避けるのは得策ではないと今は思います*5

ついでに

上記以外の英語サイトで,どうしても日本語がないとダメという人は翻訳サイトを使うという手もある.ただ英日の変換は個人的にはまだまだ過ぎると思うので,それをやるぐらいだったら普通に読んだほうがいいとは思う.
ちなみに,日本語版のURLをクリップボードから読んで,パラグラフごとに対応文を挿入したHTMLを生成するプログラムを書いてみたけど,いつものごとくひどい実装で,クオリティも低いので公開はしない.
まぁでも2ペイン表示のアドオンとかウィンドウを左右に2つ配置とかで全然事足ります.chromeだとデフォルトでこんなテクも使えるらしいですね.

追記

日本版がある著名な海外ブログをまとめてみた - YAMDAS現更新履歴 を参照したのを忘れてたので,記事に追加.CNetとかZDNetという選択肢もあるのかもしれない.
さらに,vallog: プログラマが好きそうな読み物100 を参照していくつか追加.

*1:みんな成功しろ!!

*2:このスタイルに適しているという意味

*3:そういえばTechCrunchもそうか

*4:例えば 「site:mdn.mainichi.jp "Click here for the original Japanese story"」

*5:よくよく考えたら例の記事の元ネタはおそらくネイティブを対象とした話だろうし

リーディング時のWPM向上を支援するスクリプト書いてみた

自分は英文を読むのがクソ遅いんですが,知らないと損する英語の速読方法(1) - 一法律学徒の英語と読書な日々に感化されてちょっとWord Per Minute(WPM),つまり分あたりに読む単語数を改善しようと思い立った.
で,それっぽい支援ソフトウェアを探したんだがあまり自分の好みの物は見つからなかったので,pythonで適当に書いてみた.ウィンドウのフォーカスあたりでちょいちょいしてるので多分windowsのみサポート.そこを消せば他でも動くはずだけど実用レベルではない.
いやね,なんかフラッシュしながら表示するのはあるんだけど,ハイライトが見つからなかったんです.フラッシュ版は,Spreeder CX | なんかはある程度注目されてる模様.

で,ハイライトが好みの人向けのこのスクリプトの内容は,

  1. 設定したWPMに応じて,Ctrl+Shift+→ キーを送って,選択中の領域を単語ごとに拡大する

だけ.
要するに,表示テキストを選択できてCtrl+Shift+→ が有効なアプリケーションならなんでもよくて*1,ウィンドウを登録した後に開始付近の単語をマウスで選択状態にしてからスタートすると指定したWPMで単語がどんどんハイライトされる.



一応ウィンドウのフォーカスをとっているので暴走はそこまでしないハズだけど,これを実行したことによって生じる不利益はなんの保証もできません.

いろいろと恥ずかしいコードだけど,まぁ自分の欲求は最低限満たしているし,時間が勿体無いから書き直すこともないだろうということで投げやりに記載*2.適当に改良してください.いや,本当はchromeのextensionでできたらよかった*3んだけど,今は新しいものに手を出す余裕が無い.まぁこのスクリプトはPDFファイルにも応用できるのでこれはこれでよしとする.

実行には,以下のインストールが必要.

  1. python(2.6推奨)
  2. pyWin32
  3. SendKeys

あとは,下のスクリプトをコピペして"speedReader.py"とでも名前をつけて保存して,ダブルクリック.

#!usr/bin/python
# coding: utf-8
# 使い方:まず使用の対象となる英文を表示しているウィンドウを登録
# マウスなどを使って開始したい位置の文字を選択状態(ハイライト)にし,
# スタートすると指定したWPMで選択領域が拡大.原理的には Ctrl+Shift+->を送っているだけ
# 止めたいときは,いったんフォーカスを他のものに移す:通常はspeedReaderをクリック
# キー出力を乗っ取っているので,こちらからの入力がたまに被さる可能性がある
# 自動スクロールは実用的でないので,キー入力などを使って手動でスクロール推奨.

import sys
import time
import SendKeys
import msvcrt
import win32gui
import win32con

 
gGoalWPM = 250
gUnitTime = 0.0
gAutoScrollNum = 10     # auto scroll with this word count
gThisWindow = win32gui.GetForegroundWindow()
gTargetWindow = gThisWindow

def setWPM(w):
    global gGoalWPM, gUnitTime
    if w < 10:
        w = 10
    elif w > 1500:
        w = 1500
    gGoalWPM = w
    gUnitTime = 1.0 / (float(gGoalWPM)/60.0)
    print "Goal WPM = %d" % gGoalWPM

def selectWindow():
    global gTargetWindow
    print "select target window"
    while True:
        gTargetWindow = win32gui.GetForegroundWindow()
        if gTargetWindow != gThisWindow:
            print "[", win32gui.GetWindowText(gTargetWindow), "]"
            return True
        if msvcrt.kbhit():
            print "cancel"
            return False
        time.sleep(0.1)
    
def printHelp():
    print "s:start, o:stop, u/d:up/down speed"
    print "c:start in 3sec, 1-9:preset speed,"
    print "w:register target window, r:reset, "
    print "h:help, p:print target window, q:quit"
        
        
# main
def main():
    global gGoalWPM, gUnitTime, gTargetWindow, gCurrentWindow
    # variables
    mCurrentTime = time.clock()
    mLastTime = mCurrentTime
    mTotalTime = 0.1                 # temp

    mShouldStop = True
    mDefocus = False
    mWordCount = 0
    # variables
    
    # always on top, window size, position
    win32gui.SetWindowPos(gThisWindow,win32con.HWND_TOPMOST,500,200,350,200,0)

    if(len(sys.argv) > 1):
        gGoalWPM = sys.argv[1]
    setWPM(gGoalWPM)
    mTotalTime = gUnitTime
    printHelp()
    
    while True:
        firstLoop = True

        # wait untile goal WPM rate
        while firstLoop or (mCurrentTime - mLastTime) < gUnitTime:
            firstLoop = False
            time.sleep(0.001)
            mCurrentTime = time.clock()
            # keyboard interrupt
            if msvcrt.kbhit():
                hitkey = msvcrt.getch()
                if hitkey == '\000':
                    pass        # Send key
                elif hitkey == 'q':
                    print "exit"
                    sys.exit()
                elif hitkey == 'u':
                    setWPM(gGoalWPM + 10)
                elif hitkey == 'd':
                    setWPM(gGoalWPM - 10)
                elif hitkey == '1':    # preset 1
                    setWPM(150)
                elif hitkey == '2':    # preset
                    setWPM(200)
                elif hitkey == '3':    # preset
                    setWPM(250)
                elif hitkey == 'w':
                    selectWindow()
                    # win32gui.SetForegroundWindow()
                elif hitkey == 'h':
                    printHelp()
                elif hitkey == 'p':
                    print "[", win32gui.GetWindowText(gTargetWindow), "]"
                elif hitkey == 's':
                    print "start"
                    if gTargetWindow == gThisWindow:
                        if selectWindow():
                            win32gui.SetForegroundWindow(gTargetWindow)
                            mShouldStop = False
                    else:
                        win32gui.SetForegroundWindow(gTargetWindow)
                        mShouldStop = False
                elif hitkey == 'o':
                    print "stop"
                    mShouldStop = True
                elif hitkey == 'c':
                    if mShouldStop:
                        print "wait 3 seconds..."
                        time.sleep(3)
                        win32gui.SetForegroundWindow(gTargetWindow)
                        mShouldStop = False
                elif hitkey == 'r':
                    print "reset"
                    printHelp()
                    setWPM(gGoalWPM)
                    mLastTime = mCurrentTime
                    mTotalTime = gUnitTime
                    mShouldStop = True
                    mWordCount = 0

        if not mShouldStop:
            if not mDefocus:
                curWindow = win32gui.GetForegroundWindow()
                if curWindow != gTargetWindow:
                    print "defocus"
                    print "[", win32gui.GetWindowText(curWindow), "]"
                    mDefocus = True
                else:
                    SendKeys.SendKeys("""^+{RIGHT}""", 0.0)
                    minutes = int(mTotalTime) / 60
                    seconds = int(mTotalTime) - minutes * 60
                    decisec = int((mTotalTime - float(minutes*60 + seconds)) * 100.0)

                    print "%d/%d[wpm], " % (float(mWordCount+1)/mTotalTime*60.0, gGoalWPM),
                    print "%3d[w], %02d:%02d:%02d" % (mWordCount, minutes, seconds, decisec)
                    if gAutoScrollNum > 0 and mWordCount%gAutoScrollNum == 0:
                        # I don't know why this doesn't work
                        # win32gui.SendMessage(gTargetWindow, win32con.WM_VSCROLL, win32con.SB_LINEDOWN, 0)
                        pass

                    mTotalTime += gUnitTime
                    mWordCount = mWordCount + 1

            else:
                if gTargetWindow != gThisWindow and gTargetWindow == win32gui.GetForegroundWindow():
                    print "get focus"
                    mDefocus = False
                    mShouldStop = False

        mLastTime = mCurrentTime


# main
if __name__ == "__main__":
    main()
メモ

始めてwin32API使ったけど,なんかよくわからんことが多いのでメモ.

  • ScrollWindowEx()で他のアプリケーションをスクロールさせるのは,スクロール領域がわからないので無理?
  • 正当法であろう SendMessage() はなぜかうまく動かなかった
  • SetForegroundWindow() は,自分自身を非アクティブからアクティブにできないらしい
追記

11/14 最大wpmが低かったのでコードを修正.

*1:メモ帳,firefox, Adobe Readerとか

*2:最近こんなんばっかだな

*3:単語でなく文字ごとにハイライトとか,適度に自動スクロールとか,領域先端の単語の意味表示とか.どなたかぜひ作ってください

Google Chromeの検索エンジンのキーワードの設定を移す

最近chromeを使い始めた.firefoxはよく落ちたり固まったりしていたので,軽さや拡張の容易さ,シンクロなどでかなり快適.あとはfirefoxで動くMouseover Dictionaryに変わるものさえ出れば,素晴らしいと思う.

さて,設定とかブックマークとかシンクロしてくれるのは嬉しいんだけど,エクステンションの設定やブックマークのキーワード検索はなぜか引き継いでくれなかった(Windows 7 -> Ubuntu 10.10).どうやら,キーワード検索のデータは"Web Data"というファイルに入っているっぽく,とりあえずコピーしたら引き継げたので適当にメモ.それぞれ以下の階層で見つかった.
Windows 7環境: "C:\Users\(user name)\AppData\Local\Google\Chrome\User Data\Default"
Ubuntu 10.10環境: "~/.config/google-chrome/Default"
Mac(OS Lion)環境: "~/Library/Application Support/Google/Chrome/Default"