D Programmierung - Bereiche
Bereiche sind eine Abstraktion des Elementzugriffs. Diese Abstraktion ermöglicht die Verwendung einer großen Anzahl von Algorithmen über eine große Anzahl von Containertypen. Bereiche betonen, wie auf Containerelemente zugegriffen wird, im Gegensatz dazu, wie die Container implementiert werden. Bereiche ist ein sehr einfaches Konzept, das darauf basiert, ob ein Typ bestimmte Sätze von Elementfunktionen definiert.
Bereiche sind ein wesentlicher Bestandteil von D. Ds Slices sind Implementierungen des leistungsstärksten Bereichs RandomAccessRange, und es gibt viele Bereichsfunktionen in Phobos. Viele Phobos-Algorithmen geben temporäre Bereichsobjekte zurück. Beispielsweise wählt filter () Elemente aus, die im folgenden Code größer als 10 sind, und gibt tatsächlich ein Bereichsobjekt zurück, kein Array.
Nummernkreise
Nummernkreise werden häufig verwendet und diese Nummernkreise sind vom Typ int. Einige Beispiele für Nummernkreise sind unten aufgeführt -
// Example 1
foreach (value; 3..7)
// Example 2
int[] slice = array[5..10];
Phobos Ranges
Bereiche, die sich auf Strukturen und Klassenschnittstellen beziehen, sind Phobos-Bereiche. Phobos ist die offizielle Laufzeit- und Standardbibliothek, die mit dem D-Sprach-Compiler geliefert wird.
Es gibt verschiedene Arten von Bereichen, darunter -
- InputRange
- ForwardRange
- BidirectionalRange
- RandomAccessRange
- OutputRange
Eingabebereich
Der einfachste Bereich ist der Eingabebereich. Die anderen Bereiche stellen mehr Anforderungen als der Bereich, auf dem sie basieren. InputRange benötigt drei Funktionen:
empty- Gibt an, ob der Bereich leer ist. es muss true zurückgeben, wenn der Bereich als leer betrachtet wird; sonst falsch.
front - Es bietet Zugriff auf das Element am Anfang des Bereichs.
popFront() - Es verkürzt den Bereich von Anfang an, indem das erste Element entfernt wird.
Beispiel
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);
}
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
[Raj(1), John(2), Ram(3)]
3
Raj(1)
false
[John(2), Ram(3)]
ForwardRange
ForwardRange erfordert zusätzlich den Funktionsteil zum Speichern von Elementen aus den anderen drei Funktionen von InputRange und gibt beim Aufrufen der Speicherfunktion eine Kopie des Bereichs zurück.
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);
}
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
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]
Bidirektionaler Bereich
BidirectionalRange bietet zusätzlich zwei Elementfunktionen über die Elementfunktionen von ForwardRange. Die Back-Funktion, die der Front ähnelt, ermöglicht den Zugriff auf das letzte Element des Bereichs. Die Funktion popBack ähnelt der Funktion popFront und entfernt das letzte Element aus dem Bereich.
Beispiel
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]));
}
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
[3, 2, 1]
Infinite RandomAccessRange
opIndex () ist im Vergleich zum ForwardRange zusätzlich erforderlich. Außerdem muss der Wert einer leeren Funktion zur Kompilierungszeit als falsch bekannt sein. Ein einfaches Beispiel mit dem Quadratbereich wird unten gezeigt.
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);
}
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
25
100
25
[100, 144, 400, 900, 1444, 1600, 2500]
Finite RandomAccessRange
opIndex () und Länge sind im Vergleich zum bidirektionalen Bereich zusätzlich erforderlich. Dies wird anhand eines detaillierten Beispiels erläutert, das das zuvor verwendete Beispiel der Fibonacci-Serie und des Squares Range verwendet. Dieses Beispiel funktioniert gut mit einem normalen D-Compiler, aber nicht mit einem Online-Compiler.
Beispiel
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);
}
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 777, 888, 0, 1, 4, 9, 16]
OutputRange
OutputRange repräsentiert die Ausgabe von gestreamten Elementen, ähnlich wie das Senden von Zeichen an stdout. OutputRange erfordert Unterstützung für die Put-Operation (Bereich, Element). put () ist eine Funktion, die im Modul std.range definiert ist. Es bestimmt die Fähigkeiten des Bereichs und des Elements zur Kompilierungszeit und verwendet die am besten geeignete Methode zur Ausgabe der Elemente. Ein einfaches Beispiel ist unten gezeigt.
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);
}
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
[1, 2, 3]
["red", "blue", "green"]