効率化しない、しかし最速にするコツ (Dictionaryキャッシュ)

用途が、「入力」と「出力」の組み合わせ、という処理で、一度の起動のうちで、利用する組み合わせ1万パターン以下のケースでは、

「入力」から「出力」を割り出す処理は平易でわかりやすく記述して、記述レベルでは効率化しない。

C#ならDictionaryを利用して、「入力」キーに対して「出力」値(オブジェクト)をセットする。

こうすることで、「入力」がDictionary既存であれば、その「出力」を利用する。なければ処理を行う。

こうすることで、見やすいソースだが効率化していない遅めの処理であっても【遅いのは1度だけ】であって、
【2度目からは処理を省略できる超高速」】となります。

私は入力が『式の文字列 string』で、出力が『二分木 object』のもので、このDictionaryキャッシュを使いました。
コツは、再利用するObjectに【初期化】機能の関数を実装しておくことです。再利用時、新規objectと同じ状態にする為。
二分木にする処理は前処理を数回行ったりするので時間がかかるものですが、これを利用することで高速になりました。
再利用時の初期化といっても、nodeごとの計算結果保持のobjectを初期化するだけなので、あくまで既存nodeの計算結果objectを1つずつ初期化するだけなので、作り直すよりかは遙かに高速でした。

iTextSharpのB4はISO B4。日本はJIS B4。プリンタが認識しない!

ということで、以下が修正後の値です。

float B4_width = 1031.74f;
float B4_height = 728.4f;
float B5_width = 728.4f;
float B5_height = 517.92f;

以下は用紙の縦・横を考慮して、PageSize.B4.Rotate()相当にしたものです。

case "B4":
case "B4縦":
return new iTextSharp.text.Rectangle(B4_height, B4_width, 90);
case "B4横":
return new iTextSharp.text.Rectangle(B4_width, B4_height, 0);
case "B5":
case "B5縦":
return new iTextSharp.text.Rectangle(B5_height, B5_width, 90);
case "B5横":
return new iTextSharp.text.Rectangle(B5_width, B5_height, 0);

リコー社のPDF directプリント対応の複合機で、PageSize.B4だと 不定形 エラーが出たので調べていたら、
生成したPDFの用紙サイズが日本のB4(JIS B4)の定義と数ミリ違っていました。
iTextSharpは独自の単位で float型で持っているので、誤差の割合分だけ修正したところ、用紙の自動選択が
正常に機能しました。

PDF表示のソフトウェアからの印刷ではエラーは出ないし、微妙に引き延ばされた程度なので気づきにくく、
等倍ならなおさら判明しません。

外国のものなので、ISOが標準なのでISO B4などで実装されているのは当然ですが、A4は同一だったので
この微妙な違いになかなか気づきませんでした。

iTextSharpは、ISO 216 で画定されている国際標準(ISO-Bシリーズ) ISO B4の大きさは 250mm×353mm
日本は JIS P0138 縦横比は 1:√2 JIS B4の大きさは 257mm×364mm
リコー MPC4503 で試しました。
です。

C#でシンプルでわかりやすい遷移を書く

ポイントは1週間後にソースを見て、「何をしたい」記述となっているのかを瞬時に思い出せる・理解できるかどうかです。

具体的には

private void test()
{
int caret_x = 10;
bool caret_Hankaku = true;

Func<string>get_caret_status=()=>
{
if(caret_x<0)
return "画面外";
if(caret_Hankaku==true)
return "半角";
return "全角";
};
string tmp_caret_status=get_caret_status();
switch(tmp_caret_status)
{
case "画面外":
・・・画面外のときの処理・・・
break;
case "半角"
・・・半角のときの処理・・・
break;
case "全角"
・・・半角のときの処理・・・
break;
}
}

のようにすることで、処理する種類を事前に仕分けしてから、switch文などで処理を分けて書けます。
同様のことはclass単位でも当然できるのですが、関数単位ならこのように「匿名関数の匿名じゃない版」を利用してみると、思い出しやすいソースになると思います。

C# yield return で多層フィルタリング

テキストファイルから読み込むのを例に、foreachで簡単に多層フィルタリングができるものの例です。



