政略結婚じゃないよ。

コンピュータや音楽のことなど

全体表示

[ リスト | 詳細 ]

記事検索
検索

CoffeeScript はじめました

windows環境でも問題なく使えるようなのでインストールしてみた。
node.js 0.6.19
CoffeeScrip 1.3.3

sprintf() 機能拡張 (2)

String#printf() を実装して sprintf() はそれを利用するラッパー関数に変更した。
さらに String#vprintf()、RegExp#printf()、
コンソール出力用に printf()、vprintf() も用意した。
以下のような記述ができるようになる。
3行目は4行目と等価。バックスラッシュを余分に重ねずにすっきり短く書ける。
"%04d".printf(10) => "0010"
"%s %d".vprintf(["foo",42]) => "foo 42"
x="foo", /\b%s\b/i.printf(x)           => /\bfoo\b/.i
x="foo", new RegExp("\\b"+x+"\\b","i") => /\bfoo\b/.i
さらに以下のように正規表現を複数に分けて書くことができるように機能追加。
(2012/6/15 19:55)
/%s|%s|%s/g.printf(
  /\bfoo\b/,
  /\bbar\b/,
  /\b42\b/) => /\bfoo\b|\bbar\b|\b42\b/g
実行環境は例によって IE8
まずは短いラッパー関数群
function String.prototype.vprintf(array) {
  return this.printf.apply(this, array);
}

function sprintf(format) {
  return format.vprintf(Array.prototype.slice.call(arguments, 1));
}

