Programmazione D - Gamme

Gli intervalli sono un'astrazione dell'accesso agli elementi. Questa astrazione consente l'uso di un gran numero di algoritmi su un gran numero di tipi di contenitori. Gli intervalli sottolineano il modo in cui si accede agli elementi del contenitore, al contrario di come vengono implementati i contenitori. Intervalli è un concetto molto semplice basato sul fatto che un tipo definisca determinati set di funzioni membro.

Gli intervalli sono parte integrante delle sezioni di D. D sono implementazioni della gamma più potente RandomAccessRange e ci sono molte caratteristiche di intervallo in Phobos. Molti algoritmi Phobos restituiscono oggetti di portata temporanea. Ad esempio, filter () sceglie gli elementi che sono maggiori di 10 nel codice seguente restituisce effettivamente un oggetto intervallo, non un array.

Intervalli di numeri

Gli intervalli di numeri sono usati abbastanza comunemente e questi intervalli di numeri sono di tipo int. Di seguito sono riportati alcuni esempi di intervalli di numeri:

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

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

Phobos Ranges

Gli intervalli relativi a strutture e interfacce di classe sono intervalli di phobos. Phobos è il runtime ufficiale e la libreria standard fornita con il compilatore del linguaggio D.

Esistono vari tipi di gamme che includono:

  • InputRange
  • ForwardRange
  • BidirectionalRange
  • RandomAccessRange
  • OutputRange

InputRange

L'intervallo più semplice è l'intervallo di input. Le altre gamme portano più requisiti in cima alla gamma su cui si basano. Ci sono tre funzioni richieste da InputRange:

  • empty- Specifica se l'intervallo è vuoto; deve restituire vero quando l'intervallo è considerato vuoto; altrimenti falso.

  • front - Fornisce l'accesso all'elemento all'inizio dell'intervallo.

  • popFront() - Riduce l'intervallo dall'inizio rimuovendo il primo elemento.

Esempio

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

Quando il codice precedente viene compilato ed eseguito, produce il seguente risultato:

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

ForwardRange

ForwardRange richiede inoltre la parte della funzione membro di salvataggio dalle altre tre funzioni di InputRange e restituisce una copia dell'intervallo quando viene chiamata la funzione di salvataggio.

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

Quando il codice precedente viene compilato ed eseguito, produce il seguente risultato:

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 fornisce inoltre due funzioni membro sulle funzioni membro di ForwardRange. La funzione back, simile a quella front, consente l'accesso all'ultimo elemento della gamma. La funzione popBack è simile alla funzione popFront e rimuove l'ultimo elemento dall'intervallo.

Esempio

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

Quando il codice precedente viene compilato ed eseguito, produce il seguente risultato:

[3, 2, 1]

Infinite RandomAccessRange

opIndex () è inoltre richiesto rispetto a ForwardRange. Inoltre, il valore di una funzione vuota deve essere noto in fase di compilazione come falso. Di seguito viene illustrato un semplice esempio con l'intervallo dei quadrati.

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

Quando il codice precedente viene compilato ed eseguito, produce il seguente risultato:

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

RandomAccessRange finito

opIndex () e length sono inoltre necessari rispetto all'intervallo bidirezionale. Ciò è spiegato con l'aiuto di un esempio dettagliato che utilizza la serie di Fibonacci e l'esempio dell'intervallo di quadrati usato in precedenza. Questo esempio funziona bene sul normale compilatore D ma non funziona sul compilatore online.

Esempio

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

Quando il codice precedente viene compilato ed eseguito, produce il seguente risultato:

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

OutputRange

OutputRange rappresenta l'output dell'elemento in streaming, simile all'invio di caratteri a stdout. OutputRange richiede il supporto per l'operazione put (range, element). put () è una funzione definita nel modulo std.range. Determina le capacità dell'intervallo e dell'elemento in fase di compilazione e utilizza il metodo più appropriato da utilizzare per l'output degli elementi. Di seguito viene mostrato un semplice esempio.

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

Quando il codice precedente viene compilato ed eseguito, produce il seguente risultato:

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