資料URL

Range, 文字列, 連想配列


順番

  • type, qualifier
  • array, GC and foreach-loop
  • string, UTF
  • Associative Array(HashMap)
  • function(template)
  • Exception, scope(exit|failure|success), DbC, unittest
  • revisit function
  • revisit template-function
  • struct, union
  • class, interface,
  • re-revisit template

復習-1

Project Euler problem-1より。

10未満の自然数のうち, 3 もしくは 5 の倍数になっているものは 3, 5, 6, 9 の4つがあり, これらの合計は 23 になる.
同じようにして, 1000 未満の 3 か 5 の倍数になっている数字の合計を求めよ.

正解:233168


復習−1−2

以下の関数を使って解いてみる

  • iota(1, 1000)
  • filter!"...."
  • reduce!"...."
[1, 2, 3, 4].filter!"(a & 0x3) == 0"    -> [4]

[1, 2, 3, 4].reduce!"a * b"             -> 24

復習−2

Project Euler problem-2より。

フィボナッチ数列の項は前の2つの項の和である. 最初の2項を 1, 2 とすれば, 最初の10項は以下の通りである.
1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...
数列の項の値が400万以下の, 偶数値の項の総和を求めよ.

正解:4613732


復習−2−1

以下の関数を使って解いてみる

  • recurrence!"...."
  • until!"...."
  • filter!"...."
recurrence!"a[n-1] * (n+1)"(1).until!"a > b"(1000)    -> [1, 2, 6, 24, 120, 720]

Range

  • リストの抽象的表現
auto popHead(R)(ref R r)
{
    enforce(!r.empty);  // 空か?

    auto e = r.front;   // 先頭要素
    r.popFront();       // 先頭を消す
    return e;
}

配列とRange

import std.array;

void main()
{
    auto arr = [1, 2, 3, 4];

    writeln(arr.empty);
    writeln(arr.front);
    arr.popFront();
    writeln(arr);
    writeln(arr.empty);
    writeln(arr.front);
    arr = [];
    writeln(arr.empty);
}

Rangeを作ってみる

  • 0, 1, 2, 3, ...
  • 無限レンジ
import std.stdio, std.range;

struct InfSeq{
    enum bool empty = false;    // 終わりなし
    int front() @property { return _i; }
    void popFront() { ++_i; }
    private int _i;
}


void main()
{
    writeln(InfSeq().take(100));
}

準備

WindowsでUTF文字列を表示させる

  • ターミナルの対応
  • 関数のハイジャック

ターミナルの対応

  • chcp 65001
  • フォントを変える

関数ハイジャック

import std.algorithm;
import std.conv, std.format, std.string;
import std.windows.charset;
import std.stdio;

version(Windows)
{
  template writeImpl(alias f, string ln)
  { void writeImpl(T...)(auto ref T args)
    { printf(f(forward!args).toMBSz);
      printf(ln); }}

  alias write = writeImpl!(text, "");
  alias writef = writeImpl!(format, "");
  alias writeln = writeImpl!(text, "\r\n");
  alias writefln = writeImpl!(format, "\r\n");
}

void main()
{
    writeln("ああああ");
    writef("嗚呼唖々");
}

UTF-32

  • 一文字 == 4byte固定
import std.stdio;

void main()
{
    // dchar, dstring が UTF-32
    dstring[] ss = ["a", "あ", "阿", "\U0001F363"];

     foreach(s; ss)
        writefln(`%(%s%) => %(%02X%)`, [s], (cast(immutable(ubyte)[])s).dup.reverse);
}

UTF-16

  • 基本:1文字 == 2byte
  • サロゲートペア(2byte*2で一文字)
import std.stdio;

void main()
{
    // wchar, wstring が UTF-32
    wstring[] ss = ["a", "あ", "阿", "\U0001F363"];

     foreach(s; ss)
        writefln(`%(%s%) => %(%02X%)`, [s], (cast(immutable(ubyte)[])s).dup.reverse);
}

UTF-8

  • 1~6 byteまでの可変長
import std.stdio;

void main()
{
    // char, string が UTF-32
    string[] ss = ["a", "あ", "阿", "\U0001F363"];

     foreach(s; ss)
        writefln(`%(%s%) => %(%02X%)`, [s], (cast(immutable(ubyte)[])s).dup.reverse);
}

問題

  • 文字列の先頭文字を得るには?
  • ヒント
    • immutable(char|wchar|dchar)[]
auto getHead(S)(S str)
{
    return str[0];
}

UTF-32の場合

  • 1文字は4byte固定
import std.stdio;

void main()
{
    dstring str = "トヨッキー";
    writeln(str[0]);
}

UTF-8の場合

import std.stdio;

void main()
{
    string str = "トヨッキー";
    writeln(str[0]);    // 文字化け
}

UTF-8:可変長

  • str[0]は先頭の1byteだけ
  • UTF-32に変換してしまう
  • Range
import std.array, std.stdio, std.utf;

void main()
{
    string str = "トヨッキー";

    // UTF-32に変換して、先頭文字だけ返す
    writeln(str.toUTF32()[0]);  // dchar

    // 先頭文字をUTF-32にして返す
    writeln(str.front);         // dchar
}

UTF-16の場合

  • 基本的には1文字は2byte
  • サロゲートペア:4byteで1文字

    • めったにないけど。
  • UTF-8と同じように処理


文字列のまとめ

  • UTF-32に一度変換してしまうのが楽
  • 文字列のRangeは、UTF-32のシーケンス
void main()
{
    // 推論させると、immutable(char)になる
    // dcharにしたいなら、明示的に書く必要がある
    foreach(dchar e; "あああ")
        writeln(e);
}

文字列の問題


入力

標準入力から一行取得する。

import std.stdio, std.range,
       std.conv, std.string;

void main()
{
    immutable n = readln()  // 1行
                 .chomp()   // 末尾\r\n削除
                 .to!uint;  // 変換

    // 入力の各行に対してループ
    foreach(line; stdin.byLine)
        writeln(line.take(n));
}

連想配列

void main()
{
    int[string] aa = ["1" : 1, "2" : 3];

    aa["4"] = 5;
    if(auto p = "3" in aa)
        writeln(*p);    // aa["3"]
    else
        writeln("aa does not have 3");

    writeln(aa.length);

    aa.remove("4");
    assert("4" !in aa);
}

foreach

void main()
{
    auto madoMagi = ["mado": 1, "homu": 2,
                     "saya": 3, "anko": 4, "mami": 5];

    foreach(k, v; madoMagi)
        writefln("%s : %s", k, v);

    writeln();

    foreach(k, ref v; madoMagi)
        v = 100;

    writefln("%-(%s : %s%|\n%)", madoMagi);
}

その他の列挙方法

  • aa.byKey:キーのレンジ
  • aa.keys:キーの配列(新たに確保)
  • aa.byValue:値のレンジ
  • aa.values:値の配列(新たに確保)

ユーザー定義のキー型

  • デフォルトではバイナリから算出
  • ユーザーがカスタマイズ可能
/// test.d
import std.stdio;

struct MyKey
{
    int a;
    int b;  // これを無視したい

    hash_t toHash() const nothrow @safe { return a; }
    bool opEquals(ref const MyKey rhs) const
    { return this.a == rhs.a; }
}

void main()
{
    int[MyKey] aa;

    aa[MyKey(1, 2)] = 12;
    aa[MyKey(2, 3)] = 3;
    aa[MyKey(3, 4)] = 4;

    aa[MyKey(1, 4)] = 8;   // rewrite

    writeln(aa);
}

問題