class values
{
public string string_1;
public values(string in_string_1)
{
string_1 = in_string_1;
}
}
static private void test_proc()
{
var IE_rfl = yr_ReadFileLine(@"C:\bin\NowPasori.txt");
var IE_rlf_filter1 = yr_ReadFileLine_Filter_有効行のみ(IE_rfl);

foreach(values tmp_line in IE_rlf_filter1)
{
string debug1 = tmp_line.string_1;
}

}
static private IEnumerable<values> yr_ReadFileLine(string in_filepath)
{
if (File.Exists(in_filepath) == false)
yield break;

using (var sr = new StreamReader(in_filepath, Encoding.GetEncoding("Shift_JIS")))
{
while (sr.Peek() > -1)
{
yield return new values(sr.ReadLine());
}
}
}
static private IEnumerable<values> yr_ReadFileLine_Filter_有効行のみ(IEnumerable<values> in_IE_line)
{
foreach (values tmp_line in in_IE_line)
{
if (string.IsNullOrWhiteSpace(tmp_line.string_1) == true ||
tmp_line.string_1.TrimStart().StartsWith(";") == true)
continue;
if (tmp_line.string_1.Contains(";") == true)
tmp_line.string_1 = tmp_line.string_1.Substring(0, tmp_line.string_1.IndexOf(';'));
tmp_line.string_1 = tmp_line.string_1.Trim();
yield return tmp_line;
}
}

ATOK手書き入力をESCキーで閉じる

閉じるボタンをマウスクリックするしか閉じる方法がないものですが、以下の様なスクリプトで閉じることができます。

$ESC:: ; 頭に$が必要です。(無限ループ防止のため)
ifWinExist, ahk_class ATOK30TegakiPalette ; 「手書き入力」が起動していたケース
{
Close_ATOK_TEGAKI()
return
}
Send, {ESC}
return


Close_ATOK_TEGAKI() {
ifWinExist, ahk_class ATOK30TegakiPalette ; 「手書き入力」が起動していたケース
{
;ATOK手書き入力が起動しているときは、必ず(隠された)アクティブなウィンドウでもあります。

;WinClose, ahk_class ATOK30TegakiPalette
;これだと、ATOK内部での「手書き入力」『起動すべきフラグ』が立ったままとなり、また出てくる・・・

CoordMode, Mouse, Screen
MouseGetPos, xpos, ypos
WinGetPos, X, Y, Width, Height, ahk_class ATOK30TegakiPalette
tar_X := X+Width-5
tar_Y := Y+5
MouseClick, Left, %tar_X%, %tar_Y%, 1, 0
MouseMove, %xpos%, %ypos%, 0
return
}

return
}

AutoHotkeyで暗号化(AES)を扱うコツ

特に暗号化後のバイナリ値の扱いで躓いたので、以下のソースを参考にしてみてください。
また、漢字も平気ですがAutoHotkeyのソース上では全角のAはダメ文字だったりしますので、そこは注意してください。

; AutoHotkeyでAES化して、暗号バイナリを 0xE8 なら "e8"のように見えるテキストにして、
; 保存、読み込み、 "e8"を 0xE8 にしてから、復号化して、元の文字列にする
; という一連の処理
; バイナリ文字列を得ても、文字列として扱う(関数に引数として渡す、関数の返値、コピー)では
; 途中に 0x00 があるとNULLとして扱うようで、それ以降を0x00の連続としてしまう・・・
; この点に注意が必要だった! (一日潰した・・・!)
; Crypt_AES()関数: https://autohotkey.com/board/topic/21913-aes-encryptdecrypt-of-a-file-in-wxp-or-higher/page-2
;
; 漢字も平気ですがAutoHotkeyのソース上では全角のAや柿などはダメ文字だったりしますので、そこは注意してください。
; ダメ文字の参考:http://blechmusik.hatenablog.jp/entry/20090725/1248455271
;
; 2017.3.26




Password = AutoHotkey

Data = 12345678901234567

Data_Len := StrLen(Data)

moto_text = %Data%0123456789abcdef ; allocate 16 byte more space

Encodeed_text := moto_text
Enc_size := Enc_AES(&Encodeed_text, Data_Len, Password, 256)
Encodeed_text := StrGet(&Encodeed_text, Enc_size)


