投稿者「Yusuke Yamasaki」のアーカイブ

Yusuke Yamasaki について

http://www.linkedin.com/in/yyamasak

Tclでベクトル計算するサンプル

SXGAのリモートデスクトップウィンドウをアスペクト比を保ちつつ65%に縮小し、FullHDのディスプレイの右下に配置したい。手計算でも簡単にできることだが、今まで使ってなかった行列計算ライブラリの練習台に使うことを思いついた。

+-----------------------------+
|                             |
|                             |
|                             |
|                             |
|                             |
|               nw------------+
|               |xxxx         |
|               |   x rec x   |
|               |        xxxx |
|               |           xxx
+---------------+------------se
package require math::linearalgebra
namespace eval LinearAlgebraSample {
  namespace import -force ::math::linearalgebra::*
 
  set scale 0.65
  set v(se) [mkVector 2]
  set v(rec) [mkVector 2]
 
  setelem v(se) 0 0 1920
  setelem v(se) 1 0 1280
 
  setelem v(rec) 0 0 1280
  setelem v(rec) 1 0 1024
 
  set v(nw) [sub_vect $v(se) [scale $scale $v(rec)]]
  show $v(nw)
}

例題がしょぼすぎて練習になってないな。

線形代数苦手でプログラミングにも活用してないし、Maxima、Scilab、Octave、Rとかも使ってない。
ロボットの研究室にいたのに行列計算が苦手というのは勉強してなかった証拠ですね。

ネットワークアドレス変換関連の用語

NAT (Network Address Translation)

グローバルIPとローカルIPを1:1対応させる。

NAPT (Network Address Port Translation)

グローバルIPアドレスとポート番号のペアをローカルIPとポート番号のペアに1:1対応させる。

NAPTには多数の同義語が存在する

  • IPマスカレードはNAPTのLinuxにおける実装。
  • NATP(NAT+)、PAT(Port Address Translation)
  • ポートマッピング、ポートフォワーディング
  • 仮想サーバー、バーチャルサーバー、ローカルサーバー
  • NATオーバーロード、オーバーロード変換

静的NAT、静的NAPTは外部から内部のネットワークへのアクセスに使う。一般的なブロードバンドルータの設定に存在するのはこれらの設定。

動的NAT、動的NAPTは内部から外部への通信に対する応答が、送信元に返るようにするための機能であり、通常ユーザーが意識することはない。

と思う。

参考

IPマスカレード(NAPT,PAT,NAT+)を設定する
NAT と IPマスカレード(ポートフォワーディング) について
4.インターネットへの接続形態

Excelオートシェイプでフリーフォームの頂点を直角にする

Excel2007登場以来、なんかいい方法はないものかと探していたけど、ついに見つけた。ありがたや。

図形描画で直線の頂点を編集するには?

Public Sub main() 
    On Error GoTo L_Exit 
    With Selection.ShapeRange.Nodes 
        On Error Resume Next 
        Dim i As Integer 
        For i = 1 To .Count 
            .SetSegmentType i, msoSegmentLine 
            .SetEditingType i, msoEditingCorner 
        Next 
    End With 
L_Exit: 
    On Error GoTo 0 
End Sub

かざぐるマウスからGestures for Chrome (ChromeGesture)に乗り換え。

林檎の木で紹介されてたCloverを入れてみたけど、マウスホイールでスクロールすると無限ループに陥ってしまった。かざぐるマウスを停止すると問題は発生しない。設定でエクスプローラを除外するとか、いろいろやってみたけど効果はなし。
今まで使ったマウスジェスチャソフトで一番気に入っているものだけど、Tcl/Tkとの相性問題というのもあった。
最近はブラウザを使い分けることがほぼなくなったことと、Gestures for Chromeがそろそろ完成度が高まってるのではないかと思い、
ちょっと入れてみました。かざぐるマウスの自分設定と同じにしたものを晒しておきます。
Chrome拡張はchrome:chrome_urlsに対しては無効なので、スタートページでもやはり無効です。
あとページによってはジェスチャの開始点が限定される場合がある。それ以外はそこそこ使えそうなので、しばらくこれで試してみます。

Public Sub main() 
    On Error GoTo L_Exit 
    With Selection.ShapeRange.Nodes 
        On Error Resume Next 
        Dim i As Integer 
        For i = 1 To .Count 
            .SetSegmentType i, msoSegmentLine 
            .SetEditingType i, msoEditingCorner 
        Next 
    End With 
