D-programming-ranges

提供:Dev Guides
移動先:案内検索

Dプログラミング-範囲

範囲は、要素アクセスの抽象化です。 この抽象化により、多数のコンテナタイプで多数のアルゴリズムを使用できます。 範囲は、コンテナの実装方法ではなく、コンテナ要素へのアクセス方法を強調します。 範囲は、型がメンバー関数の特定のセットを定義するかどうかに基づく非常に単純な概念です。

範囲はDの不可欠な部分です。 Dのスライスは偶然、最も強力な範囲であるRandomAccessRangeの実装であり、Phobosには多くの範囲機能があります。 多くのPhobosアルゴリズムは一時的な範囲オブジェクトを返します。 たとえば、filter()は、次のコードで10より大きい要素を選択し、実際には配列ではなく範囲オブジェクトを返します。

番号範囲

番号範囲は非常に一般的に使用され、これらの番号範囲はint型です。 番号範囲のいくつかの例を以下に示します-

//Example 1
foreach (value; 3..7)

//Example 2
int[] slice = array[5..10];

フォボス山脈

構造体とクラスインターフェイスに関連する範囲は、phobosの範囲です。 Phobosは、D言語コンパイラに付属する公式のランタイムおよび標準ライブラリです。

を含む範囲のさまざまなタイプがあります-

  • 入力範囲
  • ForwardRange
  • BidirectionalRange
  • RandomAccessRange
  • 出力範囲

入力範囲

最も単純な範囲は入力範囲です。 他の範囲は、それらが基づく範囲に加えてより多くの要件をもたらします。 InputRangeが必要とする3つの機能があります-

  • -範囲が空かどうかを指定します。範囲が空と見なされる場合、trueを返す必要があります。そうでない場合はfalse。
  • front -範囲の先頭にある要素へのアクセスを提供します。
  • * popFront()*-最初の要素を削除することにより、最初から範囲を短くします。

import std.stdio;
import std.string;

struct Student {
   string name;
   int number;

   string toString() const {
      return format("%s(%s)", name, number);
   }
}

struct School {
   Student[] students;
}
struct StudentRange {
   Student[] students;

   this(School school) {
      this.students = school.students;
   }
   @property bool empty() const {
      return students.length == 0;
   }
   @property ref Student front() {
      return students[0];
   }
   void popFront() {
      students = students[1 .. $];
   }
}

void main() {
   auto school = School([ Student("Raj", 1), Student("John", 2), Student("Ram", 3)]);
   auto range = StudentRange(school);
   writeln(range);

   writeln(school.students.length);

   writeln(range.front);

   range.popFront;

   writeln(range.empty);
   writeln(range);
}

上記のコードをコンパイルして実行すると、次の結果が生成されます-

[Raj(1), John(2), Ram(3)]
3
Raj(1)
false
[John(2), Ram(3)]

ForwardRange

ForwardRangeはさらに、InputRangeの他の3つの関数からのsaveメンバー関数部分を必要とし、save関数が呼び出されたときに範囲のコピーを返します。

import std.array;
import std.stdio;
import std.string;
import std.range;

struct FibonacciSeries {
   int first = 0;
   int second = 1;
   enum empty = false;  // infinite range

   @property int front() const {
      return first;
   }
   void popFront() {
      int third = first + second;
      first = second;
      second = third;
   }
   @property FibonacciSeries save() const {
      return this;
   }
}

void report(T)(const dchar[] title, const ref T range) {
   writefln("%s: %s", title, range.take(5));
}

void main() {
   auto range = FibonacciSeries();
   report("Original range", range);

   range.popFrontN(2);
   report("After removing two elements", range);

   auto theCopy = range.save;
   report("The copy", theCopy);

   range.popFrontN(3);
   report("After removing three more elements", range);
   report("The copy", theCopy);
}

上記のコードをコンパイルして実行すると、次の結果が生成されます-

Original range: [0, 1, 1, 2, 3]
After removing two elements: [1, 2, 3, 5, 8]
The copy: [1, 2, 3, 5, 8]
After removing three more elements: [5, 8, 13, 21, 34]
The copy: [1, 2, 3, 5, 8]

BidirectionalRange

BidirectionalRangeは、ForwardRangeのメンバー関数に対して2つのメンバー関数を追加で提供します。 frontと同様のback関数は、範囲の最後の要素へのアクセスを提供します。 popBack関数はpopFront関数に似ており、範囲から最後の要素を削除します。

import std.array;
import std.stdio;
import std.string;

struct Reversed {
   int[] range;

   this(int[] range) {
      this.range = range;
   }
   @property bool empty() const {
      return range.empty;
   }
   @property int front() const {
      return range.back; // reverse
   }
   @property int back() const {
      return range.front;//reverse
   }
   void popFront() {
      range.popBack();
   }
   void popBack() {
      range.popFront();
   }
}

void main() {
   writeln(Reversed([ 1, 2, 3]));
}

上記のコードをコンパイルして実行すると、次の結果が生成されます-

[3, 2, 1]

無限のRandomAccessRange

ForwardRangeと比較すると、opIndex()がさらに必要です。 また、空の関数の値は、コンパイル時にfalseとして認識されます。 簡単な例は、正方形の範囲で説明されています。

import std.array;
import std.stdio;
import std.string;
import std.range;
import std.algorithm;

