D 语言 - 范围

范围是元素访问的抽象。 这种抽象允许在大量容器类型上使用大量算法。 范围强调如何访问容器元素,而不是如何实现容器。 范围是一个非常简单的概念,它基于类型是否定义了某些成员函数集。

范围是D的一个组成部分。D的切片恰好是最强大的RandomAccessRange的实现,并且Phobos中有很多Range功能。 许多 Phobos 算法返回临时范围对象。 例如,下面代码中filter()选择大于10的元素实际上返回的是一个范围对象,而不是一个数组。

数字范围

数字范围非常常用,这些数字范围是 int 类型。 下面显示了数字范围的一些示例 −

// 示例 1 
foreach (value; 3..7)  

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

Phobos范围

与结构和类接口相关的范围是phobos范围。 Phobos 是 D 语言编译器附带的官方运行时和标准库。

范围有多种类型,包括 −

  • 输入范围
  • 前向范围
  • 双向范围
  • 随机访问范围
  • 输出范围

输入范围

最简单的范围是输入范围。 其他范围在它们所基于的范围之上提出了更多要求。 InputRange需要三个函数 −

  • empty − 指定范围是否为空; 当范围被认为是空时,它必须返回 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)]

前向范围

前向范围还需要输入范围的其他三个函数中的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]

双向范围

双向范围在前向范围的成员函数的基础上还提供了两个成员函数。 back 函数与 front 类似,提供对范围的最后一个元素的访问。 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]

无限随机访问范围

与前向范围相比,还需要 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 { 
      /* 该函数以恒定时间运行 */ 
      immutable integerValue = first + cast(int)index; 
      return integerValue * integerValue; 
   } 
}
  
bool are_lastTwoDigitsSame(int value) { 
   /* 必须至少有两位数字 */ 
   if (value < 10) { 
      return false; 
   } 
   
   /* 最后两位数字必须能被 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]

有限随机访问范围

与双向范围相比,还需要 opIndex() 和长度。 借助使用斐波那契数列和前面使用的平方范围示例的详细示例来解释这一点。 此示例在普通 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;   //  无限范围
   
   @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 { 
      /* 该函数以恒定时间运行 */ 
      immutable integerValue = first + cast(int)index; 
      return integerValue * integerValue; 
   } 
}
  
bool are_lastTwoDigitsSame(int value) { 
   /* 必须至少有两位数字 */ 
   if (value < 10) { 
      return false; 
   }
   
   /* 最后两位数字必须能被 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 { 
      /* 保存错误消息的索引 */ 
      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]

输出范围

输出范围表示流式元素输出,类似于将字符发送到 stdout。输出范围需要支持 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; 

      /* 始终包含标准输出 */ 
      this.files ~= stdout; 

      /* 每个文件名都有一个 File 对象 */ 
      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"]