L_Exit: 
    On Error GoTo 0 
End Sub

Tcl/Tkで任意のフォルダをデフォルトファイラーで開く

たまにやるけど忘れるので。スペースを含まないときは別のやり方でも開くのですが、
一般的には以下のようにします。cmdの引数にエスケープされた状態で渡すというのがキモです。
string mapの変換ルールではエスケープシーケンスが適用されることに注意します。

{
  "name": "Chrome Gestures",
  "version": "1.12.1",
  "normal_actions": {
    "L": {
      "name": "back",
      "args": []
    },
    "R": {
      "name": "forward",
      "args": []
    },
    "UL": {
      "name": "select left tab",
      "args": []
    },
    "D": {
      "name": "go to #1",
      "args": [
        "http://www.google.com/",
        "Google"
      ]
    },
    "DR": {
      "name": "close this tab",
      "args": []
    },
    "#FlipBack": {
      "name": "back",
      "args": []
    },
    "#FlipForward": {
      "name": "forward",
      "args": []
    },
    "U": {
      "name": "run script #1",
      "args": [
        "(function(){if((document.fullScreenElement&&document.fullScreenElement!==null)||(!document.mozFullScreen&&!document.webkitIsFullScreen)){if(document.documentElement.requestFullScreen){document.documentElement.requestFullScreen();}else if(document.documentElement.mozRequestFullScreen){document.documentElement.mozRequestFullScreen();}else if(document.documentElement.webkitRequestFullScreen){document.documentElement.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);}}else{if(document.cancelFullScreen){document.cancelFullScreen();}else if(document.mozCancelFullScreen){document.mozCancelFullScreen();}else if(document.webkitCancelFullScreen){document.webkitCancelFullScreen();}}})()",
        "Fullscreen"
      ]
    },
    "UR": {
      "name": "select right tab",
      "args": []
    },
    "DL": {
      "name": "re-open closed tab",
      "args": []
    }
  },
  "linkdrag_actions": {
    "D": {
      "name": "open in new tab",
      "args": []
    },
    "DU": {
      "name": "open in background tab",
      "args": []
    },
    "U": {
      "name": "copy url",
      "args": []
    },
    "UD": {
      "name": "copy url and text",
      "args": []
    }
  },
  "textdrag_actions": {
    "DR": {
      "name": "search with #1 in new tab",
      "args": [
        "http://www.google.com/search?q=%s",
        "Google"
      ]
    },
    "UL": {
      "name": "copy text",
      "args": []
    },
    "UR": {
      "name": "copy html",
      "args": []
    }
  },
  "mouse_track": true,
  "visualized_arrow": true,
  "superdrag": false,
  "useMousewheel": true,
  "useTabList": false,
  "useSmoothScroll": false,
  "ScrollSpeedValue": 0.2,
  "useScrollAcceleration": true,
  "AccelerationValue": 2,
  "suppress_contextmenu": true,
  "minimumUnit": 10
}

-- 追記
上記のスクリプトではエクスプローラで開くのが前提だったので、私のように秀丸ファイラーなどの代替ファイラーを使ってる人にとっては100%満足できるものではありませんでした。なんかこれを書いてるちょっと前にStackOverflowに同じ内容の質問が挙がってて、上記の提案をドヤ顔で投稿したところ、100%満足な回答が返ってきました。
http://bit.ly/Uu6q6N

set folderpath "C:/Program Files/Microsoft Office"
exec {*}[auto_execok start] "" [file nativename $folderpath]

ちなみに、趣旨が異なりますが、エクスプローラで指定のファイルを選択した状態でフォルダを開くには、

set out "C:/Program Files (x86)/Microsoft Office/Office14/EXCEL.EXE"
exec explorer.exe /select, [file nativename $out] &

LinkedList in XOTcl

いわゆる双方向連結リストというデータ構造ですが、かつてはリチャードストールマンに、Tclには構造体がないからlinked listを作れないと指摘されていました。今ではいくつもあるオブジェクト指向拡張を使い、クラスを導入すればTclでも他の言語と同じような形で実装できます。

arrayでもできますが、あまり使いやすくならないと思うので、僕はXOTclでLinkedList::ContainerとLinkedList::Elementというクラスを作っていて、これらを継承するか、mixinしたクラスは、連結リストの管理オブジェクト、リスト要素として扱えるようにしています。

通常のリストに比べると、要素が少ないうちは遅いですが、数千数万の要素数になってくるとかなり効率が違ってきます。

テストスクリプト