class SquaresRange {
   int first;
   this(int first = 0) {
      this.first = first;
   }
   enum empty = false;
   @property int front() const {
      return opIndex(0);
   }
   void popFront() {
      ++first;
   }
   @property SquaresRange save() const {
      return new SquaresRange(first);
   }
   int opIndex(size_t index) const {
     /*This function operates at constant time*/
      immutable integerValue = first + cast(int)index;
      return integerValue * integerValue;
   }
}

bool are_lastTwoDigitsSame(int value) {
  /*Must have at least two digits*/
   if (value < 10) {
      return false;
   }

  /*Last two digits must be divisible by 11*/
   immutable lastTwoDigits = value % 100;
   return (lastTwoDigits % 11) == 0;
}

void main() {
   auto squares = new SquaresRange();

   writeln(squares[5]);

   writeln(squares[10]);

   squares.popFrontN(5);
   writeln(squares[0]);

   writeln(squares.take(50).filter!are_lastTwoDigitsSame);
}

上記のコードをコンパイルして実行すると、次の結果が生成されます-

25
100
25
[100, 144, 400, 900, 1444, 1600, 2500]

有限RandomAccessRange

双方向範囲と比較した場合、opIndex()とlengthがさらに必要です。 これは、前に使用したフィボナッチ数列と二乗範囲の例を使用した詳細な例を使用して説明します。 この例は、通常のDコンパイラではうまく機能しますが、オンラインコンパイラでは機能しません。

import std.array;
import std.stdio;
import std.string;
import std.range;
import std.algorithm;

struct FibonacciSeries {
   int first = 0;
   int second = 1;
   enum empty = false;  // infinite range

   @property int front() const {
      return first;
   }
   void popFront() {
      int third = first + second;
      first = second;
      second = third;
   }
   @property FibonacciSeries save() const {
      return this;
   }
}

void report(T)(const dchar[] title, const ref T range) {
   writefln("%40s: %s", title, range.take(5));
}

class SquaresRange {
   int first;
   this(int first = 0) {
      this.first = first;
   }
   enum empty = false;
   @property int front() const {
      return opIndex(0);
   }
   void popFront() {
      ++first;
   }
   @property SquaresRange save() const {
      return new SquaresRange(first);
   }
   int opIndex(size_t index) const {
     /*This function operates at constant time*/
      immutable integerValue = first + cast(int)index;
      return integerValue * integerValue;
   }
}

bool are_lastTwoDigitsSame(int value) {
  /*Must have at least two digits*/
   if (value < 10) {
      return false;
   }

  /*Last two digits must be divisible by 11*/
   immutable lastTwoDigits = value % 100;
   return (lastTwoDigits % 11) == 0;
}

struct Together {
   const(int)[][] slices;
   this(const(int)[][] slices ...) {
      this.slices = slices.dup;
      clearFront();
      clearBack();
   }
   private void clearFront() {
      while (!slices.empty && slices.front.empty) {
         slices.popFront();
      }
   }
   private void clearBack() {
      while (!slices.empty && slices.back.empty) {
         slices.popBack();
      }
   }
   @property bool empty() const {
      return slices.empty;
   }
   @property int front() const {
      return slices.front.front;
   }
   void popFront() {
      slices.front.popFront();
      clearFront();
   }
   @property Together save() const {
      return Together(slices.dup);
   }
   @property int back() const {
      return slices.back.back;
   }
   void popBack() {
      slices.back.popBack();
      clearBack();
   }
   @property size_t length() const {
      return reduce!((a, b) => a + b.length)(size_t.init, slices);
   }
   int opIndex(size_t index) const {
     /*Save the index for the error message*/
      immutable originalIndex = index;

      foreach (slice; slices) {
         if (slice.length > index) {
            return slice[index];
         } else {
            index -= slice.length;
         }
      }
      throw new Exception(
         format("Invalid index: %s (length: %s)", originalIndex, this.length));
   }
}
void main() {
   auto range = Together(FibonacciSeries().take(10).array, [ 777, 888 ],
      (new SquaresRange()).take(5).array);
   writeln(range.save);
}

上記のコードをコンパイルして実行すると、次の結果が生成されます-

[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 777, 888, 0, 1, 4, 9, 16]

出力範囲

OutputRangeは、文字をstdoutに送信するのと同様に、ストリーミングされた要素の出力を表します。 OutputRangeでは、put(range、element)操作のサポートが必要です。 put()は、std.rangeモジュールで定義されている関数です。 コンパイル時に範囲と要素の機能を決定し、要素の出力に使用する最も適切な方法を使用します。 以下に簡単な例を示します。

import std.algorithm;
import std.stdio;

struct MultiFile {
   string delimiter;
   File[] files;

   this(string delimiter, string[] fileNames ...) {
      this.delimiter = delimiter;

     /*stdout is always included*/
      this.files ~= stdout;

     /*A File object for each file name*/
      foreach (fileName; fileNames) {
         this.files ~= File(fileName, "w");
      }
   }
   void put(T)(T element) {
      foreach (file; files) {
         file.write(element, delimiter);
      }
   }
}
void main() {
   auto output = MultiFile("\n", "output_0", "output_1");
   copy([ 1, 2, 3], output);
   copy([ "red", "blue", "green" ], output);
}

上記のコードをコンパイルして実行すると、次の結果が生成されます-

[1, 2, 3]
["red", "blue", "green"]