Programowanie D - zakresy

Zakresy są abstrakcją dostępu do elementów. Ta abstrakcja umożliwia stosowanie dużej liczby algorytmów w wielu typach kontenerów. Zakresy kładą nacisk na sposób uzyskiwania dostępu do elementów kontenera, w przeciwieństwie do sposobu implementacji kontenerów. Zakresy to bardzo prosta koncepcja oparta na tym, czy typ definiuje określone zestawy funkcji składowych.

Zakresy są integralną częścią wycinków D.D są implementacjami najpotężniejszego zakresu RandomAccessRange, aw Phobos jest wiele funkcji zasięgu. Wiele algorytmów Phobos zwraca tymczasowe obiekty zasięgu. Na przykład filter () wybiera elementy, które są większe niż 10 w poniższym kodzie, w rzeczywistości zwraca obiekt zakresu, a nie tablicę.

Zakresy liczbowe

Zakresy liczb są dość powszechnie używane, a te zakresy liczb są typu int. Poniżej przedstawiono kilka przykładów zakresów liczb -

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

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

Zakresy Phobosa

Zakresy związane ze strukturami i interfejsami klas to zakresy phobos. Phobos jest oficjalną biblioteką wykonawczą i standardową dostarczaną wraz z kompilatorem języka D.

Istnieją różne rodzaje zakresów, które obejmują -

  • InputRange
  • ForwardRange
  • BidirectionalRange
  • RandomAccessRange
  • OutputRange

InputRange

Najprostszym zakresem jest zakres wejściowy. Inne zakresy stawiają więcej wymagań niż zakres, na którym są oparte. Istnieją trzy funkcje, których wymaga InputRange -

  • empty- Określa, czy zakres jest pusty; musi zwrócić prawdę, gdy zakres jest uważany za pusty; w przeciwnym razie fałsz.

  • front - Zapewnia dostęp do elementu na początku zakresu.

  • popFront() - Skraca zasięg od początku usuwając pierwszy element.

Przykład

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); 
}

Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -

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

ForwardRange

ForwardRange dodatkowo wymaga części składowej składowej składowania z pozostałych trzech funkcji InputRange i zwraca kopię zakresu po wywołaniu funkcji składowania.

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); 
}

Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -

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]

Dwukierunkowy zakres

BidirectionalRange dodatkowo zapewnia dwie funkcje składowe w stosunku do funkcji składowych ForwardRange. Funkcja tyłu, podobnie jak przód, zapewnia dostęp do ostatniego elementu asortymentu. Funkcja popBack jest podobna do funkcji popFront i usuwa ostatni element z zakresu.

Przykład

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])); 
}

Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -

[3, 2, 1]

Nieskończony RandomAccessRange

opIndex () jest dodatkowo wymagana w porównaniu z ForwardRange. Ponadto wartość pustej funkcji, która ma być znana w czasie kompilacji jako fałsz. Prosty przykład wyjaśniono za pomocą zakresu kwadratów pokazano poniżej.

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); 
}

Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -

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

Finite RandomAccessRange

opIndex () i length są dodatkowo wymagane w porównaniu z zakresem dwukierunkowym. Jest to wyjaśnione za pomocą szczegółowego przykładu, w którym wykorzystano wcześniej używany przykład szeregów Fibonacciego i zakresu kwadratów. Ten przykład działa dobrze na normalnym kompilatorze D, ale nie działa na kompilatorze online.

Przykład

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); 
}

Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -

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

OutputRange

OutputRange reprezentuje przesyłane strumieniowo dane wyjściowe elementu, podobne do wysyłania znaków na standardowe wyjście. OutputRange wymaga obsługi operacji put (zakres, element). put () to funkcja zdefiniowana w module std.range. Określa możliwości zakresu i elementu w czasie kompilacji i używa najbardziej odpowiedniej metody do użycia w celu wyprowadzenia elementów. Poniżej przedstawiono prosty przykład.

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); 
}

Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -

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