CLOVER🍀

That was when it all began.

Shift_JIS → Windows-31Jマッピング変換コード

近々仕事で書くことになりそうなので、久しぶりにShift_JISとWindows-31Jのマッピング変換コードでも。

public class CodeReviser {
    public static void main(String[] args) {
        String target = args[0];
        System.out.println("Before [" + target + "] Hex[" + toHexString(target) + "]");
        String converted = reviseCode(target);
        System.out.println("After [" + converted + "] Hex[" + toHexString(converted) + "]");
    }

    public static String reviseCode(String target) {
        StringBuilder builder = new StringBuilder();

        for (char c : target.toCharArray()) {
            switch (c) {
            case '\u2014':  // EM DASH
                builder.append('\u2015');  // HORIZONTAL BAR
                break;
            case '\u301C':  // WAVE DASH
                builder.append('\uFF5E');
                break;
            case '\u2016':  // DOUBLE VERTICAL LINE
                builder.append('\u2225');
                break;
            case '\u2212':  // MINUS SIGN
                builder.append('\uFF0D');
                break;
            case '\u00A2':  // CENT SIGN
                builder.append('\uFFE0');
                break;
            case '\u00A3':  // POUND SIGN
                builder.append('\uFFE1');
                break;
            case '\u00AC':  // NOT SIGN
                builder.append('\uFFE2');
                break;
            default:
                builder.append(c);
                break;
            }
        }

        return builder.toString();
    }

    public static String toHexString(String target) {
        StringBuilder builder = new StringBuilder();

        for (char c : target.toCharArray()) {
            builder.append("[\\u").append(Integer.toHexString(c)).append("]");
        }

        return builder.toString();
    }
}

テスト。実行環境はCentOSなので、ターミナルの入力コードはShift_JISマッピングで動きますよ。

$ javac CodeReviser.java 
$ java CodeReviser "あ〜‖−¢£¬い"
Before [あ〜‖−¢£¬い] Hex[[\u3042][\u301c][\u2016][\u2212][\ua2][\ua3][\uac][\u3044]]
After [あ〜‖−¢£¬い] Hex[[\u3042][\uff5e][\u2225][\uff0d][\uffe0][\uffe1][\uffe2][\u3044]]

よく見かける決まりきったコードですが、もっと短くならん?ってことで、Mapを使うように変更。

import java.util.Collections;
import java.util.Map;
import java.util.HashMap;

public class CodeReviser {
    private static final Map<Character, Character> CODE_MAP;

    static {
        Map<Character, Character> map = new HashMap<Character, Character>();
        map.put('\u2014', '\u2015');  // EM DASH
        map.put('\u301C', '\uFF5E');  // WAVE DASH
        map.put('\u2016', '\u2225');  // DOUBLE VERTICAL LINE
        map.put('\u2212', '\uFF0D');  // MINUS SIGN
        map.put('\u00A2', '\uFFE0');  // CENT SIGN
        map.put('\u00A3', '\uFFE1');  // POUND SIGN
        map.put('\u00AC', '\uFFE2');  // NOT SIGN
        CODE_MAP = Collections.unmodifiableMap(map);
    }

    public static void main(String[] args) {
        …省略…
    }

    public static String reviseCodeUseMap(String target) {
        StringBuilder builder = new StringBuilder();

        for (Character c : target.toCharArray()) {
            Character converted = CODE_MAP.get(c);
            if (converted != null) {
                builder.append(converted);
            } else {
                builder.append(c);
            }
        }

        return builder.toString();
    }

    public static String reviseCode(String target) {
        …省略…
    }

    public static String toHexString(String target) {
        …省略…
    }
}

mainはこう変更して…

    public static void main(String[] args) {
        String target = args[0];
        System.out.println("Before [" + target + "] Hex[" + toHexString(target) + "]");
        String converted = reviseCode(target);
        System.out.println("After [" + converted + "] Hex[" + toHexString(converted) + "]");

        target = args[0];
        System.out.println("Before [" + target + "] Hex[" + toHexString(target) + "]");
        converted = reviseCodeUseMap(target);
        System.out.println("After [" + converted + "] Hex[" + toHexString(converted) + "]");
    }

稼働確認。

$ java CodeReviser "あ〜‖−¢£¬い"
Before [あ〜‖−¢£¬い] Hex[[\u3042][\u301c][\u2016][\u2212][\ua2][\ua3][\uac][\u3044]]
After [あ〜‖−¢£¬い] Hex[[\u3042][\uff5e][\u2225][\uff0d][\uffe0][\uffe1][\uffe2][\u3044]]
Before [あ〜‖−¢£¬い] Hex[[\u3042][\u301c][\u2016][\u2212][\ua2][\ua3][\uac][\u3044]]
After [あ〜‖−¢£¬い] Hex[[\u3042][\uff5e][\u2225][\uff0d][\uffe0][\uffe1][\uffe2][\u3044]]

同じ結果に。