set office_dir {C:\Program Files\Microsoft Office}
set escaped_office_dir [string map {\\ \\\\} [file nativename $office_dir]]
puts [concat exec [auto_execok start] explorer $escaped_office_dir]
eval exec [auto_execok start] explorer $escaped_office_dir

結果

set folderpath "C:/Program Files/Microsoft Office"
exec {*}[auto_execok start] "" [file nativename $folderpath]

tktableなんかもarrayをデータソースにすると高速なんですが、arrayを直接操作するよりも、データの順序などを安全に扱うことができる連結リストでデータ管理するのがいいと思います。

set out "C:/Program Files (x86)/Microsoft Office/Office14/EXCEL.EXE"
exec explorer.exe /select, [file nativename $out] &

パッケージにしておきました。
linkedlist.3.0.120823

過去にやった「リレーショナルデータベースの間違った使い方」

いきあたりばったりのアーキテクチャと教訓:リレーショナルデータベースの間違った使い方10項目

動的なテーブルの作成 ⇒ INSERTのトリガで月ごとのテーブル作るようにしたらINSERTが遅すぎて失敗しました。
テーブルをキャッシュとして使う ⇒ Railsではセッションキャッシュに使ってたけど、のちにcookieセッションに変わったと思う。
テーブルをキューとして使う ⇒ やりました。Dequeを使うのがいいのですが、自作のデータグリッドウィジェットと自作のTcl版ActiveRecordの依存性が高すぎて変更が大変。いまだに変えられない。
テーブルをログとして使う ⇒ 他にどうすんの?KVSってやつ?検索とか似たような機能あるの?
分散したグローバルなロック ⇒ なにそれ?
ストアドプロシージャ ⇒ 使ったことない。
使われない項目 ⇒ いくつかあるけど、そんなに多くない。検索キーにならないカラムが増えていくのを、YAML形式とかでテキストカラムに押し込めるべきか悩んでる。
ORMによって繰り返されるクエリ ⇒ よくやりました。暗黙的遅延ロード機能なんかつけるからだ。ActiveRecordなら:include=>"groups"、EFなら.Include("groups")を使う。
負荷のコントロール ⇒ DBじゃないけど、ウェブサーバのリバースプロキシとかはよくやりました。メモリリークだらけのRailsには必要だった。

break可能なコードブロック

Tclにはgotoみたいに直列に書いたコードをスキップする制御構文がない。

例えばなんかデータを受信して、チェックしたり加工したりしたあとでどこかに記録するけど、
結果によらず受信回数はカウントしときたい場合、こんなコードを書いたとする。

proc on_receive {data} {
  global cnt
  set res [validate $data]
  if {$res == 1} {
    incr cnt; return $res
  }
  set res [convert $data data2]
  if {$res == 2} {
    incr cnt; return $res
  }
  set res [save $data2]
  incr cnt; return $res
}

incr cnt; return $res; というコードを何度も書かないといけないのが嫌だ。

そこで今まではこんな書き方をしてたことがあった。

proc on_receive {data} {
  global cnt
  while {1} {
    set res [validate $data]
    if {$res == 1} break
    set res [convert $data data2]
    if {$res == 2} break
    set res [save $data2]
    break; # Don't forget!
  }
  incr cnt; return $res
}

ときどきだけど、while {1} の最後のbreakを忘れて、
無限ループに陥ることがあったので、ケアレスミスを減らすため、こう変えた。

proc on_receive {data} {
  global cnt
  foreach once {1} {
    set res [validate $data]
    if {$res == 1} break
    set res [convert $data data2]
    if {$res == 2} break
    set res [save $data2]
  }
  incr cnt; return $res
}

ちなみにforeachのところは、こうしても同じ。

