[c#] SJISというかCP932とかいう呪い

文字変換

未だに「xxx(※1)はシフトジスでくださいねー」とかいうトチ狂った環境がどのような場所にもある。大抵の社畜が躓くのがあのExcelとのデータやり取りなのだが、何故かあのソフトウェアを皆さん本当に大好きなので困る。中途半端に万能なのが悪いのだが、使用者にも問題(例えばまともにcsv開けない(※2)とか)も多く誰を責めていいものやら。

部下(社畜)を殺しておいて、まるで花火見物で雨に振られたときのように「無駄足だった」か……。面白いのう、お前ら。まあ、せっかくだから死んで行けや

という気分ですよ。阿葉山宗介さんもそりゃマジギレですよ。多くの人間が(主にエクセル対策で)死んでいった。それが文字コード問題。まあ俺から言わせればSJISなんて大したこと無い。EBCDIC(IBMメインフレームとか)の地獄をお前らに味わわせてやりたい。

※1 ここにはExcelとかcsvとかtsvとかお好きなものを脳内で入れてください

※2 データインポートとか誰もやらねえよ!

Windows上でのUTF8 -> CP932変換問題

この令和の時代でも上のような需要があるので文字コード変換からは逃げられないのです。で、説明するのも面倒なので"utf8 sjis 文字化け 波線"とかでググってください。簡単に説明すると、複数の「なんじゃこりゃ」仕様が重なって文字化けするんですね。一部が。世の中のシステムは必ず腐っていくものだけど、なぜかといえば必ず"その場しのぎ"でなんかしちゃうからなんですって。どこかで聞いたんだけど。

変なコードを見た

なんか1文字づつユニコードエスケープ文字になおして比較してたんだけど、これ遅くねえか?と思って調べたら遅かったっていうね。しかし、俺が想定していたコードも最速ではなかった。のだが書いた。

気がついたのだけど、エントリ単体のページはちゃんとコードハイライトが聞いているけど、トップとかカテゴリとかの一覧ページで効いてないのよね。WordPressのJQuery外してる影響っぽいけど、レンダリング速度を優先するのでこれからはコードは"続きを読む"以降に書くことにしました。

で、これ

((int)char).ToString("x4") // -> 波線だと"301c"

ReplaceUnmappedChars無印みたいなコードだった(なんか↑みたいにして変換した上で更に比較して……って何回キャストすんねん)のだけど、俺はこれが普通ちゃうの?と1を書いた。まあ普通に速いよね。でも、refで戻したほうが速いかなと2を書いたらもっと速かった。StringBuilderを魔法みたいに思っていた時期もあったので3を書いたけどもちろんテヘペロ案件です。偉そうに能書き垂れる前にコード書いてstopwatchですね。

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Diagnostics;
using System.Linq;

namespace ReplaceUnmappedChars
{
    class Program
    {
        private static Dictionary<Char, Char> remap; 

        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            var list = GetFileData();
            var sw = new Stopwatch();

            // 4のみで使用
            remap = new Dictionary<Char, Char>(){{'〜', '~'},{'−', '-'}, {'¢', '¢'}, {'£', '£'}, {'¬', '¬'}, {'—', '―'}, {'‖', '∥'}};
            // start
            sw.Reset();
            sw.Start();
            // 処理 100万回実行
            for(var idx = 0; idx < 1000000; idx++){
                ReplaceUnmappedChars4(ref list);
                // list = ReplaceUnmappedChars1(list);
            }            
            // stop
            sw.Stop();
            string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:0000}", sw.Elapsed.Hours, sw.Elapsed.Minutes, sw.Elapsed.Seconds, sw.Elapsed.Milliseconds);
            Console.WriteLine(elapsedTime);
            OutputFile(list);
        }

        // 00:00:02.0241
        static void ReplaceUnmappedChars4(ref List<string> list)
        {
            for(var i = 0; i < list.Count; i++){
                var tmp = list[i].Select(c =>
                {
                    char r;
                    if(remap.TryGetValue(c, out r)) return r;
                    return c;
                });
                list[i] = new string(tmp.ToArray());
            }
        }

        // 00:00:00.0999
        static void ReplaceUnmappedChars3(ref List<string> list)
        {
            for(var i = 0; i < list.Count; i++){
                list[i] = new StringBuilder(list[i]).Replace('〜', '~')
                    .Replace('−', '-')
                    .Replace('¢', '¢')
                    .Replace('£', '£')
                    .Replace('¬', '¬')
                    .Replace('—', '―')
                    .Replace('‖', '∥')
                    .ToString();
            }
        }

        // 00:00:00.0447
        static void ReplaceUnmappedChars2(ref List<string> list)
        {
            for(var i = 0; i < list.Count; i++){
                list[i] = list[i].Replace('〜', '~')
                    .Replace('−', '-')
                    .Replace('¢', '¢')
                    .Replace('£', '£')
                    .Replace('¬', '¬')
                    .Replace('—', '―')
                    .Replace('‖', '∥');
            }
        }

        // 00:00:00.0595
        static List<string> ReplaceUnmappedChars1(List<string> list)
        {
            var newList = new List<string>();
            foreach(var str in list){
                newList.Add(
                    str.Replace('〜', '~')
                        .Replace('−', '-')
                        .Replace('¢', '¢')
                        .Replace('£', '£')
                        .Replace('¬', '¬')
                        .Replace('—', '―')
                        .Replace('‖', '∥')
                );
            }
            return newList;
        }

        // 00:00:01.0786
        static List<string> ReplaceUnmappedChars(List<string> list)
        {
            var newList = new List<string>();
            foreach(var str in list){
                var newStr = string.Empty;
                foreach(var s in str){
                    if(s > 127){
                        switch(s){
                            case '〜':
                                newStr += '~';
                                break;
                            case '−':
                                newStr += '-';
                                break;
                            case '¢':
                                newStr += '¢';
                                break;
                            case '£':
                                newStr += '£';
                                break;
                            case '¬':
                                newStr += '¬';
                                break;
                            case '—':
                                newStr += '―';
                                break;
                            case '‖':
                                newStr += '∥';
                                break;
                            default:
                                newStr += s;
                                break;
                        }
                    }else{
                        newStr += s;
                    }
                }
                newList.Add(newStr);
            }
            return newList;
        }

        static List<string> GetFileData()
        {
            var list = new List<string>();
            using var reader = new StreamReader("source.txt", Encoding.Default);
            while(reader.Peek() > -1){
                list.Add(reader.ReadLine());
            }
            reader.Close();
            return list;
        }

        static void OutputFile(List<string> list)
        {
            // .net coreにはdefaultでcp932がないので"System.Text.Encoding.CodePages"をNugetでゲットしてRegisterProvider呼ぶ
            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
            using var writer = new StreamWriter("destination_fix4.txt", false, Encoding.GetEncoding(932));
            foreach(var str in list){
                writer.WriteLine(str); // 本来はここで変換するんだけど
            }
            writer.Close();
        }
    }
}

ハイライトきいてなくね?!?!まあいいか。気が向いたら調べる。なんも変えてないんだけど効いたり効かなかったりするんだよなー。これとか効いてるもんなー。https://www.dobusarai.net/blog/visual-studio-code-embeddedresource/ ちなみに編集エディタでは効いてるのよなー。

追記1

hogefuga

テスト。何故かc#だけだとだめみたい。なるほどなー。

追記2

Linqでやってる海外の記事を見たので、上のコードに追加(4)。遅い。