が、よーく見ると最初のメソッドと変更後のメソッドでは、ちょっと内容が異なります。具体的には、
ここと

    public static String reviseCode(String target) {
        StringBuilder builder = new StringBuilder();

        for (char c : target.toCharArray()) {

ここ

    public static String reviseCodeUseMap(String target) {
        StringBuilder builder = new StringBuilder();

        for (Character c : target.toCharArray()) {
            Character converted = CODE_MAP.get(c);

後者はcharではなく、Characterになっております。この辺は、Javaのプリミティブはオブジェクトではないからですよねぇ。知らない人からすると、ちょっと混乱しそうな部分だと思います。というか、繰り返しのコードこそ減りましたが、実際にはあんまり短くなってない?

では、今度はScalaで書き直してみます。まずは最初のJavaを愚直に移植。

object CodeReviser {
  def main(args: Array[String]): Unit = {
    val target = args(0)
    println("Before [%s] Hex[%s]".format(target, toHexString(target)))
    var converted = reviseCode(target)
    println("After [%s] Hex[%s]".format(converted, toHexString(converted)))
  }

  def reviseCode(target: String): String = {
    val builder = new StringBuilder

    for (c <- target.toCharArray) {
    // for (c <- target) { // コレでもOK
      c match {
        case '\u2014' => builder += '\u2015'  // EM DASH
        case '\u301C' => builder += '\uFF5E'  // WAVE DASH
        case '\u2016' => builder += '\u2225'  // DOUBLE VERTICAL LINE
        case '\u2212' => builder += '\uFF0D'  // MINUS SIGN
        case '\u00A2' => builder += '\uFFE0'  // CENT SIGN
        case '\u00A3' => builder += '\uFFE1'  // POUND SIGN
        case '\u00AC' => builder += '\uFFE2'  // NOT SIGN
        case _ => builder += c
      }
    }

    builder.toString
  }

  def toHexString(target: String): String = {
    val builder = new StringBuilder

    for (c <- target.toCharArray) {
      builder append "[\\u"
      builder append Integer.toHexString(c)
      builder append "]"
    }

    builder.toString
  }
}

実行。

$ fsc CodeReviser.scala 
$ scala CodeReviser "あ〜‖−¢£¬い"
Before [あ〜‖−¢£¬い] Hex[[\u3042][\u301c][\u2016][\u2212][\ua2][\ua3][\uac][\u3044]]
After [あ〜‖−¢£¬い] Hex[[\u3042][\uff5e][\u2225][\uff0d][\uffe0][\uffe1][\uffe2][\u3044]]

うん、同じ結果。この時点で既にJava版よりかなり短いのですが、せっかくScalaを使っているのでもうちょっと宣言的に書き直してみましょう。

object CodeReviser {
  def main(args: Array[String]): Unit = {
    …省略…
  }

  def reviseCodeUseMapFunc(target: String): String = target map { c =>
    c match {
      case '\u2014' => '\u2015'  // EM DASH
      case '\u301C' => '\uFF5E'  // WAVE DASH
      case '\u2016' => '\u2225'  // DOUBLE VERTICAL LINE
      case '\u2212' => '\uFF0D'  // MINUS SIGN
      case '\u00A2' => '\uFFE0'  // CENT SIGN
      case '\u00A3' => '\uFFE1'  // POUND SIGN
      case '\u00AC' => '\uFFE2'  // NOT SIGN
      case _ => c
    }
  }

  def reviseCode(target: String): String = {
    …省略…
  }

  def toHexString(target: String): String = {
    …省略…
}

一気に短くなりました。Stringにmapなんてメソッドあったか?と思われた方は、Scalaのimplicit conversion(暗黙の型変換)について調べてみてください。

実行結果は、もちろん同じです。

$ fsc CodeReviser.scala 
$ scala CodeReviser "あ〜‖−¢£¬い"
Before [あ〜‖−¢£¬い] Hex[[\u3042][\u301c][\u2016][\u2212][\ua2][\ua3][\uac][\u3044]]
After [あ〜‖−¢£¬い] Hex[[\u3042][\uff5e][\u2225][\uff0d][\uffe0][\uffe1][\uffe2][\u3044]]
Before [あ〜‖−¢£¬い] Hex[[\u3042][\u301c][\u2016][\u2212][\ua2][\ua3][\uac][\u3044]]
After [あ〜‖−¢£¬い] Hex[[\u3042][\uff5e][\u2225][\uff0d][\uffe0][\uffe1][\uffe2][\u3044]]

最後、このバージョンからMapを使ったものに変更してみましょう。先のパターンよりコードはそんなに短くなりませんが、JavaとはちょっとMapの使い方が異なるので面白いですよ。

object CodeReviser {
  def main(args: Array[String]): Unit = {
    val target = args(0)
    println("Before [%s] Hex[%s]".format(target, toHexString(target)))
    var converted = reviseCode(target)
    println("After [%s] Hex[%s]".format(converted, toHexString(converted)))

    println("Before [%s] Hex[%s]".format(target, toHexString(target)))
    converted = reviseCodeUseMapFunc(target)
    println("After [%s] Hex[%s]".format(converted, toHexString(converted)))

    println("Before [%s] Hex[%s]".format(target, toHexString(target)))
    converted = reviseCodeUseMap(target)
    println("After [%s] Hex[%s]".format(converted, toHexString(converted)))
  }

  val CODE_MAP: Map[Char, Char] = Map(
    '\u2014' -> '\u2015', // EM DASH
    '\u301C' -> '\uFF5E', // WAVE DASH
    '\u2016' -> '\u2225', // DOUBLE VERTICAL LINE
    '\u2212' -> '\uFF0D', // MINUS SIGN
    '\u00A2' -> '\uFFE0', // CENT SIGN
    '\u00A3' -> '\uFFE1', // POUND SIGN
    '\u00AC' -> '\uFFE2'  // NOT SIGN
  )

  def reviseCodeUseMap(target: String): String =
    target map { c => CODE_MAP.getOrElse(c, c) }

  def reviseCodeUseMapFunc(target: String): String = target map { c =>
    …省略…
  }

  def reviseCode(target: String): String = {
    …省略…
  }

  def toHexString(target: String): String = {
    …省略…
  }
}

MapのgetOrElseメソッドを使うことで、Javaのように存在チェックが入らないことがポイントです。

とまあここまで書いておいてなんですが、実際に仕事で書かなくちゃいけないのは、たぶんPHPなんですけどね!!