Programmation D - Gammes

Les plages sont une abstraction de l'accès aux éléments. Cette abstraction permet d'utiliser un grand nombre d'algorithmes sur un grand nombre de types de conteneurs. Les plages soulignent la manière dont les éléments de conteneur sont accédés, par opposition à la manière dont les conteneurs sont implémentés. Les plages sont un concept très simple basé sur le fait qu'un type définit certains ensembles de fonctions membres.

Les plages font partie intégrante des tranches de D. D se trouvent être des implémentations de la plage la plus puissante RandomAccessRange, et il existe de nombreuses fonctionnalités de plage dans Phobos. De nombreux algorithmes Phobos renvoient des objets de plage temporaires. Par exemple, filter () choisit les éléments supérieurs à 10 dans le code suivant qui renvoie en fait un objet de plage, pas un tableau.

Plages de nombres

Les plages de nombres sont assez couramment utilisées et ces plages de nombres sont de type int. Voici quelques exemples de plages de nombres -

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

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

Gammes Phobos

Les plages liées aux structures et aux interfaces de classe sont des plages phobos. Phobos est le runtime officiel et la bibliothèque standard fournis avec le compilateur de langage D.

Il existe différents types de gammes qui incluent -

  • InputRange
  • ForwardRange
  • BidirectionalRange
  • RandomAccessRange
  • OutputRange

Plage d'entrée

La plage la plus simple est la plage d'entrée. Les autres gammes apportent plus d'exigences au-dessus de la gamme sur laquelle elles sont basées. InputRange requiert trois fonctions:

  • empty- Il spécifie si la plage est vide; il doit retourner true lorsque la plage est considérée comme vide; faux sinon.

  • front - Il permet d'accéder à l'élément au début de la plage.

  • popFront() - Il raccourcit la plage depuis le début en supprimant le premier élément.

Exemple

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

Lorsque le code ci-dessus est compilé et exécuté, il produit le résultat suivant -

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

ForwardRange

ForwardRange nécessite en outre la partie de fonction de membre de sauvegarde des trois autres fonctions de InputRange et renvoie une copie de la plage lorsque la fonction de sauvegarde est appelée.

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

Lorsque le code ci-dessus est compilé et exécuté, il produit le résultat suivant -

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]

Bidirectionnel

BidirectionalRange fournit en outre deux fonctions membres sur les fonctions membres de ForwardRange. La fonction back, similaire à front, permet d'accéder au dernier élément de la plage. La fonction popBack est similaire à la fonction popFront et elle supprime le dernier élément de la plage.

Exemple

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

Lorsque le code ci-dessus est compilé et exécuté, il produit le résultat suivant -

[3, 2, 1]

Plage d'accès aléatoire infinie

opIndex () est également requis par rapport à ForwardRange. En outre, la valeur d'une fonction vide doit être connue au moment de la compilation comme étant false. Un exemple simple est expliqué avec une plage de carrés ci-dessous.

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

Lorsque le code ci-dessus est compilé et exécuté, il produit le résultat suivant -

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

Plage d'accès aléatoire finie

opIndex () et length sont en plus requis par rapport à la plage bidirectionnelle. Ceci est expliqué à l'aide d'un exemple détaillé qui utilise la série de Fibonacci et l'exemple de plage de carrés utilisés précédemment. Cet exemple fonctionne bien sur le compilateur D normal mais ne fonctionne pas sur le compilateur en ligne.

Exemple

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

Lorsque le code ci-dessus est compilé et exécuté, il produit le résultat suivant -

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

OutputRange

OutputRange représente la sortie d'élément diffusée, similaire à l'envoi de caractères à stdout. OutputRange nécessite la prise en charge de l'opération put (range, element). put () est une fonction définie dans le module std.range. Il détermine les capacités de la plage et de l'élément au moment de la compilation et utilise la méthode la plus appropriée à utiliser pour sortir les éléments. Un exemple simple est présenté ci-dessous.

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

Lorsque le code ci-dessus est compilé et exécuté, il produit le résultat suivant -

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