/*
fw := FileOpen("c:\temp\test2.txt", "w")
fw.RawWrite(Encodeed_text, Enc_size)
fw.Close()

fr := FileOpen("c:\temp\test2.txt", "r")
fr.RawRead(Decoded_text, fr.Length)
fr.Close()
*/

;バイナリデータを変数に入れたものをユーザ定義関数へ渡す、となると、バイナリ値に 0x00 があると、それ以降は0x00で初期化したものを渡してしまう・・・
;なので、変数のアドレス渡し、の方法が必須となります。
Encoded_DXtext := text_to_DXtext(&Encodeed_text, Enc_size)


fw := FileOpen("c:\temp\test2.txt", "w")
tmp_text := Format("{1},{2}", Enc_size, Encoded_DXtext)
fw.WriteLine(tmp_text)
fw.Close()

fr := FileOpen("c:\temp\test2.txt", "r")
tmp_line := fr.ReadLine()
tmp_line := RegExReplace(tmp_line, "`n$")
fr.Close()
COMMA := ","
StringSplit, tmp_line_array, tmp_line, %COMMA%
Encoded_DXtext := tmp_line_array2
;msgbox, Encoded_DXtext : %Encoded_DXtext%
Encoded_size := StrLen(tmp_line_array2) / 2


VarSetCapacity(Decoded_text, Encoded_size)
DXtext_To_Text(&Decoded_text, Encoded_DXtext)
;test := StrGet(&Decoded_text, Encoded_size)
;msgbox, Decoded_text : %test% Encoded_size : %Encoded_size%
Dec_size := Dec_AES(&Decoded_text, Encoded_size, Password, 256)
Decoded_text := StrGet(&Decoded_text, Dec_size)
msgbox, Dec_size : %Dec_size% 復号後Decoded_text : %Decoded_text%
ExitApp







Enc_AES(pData, nSize, sPassword, SID = 256) {
ret := Crypt_AES(pData, nSize, sPassword, SID, 1)
Return, %ret%
}
Dec_AES(pData, nSize, sPassword, SID = 256) {
ret := Crypt_AES(pData, nSize, sPassword, SID, 0)
Return, %ret%
}



Crypt_AES(pData, nSize, sPassword, SID = 256, bEncrypt = True) {

CALG_AES_256 := 1 + CALG_AES_192 := 1 + CALG_AES_128 := 0x660E

CALG_SHA1 := 1 + CALG_MD5 := 0x8003

DllCall("advapi32\CryptAcquireContextA", "UintP", hProv, "Uint", 0, "str"

, "Microsoft Enhanced RSA and AES Cryptographic Provider" . (A_OSVersion="WIN_XP" ? " (Prototype)" : ""), "Uint", 24, "Uint", 0)

DllCall("advapi32\CryptCreateHash", "Uint", hProv, "Uint", CALG_SHA1, "Uint", 0, "Uint", 0, "UintP", hHash)

DllCall("advapi32\CryptHashData", "Uint", hHash, "Uint", &sPassword, "Uint", StrLen(sPassword), "Uint", 0)

DllCall("advapi32\CryptDeriveKey", "Uint", hProv, "Uint", CALG_AES_%SID%, "Uint", hHash, "Uint", SID<<16, "UintP", hKey)

DllCall("advapi32\CryptDestroyHash", "Uint", hHash)

If bEncrypt

DllCall("advapi32\CryptEncrypt", "Uint", hKey, "Uint", 0, "Uint", True, "Uint", 0, "Uint", pData, "UintP", nSize, "Uint", nSize+16)

Else DllCall("advapi32\CryptDecrypt", "Uint", hKey, "Uint", 0, "Uint", True, "Uint", 0, "Uint", pData, "UintP", nSize)

DllCall("advapi32\CryptDestroyKey", "Uint", hKey)

DllCall("advapi32\CryptReleaseContext", "Uint", hProv, "Uint", 0)

Return nSize

}

DecText_1(in_x){
if(in_x=="0" || in_x=="")
Return 0
if(in_x=="1")
Return 1
if(in_x=="2")
Return 2
if(in_x=="3")
Return 3
if(in_x=="4")
Return 4
if(in_x=="5")
Return 5
if(in_x=="6")
Return 6
if(in_x=="7")
Return 7
if(in_x=="8")
Return 8
if(in_x=="9")
Return 9
if(in_x=="A" || in_x=="a")
Return 10
if(in_x=="B" || in_x=="b")
Return 11
if(in_x=="C" || in_x=="c")
Return 12
if(in_x=="D" || in_x=="d")
Return 13
if(in_x=="E" || in_x=="e")
Return 14
if(in_x=="F" || in_x=="f")
Return 15

Return 0
}



