static string Stringize(string content) { const int MAX_WIDTH = 128; content = RegExp.RemoveLineBreaks(content); // 短い場合 if (content.Length <= MAX_WIDTH) { return("'" + RegExp.EscapeSingleQuote(content) + "'"); } // 長い場合 System.Text.StringBuilder b_src = new System.Text.StringBuilder(); b_src.Append("[\n"); bool first = true; int i = 0; for (;;) { int c = content.Length - i; if (c > MAX_WIDTH) { c = MAX_WIDTH; } if (first) { first = false; } else { b_src.Append(",\n"); } b_src.Append('\''); b_src.Append(RegExp.EscapeSingleQuote(content.Substring(i, c))); b_src.Append('\''); i += c; if (i == content.Length) { break; } } b_src.Append("\n].join(\"\")"); return(b_src.ToString()); }
public static string GenerateTokenReplacing2(string input, Argument args) { // 1 各単語の登場数を数える // Gen::Dictionary <string, int> hist = new Gen::Dictionary <string, int>(); foreach (Rgx::Match m in reg_tok.Matches(input)) { string word = m.Value; if (hist.ContainsKey(word)) { hist[word]++; } else { hist[word] = 1; } } // 2 単語の "重み" を計算する // // ※優先順位は頻度だけで決まる (共に置換すると決まっている場合) // // ∵トークン A, B のどちらを 1文字/2文字で表現するか決める場合は、 // 「1文字の場合の gain がより多い方を選ぶ」のではなく合計の gain で判断すべき。 // A, B をそれぞれ a, b と符号化する時、合計の gain は // (len(A)-len(a))count(A)+(len(B)-len(b))count(B)-(len(A)+1+len(B)+1) // = const - len(a)count(A) - len(b)count(B) // これを最大化する為には count() の大きいトークンに小さい符号を割り当てれば良い。 // 重要なのは各トークン自体の長さには依存しないという事である■ // // ※或るトークン A が数字 N をおしのけてまで符号化するべきかどうかも頻度で決まる // // - もし数字Nも置換対象になっている場合は大体 count(A)-count(N) の gain がある。 // (もしトークン A を1文字長い別の符号で表すと count(A) のロスがある。 // 一方で数字 N を1文字長い別の符号で表すと count(N) のロスがある。 // 実際には1文字長いだけの符号で表す事ができるかどうかは分からないが。) // - 加えて数字Nをおしのける事によって数字Nを表に登録する必要が出るので // len(N) のロスがある。更に、数字Nの符号 n が最大の符号だとすると区切り文字 // として 1 以上のロスが生じる。つまり、符号表が // ....|foo → ....|foo||||||n // 等の様に変化するという事である。近似的に常に 1 文字のロスがあるという事にする。 // →結局近似的に count(A) - (count(N)+len(N)+1) の gain と考える事にする。 // つまり、数字の重みは count(N)+len(N)+1 という事である。 // int wordCount = hist.Count; string[] tokens = new string[wordCount]; int [] weights = new int [wordCount]; int j = 0; foreach (string cand in hist.Keys) { tokens [j] = cand; weights[j] = hist[cand]; int icand; if (int.TryParse(cand, out icand) && icand.ToString() == cand) { weights[j] += cand.ToString().Length + 1; } j++; } System.Array.Sort(weights, tokens); // 3 重みの高い符号から順番に符号を割り当てる // - 符号は短い物から順に割り当てる // - 整数は無条件で符号化対象として考慮する // - その他の単語は gain > loss の時に考慮する: // "次に短い符号の長さ" を用いて、 // その単語の gain, loss を計算する。 // string[] mapCodeToWord = new string[wordCount]; // 新しいコードの割当先、かつ、有意な符号の終端を表す。 // 有意な符号とは "自分自身から map される符号" ではないという事。 int code = 0; // 有意かどうかに拘わらず登録されている符号の数 int codeCount = 0; // code Count // 現在割り当てられている符号の長さ // 及び、その長さの符号の範囲 int ndigit = 0, code0 = -1, codeM = 0; int effectiveWordCount = wordCount; for (int index = wordCount; --index >= 0;) { string cand = tokens[index]; if (codeCount >= codeM) { ndigit++; code0 = codeM; codeM = (int)System.Math.Pow(10, ndigit); } int icand; bool isInt = int.TryParse(cand, out icand) && icand.ToString() == cand; if (!isInt || icand >= effectiveWordCount) { // gain, loss を計算し割に合わないなら跳ばす // 小さい整数(<effectiveWordCount)は別の単語に上書きされる危険があるので跳ばせない int loss = cand.Length + 1; int gain = (cand.Length - ndigit) * hist[cand]; if (gain <= loss) { effectiveWordCount--; continue; } } if (isInt && code0 <= icand && icand < codeM) { // 不変な整数値の場合 // 整数値で符号と自身の長さが同じ場合は符号表に登録する必要はない // 自分自身に対して mapping すれば良い。 if (icand < code) { // 自分自身の番号に既に別の単語が割り当てられている場合は待避 mapCodeToWord[code++] = mapCodeToWord[icand]; } if (icand < wordCount) { mapCodeToWord[icand] = ""; } } else { // 普通の単語の場合 // 登録する。既に不変の整数値として決まっている番号は跳ばす。 while (mapCodeToWord[code] != null) { code++; } mapCodeToWord[code++] = cand; } codeCount++; } // 4 置換の実行 if (code == 0) { return(input); } Gen::Dictionary <string, string> mapWordToCode = new Gen::Dictionary <string, string>(); System.Text.StringBuilder table = new System.Text.StringBuilder(); for (int c = 0; c < code; c++) { string word = mapCodeToWord[c]; if (word != "") { mapWordToCode[word] = c.ToString(); } if (c > 0) { table.Append(':'); } table.Append(word); // System.Console.WriteLine( // "dbg: {0} -> {1} {2}", word, c, hist.ContainsKey(c.ToString()) ? "collide" : ""); } string replaced = reg_tok.Replace(input, delegate(Rgx::Match m) { string word = m.Value; if (mapWordToCode.ContainsKey(word)) { return(mapWordToCode[word]); } else { return(word); } }); string strTable; string strSource; if (args.FlagFoldString) { strTable = Stringize(table.ToString()); strSource = Stringize(replaced); } else { strTable = "'" + RegExp.EscapeSingleQuote(table.ToString()) + "'"; strSource = "'" + RegExp.EscapeSingleQuote(replaced) + "'"; } // string ret = @"(function(){ // var r = " + strTable + @".split("":""); // var s = " + strSource + @"; // eval(s.replace(/\b\d+\b/g,function($){return r[$]||$;})); // })();"; string ret = @"(function(r){ r=" + strTable + @".split("":""); eval(" + strSource + @".replace(/\b\d+\b/g,function($){return r[$]||$;})); })();"; if (!args.FlagFoldString) { ret = RegExp.RemoveLineBreaks(ret); } return(ret.Length < input.Length ? ret : input); }