proc on_receive {data} {
  global cnt
  for {} {1} break {
    # ...
  }

だけど何とも見た目が悪い。
初めて見た人は foreach {var1 var2} {1 2} break 並みに分かりづらいだろう。
多重代入のためにlassignが導入されたように、やはり目的が分かる名前の制御構文があったほうがかっこいい。

8.6のtry(8.5ではtcllibに入ってる)を使って、blockという途中でbreak可能な制御構文を作ってみる。

if {[info tclversion] <= 8.5} {
package require try
}
proc block {script} {
  try {
    return [uplevel 1 $script]
  } on break {result options} {}
}

さっきのコードを書き換えてみる。

proc on_receive {data} {
  global cnt
  block {
    set res [validate $data]
    if {$res == 1} break
    set res [convert $data data2]
    if {$res == 2} break
    set res [save $data2]
  }
  incr cnt
  return $res
}

どうかな。

これだけでblockなんて名乗るのはおこがましいので、もうちょい慎ましい名前を考えたら使ってみようと思う。

候補

  • breakable
  • through

Tclだとそんなところに脳のリソースを割かないといけないの?とか言わないでね。

ソースコード中のUnicodeリテラルを展開して出力

Tclスクリプトにマルチバイト文字が入ってると、環境によっては文字化けして実行できないという問題がありました。
コメントは英語表記にしてあるのでいいとして、ギリシャ文字のシグマとか、パスワード隠し記号とかは見た目上どうしても使いたいので、そういう記号は全部Unicodeリテラル表記にしました。ここのツールを使うと簡単にUnicodeリテラルを調べられます。

http://www.snible.org/java2/uni2java.html

ついでに、あとでリテラルが何の記号に対応するかみたいという場合もあると思って、以下のようなスクリプトを書きました。
これでリテラルを展開した結果を見られます。

proc on_receive {data} {
  global cnt
  set res [validate $data]
  if {$res == 1} {
    incr cnt; return $res
  }
  set res [convert $data data2]
  if {$res == 2} {
    incr cnt; return $res
  }
  set res [save $data2]
  incr cnt; return $res
}

仕組みとしては単に\uXXXXという文字を見つけて、置換してるだけです。
Tclの場合は、Unicodeは16bitまでしか定義されてないので、リテラル表記は例外なくこれで処理できるようです。
なお、{\uXXXX}という表記はリテラルとはみなされないのですが、ここでは考慮してません。

Jet/ACEでExcelファイルをCSVに変換する

ある分析装置がxlsx形式でレポート出力するのでExcelのCOMインターフェース経由でCSVに落としてから処理していました。
この方法だとMicrosoft Excelが必須になってしまいます。
あとExcel本体経由なので、セキュリティ設定によっては手動でロック解除しないと開けないとかいう場合もあります。
そこでライセンス料をケチるべく、Excelなしでxls/xlsx -> CSV変換プログラムを書く方法を調べました。

# filename: subst_unicode_literal.tcl
proc read_file {f} {
	set ch [open $f r]
	set out [read $ch]
	close $ch
	return $out
}
 
proc extract_unicode_literals {str _ll _lu} {
	upvar $_ll ll
	upvar $_lu lu
	set ll [lsort -unique [regexp -all -inline -expanded {(\\u[0-9A-Fa-f]{4})} $str]]
	set lu [subst [join $ll]]
}
 
proc make_map {ll lu} {
	set map {}
	foreach l $ll u $lu {
		lappend map $l $u
	}
	return $map
}
 
set f [lindex $argv 0]
set str [read_file $f]
 
set ll {}
set lu {}
extract_unicode_literals $str ll lu
set map [make_map $ll $lu]
set out [string map $map $str]
 
puts -nonewline $out

コンパイルは、

csc Excel2CSV.cs

ファイル名を引数に与えると、標準出力にCSVを出力します。エラーは終了コードで通知します。

Excel2CSV.exe sample.xlsx > sample.xlsx.csv
echo %ERRORLEVEL%
0

必須コンポーネント

Microsoft Access Database Engine 2010 Redistributable

.NET Framework 2.0 以上?

.NET Framework 4.0向けに コンパイルしたもの Excel2CSV

ただ、COM経由で取得したCSVとは、数値の値が違うことがあります。
原因はDAOを使った場合は、表示書式を適用した後の値しか取得できないためのようです。
たとえば、12345.6789という数値を持つセルに、"小数以下桁数2桁、桁区切り"という書式が設定されている場合、12,345.68が抽出されます。
考え方によってはこの方がいいというケースも、だめなケースもあるかも知れません。
とりあえず、桁区切り(,)の書式はCSVにとって邪魔なので、上記のプログラムでは数値とみなせる文字列は一旦数値に変換しています。

ちなみにxdoc2txtの場合は、桁区切りなし、四捨五入済みの数値を吐きます。
というか、いろいろ実験してたらxdoc2txtの出力ってゴミが入ってたりしてプログラムから後処理するのがめんどくさげ。
そもそも商用ライセンスは1000本単位じゃないと買えないので却下。

あと、xlsxをxlsに変換してExcel2CSVにかけると、可視シートだけが出力されるという微妙な違いもありました。
普通は隠しシートの内容を見たいということはないと思いますが。