配列の活用(ポインタとレンジ)

Last-modified: 2013-03-14 (木) 12:12:10

ポインタ

C言語との連携のために必要
・配列←→ポインタの変換

int[] ary = [1,2,3];
int size = ary.length;
int* p = ary.ptr; // ptrは配列変数のプロパティ
ary = p[0..size];

注意点:C言語との間でポインタによる領域の受け渡しを行う場合はGCによる予期せぬ開放が悪さをしないかをプログラマ自身が把握し制御を行うこと
例:dupを使用して領域をコピーしたり、関数をぬけても無効化されない変数で持っておく、など

レンジ(Range)とは

・配列を真似たオブジェクト

PhobosにはRangeを受け取りRangeを返す関数が多数用意されている

注意:std.rangeには interface InputRage など、レンジ用のインターフェースが用意されているが、phobosで使用されているレンジは構造体ベースであり、このインターフェースを直接実装するものではない
もちろん、自分がレンジオブジェクトを作成するのなら、クラスベースで実装しても何ら問題はない

・遅延評価と効率
レンジの特徴として遅延評価や巨大な中間オブジェクトの生成がないことが挙げられる
遅延評価されるから、レンジの各要素は必要になるまで計算されない
また、遅延評価されるのと中間オブジェクトを生成しないから効率もよい

使用モジュール

よく使うのが下の3つ 以降のサンプルでは importの記述を省略する

import std.array;
import std.range;
import std.algorithm;

レンジから配列へ戻す

・最初におぼえるべきこと
配列を操作するつもりがレンジを受け取って困るケースが多々あるのでその対策として

int[] a = [9,8,7];
auto r = sort(a); // レンジを返す関数の例
a = array(r); // レンジからプリミティブな配列へ戻す(コピーを伴う)。D2.059以降では r.array(); と書ける

ただし、レンジは必要最低限の機能を備えている上パフォーマンスも十分に考慮されているので配列に戻さなくてもすんなり使えることが多い。
いちいち配列に戻したら負け。関数間で受け渡しする場合には auto型やテンプレートを多用する羽目になるので好みが分かれる

配列をレンジに

std.arrayをimportしていると配列をレンジとして扱える
標準ライブラリでレンジを扱うモジュールでは、std.arrayがimportされているので、配列をそのまま渡せばいい
つまり、std.arrayをimportしておけば、実用上は配列はレンジの一種だと考えても差し支えない

レンジの活用

配列・レンジを操作する標準関数が多数用意されている。
活用すればfor/foreachループによるお決まりのコードの多くを簡潔に書けるようになる

Filter

int[] ary = [1,2,3,4,5];
auto result = ary.filter!(n=>n%2==0)();
std.stdio.writeln(result);

Map

import std.string; // capitalize
string[] names = ["bill","john", "paul"];
auto result = names.map!(a=>a.capitalize())();
std.stdio.writeln(result);

Chain

要素の型が同じ複数のレンジを"結合"して一続きのレンジとして扱うことができる
int[] ary = [6,5,4];
auto result = chain([9,8,7],ary,[3,2,1]);
std.stdio.writeln(result); // BEFORE
ary.sort; // Chain中の一部のレンジが変更されたら
std.stdio.writeln(result); // AFTER

レンジの種類

入力レンジ・出力レンジ、前進レンジ・双方向レンジ、ランダムアクセスレンジなどの種類に分けることができる。
使用するレンジの機能が足りていない(双方向が要求されるのに前進しか持たないなど)場合はコンパイルエラーとなるためレンジの種類を確認して修正する必要がある。

レンジを受け取ったり返したりできる関数を自作

std.rangeに isInputRage などのテンプレート関数が用意されているので、それを用いて受け取ったレンジを判別する
関数テンプレートとして作成し、テンプレート制約にてこの判別を利用するのがよい
上にも書いた通り、interfaceとして用意されているレンジは通常使われないので注意
自分でレンジ型を定義したい場合は、empty,frontなどいくつかの必須プロパティを定義する必要があるので、詳しくはstd.rangeのドキュメントおよびforeachの関連箇所の記述を参照すること