LoginSignup
0
0

文字列から特定文字を除外して全体の容量を削減、あるいは縮めやすくする魔法を紹介します。本記事ではこの工程を濾過と呼びます。
その対象文字となるのは全角英数字、Greek、Cyrillic、平仮名、片仮名、半角片仮名となります。同種の文字が連続しているほど効果が高くなります(2文字目からは1 byte)。

原理

以下の規則で変換します。当然可逆変換です。元の文字列に[#-~]が含まれていても問題ありません。

  • 全角英数字 → 半角英数字. 但し \(\uFF3C), ~(\uFF5E)は除外
  • Greek([\u0391-\u03CE]) → [A-~]
  • Cyrillic([\u0410-\u044F]) → [?-~]
  • 平仮名([\u3041-\3093]) → [#-u]
  • 片仮名([\u03A1-\u30FC][#-~]
  • 半角片仮名([\uFF61-\uFF9F][@-~]

実装

/* 対象文字自動設定
@txt: 文字列
@encode: 真なら濾過、偽なら逆濾過
*/
function selectFilter(txt,encode){return encode?
	/[!#-&’(-[]-}¥]/.test(txt)|
	/[\u0391-\u03CE]/.test(txt)<<1|
	/[\u0410-\u044F]/.test(txt)<<2|
	/[\u3041-\u3093]/.test(txt)<<3|
	/[\u03A1-\u30FC]/.test(txt)<<4|
	/[\uFF61-\uFF9F]/.test(txt)<<5:
	/\1[!#-\}]/.test(txt)|
	/\2[A-~]/.test(txt)<<1|
	/\3[?-~]/.test(txt)<<2|
	/\4[#-u]/.test(txt)<<3|
	/\5[#-~]/.test(txt)<<4|
	/\6[@-~]/.test(txt)<<5
}
/* encoder
@txt: 文字列
@flag: 濾過対象文字. 以下の数値の合計で決まる.
	例えばflag=8なら平仮名, flag=6ならGreek + Cyrillic
	1: 全角英数字
	2: Greek
	4: Cyrillic
	8: 平仮名
	16: 片仮名
	32: 半角片仮名
*/
function strFiltering(txt,flag){if(!flag)return txt;
	var a=0,b,c=96,d=7,A=[],F=[],G=[],C=[],H=[],K=[],L=[],S=String.fromCharCode;
	for(;d;)F[--d]=[];
	while(c)F[0][S(--c+32)]=1;
	// build filter
	if(flag&1){for(c=93,A[S(8217)]="'",A[S(65509)]="\\";c;)if(--c^59&&c^6&&c^1)F[1][A[S(c+65281)]=S(c+33)]=1;txt=txt.replace(/\1/g,"\1\1")}
	if(flag&2){for(c=62;c;)F[2][G[S(--c+913)]=S(c+65)]=1;txt=txt.replace(/\2/g,"\2\2")}
	if(flag&4){for(c=64;c;)F[3][C[S(--c+1040)]=S(c+63)]=1;txt=txt.replace(/\3/g,"\3\3")}
	if(flag&8){for(c=83;c;)F[4][H[S(--c+12353)]=S(c+35)]=1;txt=txt.replace(/\4/g,"\4\4")}
	if(flag&16){for(c=92;c;)F[5][K[S(--c+12449)]=S(c+35)]=1;txt=txt.replace(/\5/g,"\5\5")}
	if(flag&32){for(c=63;c;)F[6][L[S(--c+65377)]=S(c+64)]=1;txt=txt.replace(/\6/g,"\6\6")}
	// encoding loop
	for(txt=txt.split(flag="");c=txt[a];txt[a++]=b+c){b="";
		if(A[c]){if(d^1)b=flag="\1",d=1;c=A[c]}
		else if(G[c]){if(d^2)b=flag="\2",d=2;c=G[c]}
		else if(C[c]){if(d^3)b=flag="\3",d=3;c=C[c]}
		else if(H[c]){if(d^4)b=flag="\4",d=4;c=H[c]}
		else if(K[c]){if(d^5)b=flag="\5",d=5;c=K[c]}
		else if(L[c]){if(d^6)b=flag="\6",d=6;c=L[c]}
		else if(d&&F[d][c])b=flag,d=0
	}return txt.join("")
}
/* decoder
@txt: 文字列
@flag: 濾過対象文字. strFilteringと同じ仕様
*/
function strFiltered(txt,flag){if(!flag)return txt;
	for(var a=0,b=0,c,d,e=6,C=[],D,S=String.fromCharCode;e;)if(flag>>--e&1)C[S(e+1)]=e?[]:{"'":S(8217),"\\":S(65509)};
	// build filter
	if(flag&1)for(c=93;c;)if(--c^59&&c^6&&c^1)C[S(1)][S(c+33)]=S(c+65281);
	if(flag&2)for(c=62;c;)C[S(2)][S(--c+65)]=S(c+0x391);
	if(flag&4)for(c=64;c;)C[S(3)][S(--c+63)]=S(c+1040);
	if(flag&8)for(c=83;c;)C[S(4)][S(--c+35)]=S(c+0x3041);
	if(flag&16)for(c=92;c;)C[S(5)][S(--c+35)]=S(c+0x30A1);
	if(flag&32)for(c=63;c;)C[S(6)][S(--c+64)]=S(c+0xFF61);
	// decoding loop
	for(txt=txt.split("");c=txt[a++];)
		C[c]?c!=txt[a]?D=C[c==d?e^=1:e=1,d=c]:txt[a++,b++]=c:txt[b++]=e&&D[c]||c;
	txt.length=b;return txt.join("")
}
使用例
var s="アホハあおより蒼紫様ハチマキまきまち操気弾";
var e=strFiltering(s,selectFilter(s,1));
var d=strFiltered(e,selectFilter(e));
console.log(e,d);
// 対象文字手動選択
s="Соседская хурма часто ест клиентов.";
e=strFiltering(s,4);
d=strFiltered(e,4);
console.log(e,d);

ちなみにselectFilterは濾過(strFiltering)に使うには問題無いですが、逆濾過(strFiltered)に使う場合は要注意です。濾過前の文字列に[\x01-\x06][#-~]の組が含まれていると、正しく逆濾過できない事があります。
それが心配なら逆濾過でselectFilterを使わず、引数flagに数値を直接入力。上記例で言えばd=strFiltered(e,24)と記述。何でもかんでもまとめて処理したいなら、両方の関数の第2引数には63を指定すれば良い。

余談

ひょっとしたら見覚えのある人もいるかも。確か15年くらい前に公開した化石programに組み込まれているとかいないとか…

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0