LoginSignup
1
1

More than 5 years have passed since last update.

D言語LispでBase64デコーダを書いた(Oneliner Base64 decoder in Dlang) (通常の実装&解説も一応...)

Last updated at Posted at 2016-06-25

以前書いたBase64エンコーダ

D言語で書く "イケメンなコード" ~ Base64エンコーダをワンライナーで書く ~

のバグを直した時に、「あっ、まだデコーダを書いてないや」と思ったので書きました。
例によって型推論が効いているので引数の型は省略しました。
今回は記事の末尾にこれのもととなった、ヒューマンリーダブル(?)な通常の実装も載せておきます。

tinyBase64Decoder.d
import std.algorithm,
       std.string,
       std.ascii,
       std.range,
       std.conv;

R delegate(Args) Z(R, Args...)(R delegate(R delegate(Args), Args) f){
  return (Args args) => f(Z(f), args);
}

ubyte[] decode(string data) {
  return
    (convb =>
      (table =>
        (converted =>
          (cLen =>
            (quotients =>
              (qLen =>
                (qLen / 2).iota.map!(i =>
                  (buf =>
                    (bu2 =>
                      ((b0, b1) =>
                        (b0 << 4 ^ b1) & 0xff
                      )(bu2(buf[0]), bu2(buf[1]))
                    )((string bs) => bs.parse!ubyte(2))
                  )(quotients[(i * 2)..((i + 1) * 2)])
                ).array.to!(ubyte[])
              )(quotients.length)
            )(
              (cLen / 4).iota.map!(i =>
                converted[(i * 4)..((i + 1) * 4)]
              ).array
            )
          )(converted.length)
        )(
          data.filter!(x => x != '=').map!(e => 
            table[e.to!string]
          ).array.join
        )
      )((charset => 
          assocArray(
            zip(
              charset,
              charset.length.iota.map!(i =>
                (e =>
                  e.length == 6
                    ? e
                    : repeat("0", 6 - e.length).join ~ e
                )(convb(i, 2)))
              ))
          )((uppercase ~ lowercase ~ digits ~ "+/").split(string.init))
      )
    )((ulong N, int base) =>
      (convbM =>
        convbM(N, N, [], base)
      )(Z((string delegate(ulong, ulong, ulong[], int base) convbM, ulong N, ulong tmp, ulong[] stack, int base) =>
          tmp ? convbM(N, tmp / base, stack ~ (tmp % base), base) : stack.reverse.map!(e => e.to!string).join)));
}

unittest {
  string base    = "ABCDEFG",
         encoded = encode(base);

  assert(base == decode(encoded));
}

エンコードに比べて割りと楽でした(ビット演算で少しだけ躓いてしまったので頑張りたい)

では、ヒューマンリーダブルなコードを掲載しておきます(解説も簡単ながら付けておきました):

import std.algorithm,
       std.string,
       std.ascii,
       std.range,
       std.conv;

ubyte[] decodeX(string data) {
  //Base64のデコード用の変換テーブル(Base64文字列 -> 6bit)
  string[string] table =
    ((string delegate(ulong, int) convb) =>
      ((string[] charset) => 
        assocArray(
          zip(
            charset,
            charset.length.iota.map!((ulong i) =>
              ((string e) =>
                e.length == 6
                  ? e
                  : repeat("0", 6 - e.length).join ~ e
              )(convb(i, 2)))
          )
        )
      )((uppercase ~ lowercase ~ digits ~ "+/").split(string.init))
    )((ulong N, int base) =>
      (convbM =>
        convbM(N, N, [], base)
      )(Z((string delegate(ulong, ulong, ulong[], int base) convbM, ulong N, ulong tmp, ulong[] stack, int base) =>
          tmp ? convbM(N, tmp / base, stack ~ (tmp % base), base) : stack.reverse.map!(e => e.to!string).join)));


  //変換表に従い、Base64文字列を6bitに変換し、それを結合
  string converted =
    data.filter!(x => x != '=').map!(e => 
      table[e.to!string]
    ).array.join;

  //4bitずつにconvertedを切り分けるための配列
  string[] quotients;
  //デコードした結果を格納する
  ubyte[] decoded;

  //4文字(4bit)分取り出して、配列に追加する(切り出し)
  foreach (i; (converted.length / 4).iota) {
    string str = converted[(i * 4)..((i + 1) * 4)];
    quotients ~= str;
  }

  //隣接する4bit * 2の8bitからバイナリを復元
  foreach (i; (quotients.length / 2).iota) {
    //隣接する4bit * 2を取り出す
    string[] buf = quotients[(i * 2)..((i + 1) * 2)];

    //文字列化された数字を2進数として解釈し、10進数に変換する
    auto bu2 = ((string bs) => bs.parse!ubyte(2));
    ubyte b0 = bu2(buf[0]),
          b1 = bu2(buf[1]);

    //bit演算してバイナリに戻す
    decoded ~= (b0 << 4 ^ b1) & 0xff;
  }

  /*
    Base64のデコードは数多くの実装が存在するが(検索すると多くの人がすでに実装&解説をしています)、その多くは6bit * 4 = 24bit を 8bit * 3として扱うためにbit演算をしているような気がします。
    しかし、隣接する4bit * 2から計算する方法を用いれば、6bit * 4とするために`quotients` % 4(このコードのquotientsに相当するもの)を別に考える必要がなくなります(たぶん... 自信はないですけど)。
  */

  return decoded;
}
1
1
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
1
1