function RegExp.prototype.printf() {
  return new RegExp(
    this.source.vprintf(arguments),
    (this.ignoreCase ? "i" : "") +
    (this.global     ? "g" : "") +
    (this.multiline  ? "m" : ""));
}
コンソール用
(function() {
  if (typeof WSH == "undefined") return;
  printf = function(format) {
    WSH.StdOut.Write(format.vprintf(Array.prototype.slice.call(arguments, 1)));
  };
  vprintf = function(format, values) {
    WSH.StdOut.Write(format.vprintf(values));
  };
})();
そして以下が本命 String#printf() の実装。
function String.prototype.printf() {
  var args = arguments; current = 0;
  return this.replace(
    /%(?:([1-9]\d*)\$)?([-+ 0'])?(\d+|\*)?(?:\.(\d+|\*))?([sdfxXobc%])/g,
    function(match, index, flag, width, prec, type) {
      var arg, value, formatted, sign, fillLen, filler;
      if (type == "%") return "%";
      if (index) {
        if (width == "*" || prec == "*")
          error("'*' after argument index");
        index = +index - 1;
      } else {
        if ((width == "*" && isNaN(width = args[current++]))
        ||  (prec  == "*" && isNaN(prec  = args[current++])))
          error("not a number for '*'");
        index = current++;
      }
      if ((arg = args[index]) === undefined)
        error("too few actual arguments");
      if ((type != "s") && (isNaN(arg) || !/\d/.test(arg)))
        error("not a number for '%" + type + "'");

      formatted = type == "s" ? ((arg = (arg instanceof RegExp ? arg.source
                                                               : arg + "")),
                                 (!prec ? arg : arg.slice(0, prec))) :
                  type == "f" ? (!prec ? (value = +arg) + ""
                                       : (value = +arg).toFixed(prec)) :
                  type == "d" ? (value = ~~arg) + "" :
                  type == "x" ? (value = ~~arg).toString(16) :
                  type == "X" ? (value = ~~arg).toString(16).toUpperCase() :
                  type == "o" ? (value = ~~arg).toString( 8) :
                  type == "b" ? (value = ~~arg).toString( 2) :
                  type == "c" ? String.fromCharCode(~~arg) :
                                null; // do not reach here

      if (flag == "'" && (type == "d" || type == "f"))
        formatted = formatted.replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,");
      sign = flag == " " ? (value > 0 ? " " : value == 0 ? " " : "") :
             flag == "+" ? (value > 0 ? "+" : value == 0 ? " " : "") : "";
      filler = (!width || ((fillLen = width - (sign + formatted).length) < 1))
               ? "" : new Array(fillLen + 1)
                      .join(flag == "0" && value >= 0 ? "0" : " ");
      return flag == "-" ? sign + formatted + filler
                         : filler + sign + formatted;
    }
  );
  function error(x) { throw new Error("*** error : " + x + " in ?printf()"); }
}

sprintf() 機能追加

以下の機能を追加した。
1.最小フィールド幅、精度に * を指定できるようにした
2.フラグに + と 空白 を指定できるようにした
3.エラー発生時に例外を発生させるようにした

機能追加分の動作チェックは下記のとおり。
sprintf("%0*d %s", 3, 1, "foo")     => "001 foo"
sprintf("%*.*f %s", 5, 2, 1, "foo") => " 1.00 foo"

sprintf("%+d",42)  => "+42"
sprintf("% d",42)  => " 42"
sprintf("%+d",0)   => " 0"
sprintf("% d",0)   => " 0"
sprintf("%+d",-42) => "-42"
sprintf("% d",-42) => "-42"
実装は下記のとおり。
function sprintf(fmt) {
  // 変換指定
  //     %[引数インデックス$][フラグ][最小フィールド幅][.精度]変換指定子
  // フラグ
  //     -     左詰
  //     +     常に符号を表示
  //     空白  正の数または0のとき符号の代わりに空白を表示
  //     0     ゼロパディング
  //     '     桁区切カンマ
  // 最小フィールド幅
  //     *     対応する引数を数値として使用する(引数インデックスと併用不可)
  // 精度
  //     *     対応する引数を数値として使用する(引数インデックスと併用不可)
  // 型変換指定子
  //     d     十進整数
  //     x     十六進整数(小文字)
  //     X     十六進整数(大文字)
  //     o     八進整数
  //     b     二進整数
  //     f     小数形式浮動小数点数
  //     c     文字
  //     s     文字列
  //     %     "%"自身
  var args = arguments; current = 0;
  return fmt.replace(
    /%(?:([1-9]\d*)\$)?([-+ 0'])?(\d+|\*)?(?:\.(\d+|\*))?([sdfxXobc%])/g,
    function(match, index, flag, width, prec, type) {
      var arg, value, formatted, fillLen, filler, err = 0;
      if (type === "%") return "%";
      if (index) {
        if (width === "*" || prec === "*") err = 1;
      } else {
        if (width === "*" && isNaN(width = args[++current])) err = 2;
        if (prec  === "*" && isNaN(prec  = args[++current])) err = 2;
        index = ++current;
      }
      if (!err && ((arg = args[index]) === undefined)) err = 3;
      if (!err && (type != "s") && (isNaN(arg) || !/\d/.test(arg))) err = 4;

      if (err) throw new Error(
        { 1 : "*** error : '*' with argument index in sprintf()"
        , 2 : "*** error : not a number for '*' in sprintf()"
        , 3 : "*** error : too few arguments in sprintf()"
        , 4 : "*** error : not a number for '%d' in sprintf()"
        }[err]
      );

      switch (type) {
      case "s":
        formatted = !prec ? arg + "" : (arg + "").slice(0, prec);
        break;
      case "f":
        value = +arg;
        formatted = prec ? value.toFixed(prec) : value + "";
        if (flag === "'")
          formatted = formatted.replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,");
        break;
      case "d":
        value = ~~arg;
        formatted = value + "";
        if (flag === "'")
          formatted = formatted.replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,");
        break;
      case "x": formatted = (value = ~~arg).toString(16); break;
      case "X": formatted = (value = ~~arg).toString(16).toUpperCase(); break;
      case "o": formatted = (value = ~~arg).toString( 8); break;
      case "b": formatted = (value = ~~arg).toString( 2); break;
      case "c": formatted = String.fromCharCode(~~arg); break;
      }
      if (value !== undefined)
        formatted = (flag === "+" ? (value >  0 ? "+" :
                                     value == 0 ? " " : "") :
                     flag === " " ? (value >= 0 ? " " : "") : "") + formatted;
      fillLen = width - formatted.length;
      filler = (!width || (fillLen < 1))
               ? "" : new Array(fillLen + 1).join(flag === "0" ? "0" : " ");
      return flag === "-" ? formatted + filler
                          : filler + formatted;
    }
  );
}
大胆にもグローバル関数として定義する。
以下のことができるようにする。
sprintf("%s","foo")     => "foo"      // 文字列
sprintf("%.2s","foo")   => "fo"       //   文字数指定
sprintf("%6s","foo")    => "   foo"   //   右詰
sprintf("%-6s","foo")   => "foo   "   //   左詰
sprintf("%6.2s","foo")  => "    fo"   //   文字数指定右詰
sprintf("%-6.2s","foo") => "fo    "   //   文字数指定左詰

sprintf("%d",42)        => "42"       // 十進整数
sprintf("%4d",42)       => "  42"     //   右詰
sprintf("%-4d",42)      => "42  "     //   左詰
sprintf("%04d",42)      => "0042"     //   ゼロパディング
sprintf("%d",-42)       => "-42"      //   負の数
sprintf("%4d",-42)      => " -42"     //   負の数右詰
sprintf("%-4d",-42)     => "-42 "     //   負の数左詰
sprintf("%2d",123)      => "123"      //   指定幅を超える数値

sprintf("%f",12.5)      => "12.5"     // 小数形式浮動小数点数
sprintf("%.0f",12.5)    => "13"       //   小数点以下四捨五入
sprintf("%.2f",12.555)  => "12.56"    //   小数点以下指定(指定以下四捨五入)
sprintf("%8.2f",12.5)   => "   12.50" //   左詰
sprintf("%08.2f",12.5)  => "00012.50" //   ゼロパディング

sprintf("%x",42)        => "2a"       // 十六進整数小文字
sprintf("%X",42)        => "2A"       // 十六進整数大文字
sprintf("%04x",42)      => "002a"     // 十六進整数小文字ゼロパディング
sprintf("%04X",42)      => "002A"     // 十六進整数大文字ゼロパディング
sprintf("%o",42)        => "52"       // 八進整数
sprintf("%04o",42)      => "0052"     // 八進整数ゼロパディング
sprintf("%b",42)        => "101010"   // 二進整数
sprintf("%08b",42)      => "00101010" // 二進整数ゼロパディング

sprintf("%c",65)        => "A"        // 文字

sprintf("%% %s", "xxx") => "% xxx"    // %% は % に

sprintf("%s %d %x %c","foo",42,42,65) => "foo 42 2a A" // 複数項目

sprintf("x=%2$d;y=%1$d",10,20) => "x=20;y=10" // %数値$ 引数インデックス指定

sprintf("%d")       => "%1$???" // 引数の数が足りないとき
sprintf("%d","42a") => "42a???" // 数値に変換できないとき
実装は以下のとおり。
この関数の定義だけで完結するように修正 (2012/6/9 23:27)
function sprintf(fmt) {
  // 変換指定
  //     %[引数インデックス$][フラグ][最小フィールド幅][.精度]変換指定子
  // フラグ
  //     -   左詰
  //     0   ゼロパディング
  // 型変換指定子
  //     d   十進整数
  //     x   十六進整数(小文字)
  //     X   十六進整数(大文字)
  //     o   八進整数
  //     b   二進整数
  //     f   小数形式浮動小数点数
  //     c   文字
  //     s   文字列
  //     %   "%"自身
  var args, i;
  args = arguments; i = 0;
  return fmt.replace(
    /%(?:([1-9]\d*)\$)?([-0])?([1-9]\d*)?(?:\.(\d+))?([sdfxXobc%])/g,
    function(match, index, flag, width, prec, type) {
      var v, x, filler;
      if (type == "%") return "%";
      if (!index) index = ++i;
      v = args[index];
      x = v === undefined ? "%" + index + "$???" :
          type == "s"     ? (!prec ? (v + "") : (v + "").slice(0, prec)) :
          isNaN(v)        ? v + "???" :
          type == "d"     ? ~~v + "" :
          type == "f"     ? (prec ? (+v).toFixed(prec) : +v) :
          type == "x"     ? (~~v).toString(16) :
          type == "X"     ? (~~v).toString(16).toUpperCase() :
          type == "o"     ? (~~v).toString(8) :
          type == "b"     ? (~~v).toString(2) :
          type == "c"     ? String.fromCharCode(v) :
                            "";
      filler = (!width || (width - x.length < 1))
               ? "" : new Array(width - x.length + 1)
                      .join(flag === "0" ? "0" : " ");
      return flag === "-" ? x + filler
                          : filler + x;
    });
}

文字列 s が "abc"(小文字のみ) を含んでいるかのチェック

if (s.indexOf("abc") >= 0) ...
目にすることが多いコード。
不等号 >= がなんとなくいや
if (s.indexOf("abc") > -1) ...
上記と同じロジック。
マジックナンバー -1 がいや
if (s.match(/abc/)) ...
素直に内容を表現している。
下記の RegExp#test() より重い
if (/abc/.test(s)) ...
一番短い
s が後になるのがちょっといや

文字列 s が "abc"(小文字のみ) で始まっているかのチェック

if (s.indexOf("abc") == 0) ...
if (s.slice(0, 3) == "abc") ...
文字位置が特定できるので String#slice() が使える
文字数の 3 を引数で指定するのがいや
if (s.match(/^abc/)) ...
正規表現の ^ を使うと先頭にマッチを指定できる
if (/^abc/.test(s)) ...
一番短い
s が後になるのが... 慣れましょう!!

文字列 s が "abc"(大文字小文字問わず) で始まっているかのチェック

if (s.toLowerCase().indexOf("abc") == 0) ...
文字列全体を小文字に変換して比較
コードがやけに長くなる
if (s.slice(0, 3).toLowerCase() == "abc") ...
これも長い
if (s.match(/^abc/i)) ...
正規表現の最後に i をつけると大文字小文字を同一視して検索できる
if (/^abc/i.test(s)) ...
一番短い
s が後... 慣れましたよね!?


まとめ

--- リテラルで検索する場合
RegExp#test() を使って
/^abc/i.test(s)
のようにするのが短くかけてパフォーマンスもよいのでおすすめ。

--- 変数の内容で検索する場合
内容に正規表現の特殊機能が含まれている可能性があるので
String#indexOf() を使用する。
0 や -1 との比較を避けるためには下記のように String#contains() を実装する

function String.prototype.contains(x) {
return this.indexOf(x) > -1;
}

(実装前) if (s.indexOf(x) > -1) ...
(実装後) if (s.contains(x)) ...


.


プライバシー -  利用規約 -  メディアステートメント -  ガイドライン -  順守事項 -  ご意見・ご要望 -  ヘルプ・お問い合わせ

Copyright (C) 2019 Yahoo Japan Corporation. All Rights Reserved.

みんなの更新記事