text_to_DXtext(in_Ptext, in_length)
{
ret_text =
Loop, %in_length%
{
tmp_c := NumGet(in_Ptext+0, A_Index - 1, "UChar") ;StrGet(&in_text + A_Index - 1, 1)
tmp_DecText := Format("{1:02x}", tmp_c)
;MsgBox, tmp_c : %tmp_c% A_Index : %A_Index% tmp_DecText : %tmp_DecText%
ret_text .= tmp_DecText
}
Return, %ret_text%
}
DXtext_To_Text(in_Out_Ptext, in_text)
{
tmp_len := StrLen(in_text) / 2
Loop, %tmp_len%
{
tmp_c1 := StrGet(&in_text + (A_Index - 1) * 2 , 1)
tmp_c2 := StrGet(&in_text + (A_Index - 1) * 2 + 1, 1)
tmp_c_val := DecText_1(tmp_c1)*16 + DecText_1(tmp_c2)
tmp_c := chr(tmp_c_val)
NumPut(tmp_c_val, in_Out_Ptext+0, A_Index - 1, "UChar")
;test := StrGet(in_Out_Ptext, A_Index)
;msgbox, tmp_c_val : %tmp_c_val% test : %test%
}
;test := StrGet(in_Out_Ptext, tmp_len)
;msgbox, tmp_len : %tmp_len% test : %test%
Return, %ret_text%
}

itextsharpを利用しやすくするコツは、簡易でいいのでclassを作っておく

印刷用の文書を生成するのに、DOCやDOCX等を生成して他のソフトに印刷させるよりも、PDFでファイル出力して、PDF印刷ソフト(他のソフト)で印刷させるか、PDF Direct Print対応プリンタなら、直接ファイルコピーをしてしまう、という方法があります。

連続して帳票を出すような、バッチ系の処理であればシンプルに PDF Direct Print の方法で、というのが現実的な感じもします。
(ちなみに、Ricoh の MPC4503 やそれ以降の複合機では、標準でPDF Direct Printに対応していました。MACでは別途料金がかかる、というのがよくわかりませんが、WindowsからPDFファイルをコピー(\\複合機のIP\mpc4503jpn へコピー)して送信したぶんには大丈夫でした。)

ところで、そもそもPDFの座標系が左下を 0,0 で基準にしているので、itextsharpもそれに準じているので、初っぱなから躓いたりしたり、PDFがPostScript系なので、【そもそも手書きでスクリプトを書く】仕様なのでセンテンスを作り、フォント種・サイズ設定、など面倒にしか感じない仕様に itextsharp も準じています。

なので、利用したい方法(関数)を用意したclassをまずは作成して、それをitextsharp流儀(笑)に変換してitextsharpに渡す、というのを、一度作成しておくと非常に楽です。

私の場合は特に、PR201用の出力をPDFにする、という目的だったので、単位換算ようのクラスの作成から始め、それを左上を 0,0 基準として、横○○/160インチに □という文字を印字、そうしたら仮想ヘッドを1文字分(半角か全角かの2種類のどちらか)を右へ移動させ(pos_x のような変数に 12/160 インチ加算するだけ)、次の文字を・・・というように1文字単位で出力する関数を用意しておきました。
このようにすると、等間隔で1文字ずつ、というのがPDF、itextsharpの仕様に関係無く実現できます。
そして、CPUは今時の3GHz超えですので、B4にビッシリ文字があっても、瞬時に処理が終了します。むしろプリンタの印刷処理(用紙移動だけの時間としても)の方が遅いくらいです!

用途に応じて、lprintf(x,y,text,FontName,FontSize) を渡すだけでOKなレベルの関数を用意しておくと、その後がずっと楽です。

あと、複数ページの処理がちょっとコツが必要なので、私は1ページごとのPDFを作成する、というのを基本にして、必要があれば1ファイルにまとめる処理をitextsharpで作成しておいたので、それを外部exeで利用して、という方法で行っています。