forked from DlangRen/Programming-in-D
-
Notifications
You must be signed in to change notification settings - Fork 1
/
ranges.d
2013 lines (1504 loc) · 62 KB
/
ranges.d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
Ddoc
$(DERS_BOLUMU $(IX range) Ranges)
$(P
Ranges are an abstraction of element access. This abstraction enables the use of great number of algorithms over great number of container types. Ranges emphasize how container elements are accessed, as opposed to how the containers are implemented.
)
$(P
Ranges are a very simple concept that is based on whether a type defines certain sets of member functions. We have already seen this concept in the $(LINK2 /ders/d.en/foreach_opapply.html, $(C foreach) with Structs and Classes chapter): any type that provides the member functions $(C empty), $(C front), and $(C popFront()) can be used with the $(C foreach) loop. The set of those three member functions is the requirement of the range type $(C InputRange).
)
$(P
I will start introducing ranges with $(C InputRange), the simplest of all the range types. The other ranges require more member functions over $(C InputRange).
)
$(P
Before going further, I would like to provide the definitions of containers and algorithms.
)
$(P
$(IX container) $(IX data structure) $(B Container (data structure):) Container is a very useful concept that appears in almost every program. Variables are put together for a purpose and are used together as elements of a container. D's containers are its core features arrays and associative arrays, and special container types that are defined in the $(C std.container) module. Every container is implemented as a specific data structure. For example, associative arrays are a $(I hash table) implementation.
)
$(P
Every data structure stores its elements and provides access to them in ways that are special to that data structure. For example, in the array data structure the elements are stored side by side and accessed by an element index; in the linked list data structure the elements are stored in nodes and are accessed by going through those nodes one by one; in a sorted binary tree data structure, the nodes provide access to the preceding and successive elements through separate branches; etc.
)
$(P
In this chapter, I will use the terms $(I container) and $(I data structure) interchangeably.
)
$(P
$(IX algorithm) $(B Algorithm (function):) Processing of data structures for specific purposes in specific ways is called an $(I algorithm). For example, $(I linear search) is an algorithm that searches by iterating over a container from the beginning to the end; $(I binary search) is an algorithm that searches for an element by eliminating half of the candidates at every step; etc.
)
$(P
In this chapter, I will use the terms $(I algorithm) and $(I function) interchangeably.
)
$(P
For most of the samples below, I will use $(C int) as the element type and $(C int[]) as the container type. In reality, ranges are more powerful when used with templated containers and algorithms. In fact, most of the containers and algorithms that ranges tie together are all templates. I will leave examples of templated ranges to $(LINK2 /ders/d.en/ranges_more.html, the next chapter).
)
$(H5 History)
$(P
A very successful library that abstracts algorithms and data structures from each other is the Standard Template Library (STL), which also appears as a part of the C++ standard library. STL provides this abstraction with the $(I iterator) concept, which is implemented by C++'s templates.
)
$(P
Although they are a very useful abstraction, iterators do have some weaknesses. D's ranges were designed to overcome these weaknesses.
)
$(P
Andrei Alexandrescu introduces ranges in his paper $(LINK2 http://www.informit.com/articles/printerfriendly.aspx?p=1407357, On Iteration) and demonstrates how they can be superior to iterators.
)
$(H5 Ranges are an integral part of D)
$(P
D's slices happen to be implementations of the most powerful range $(C RandomAccessRange), and there are many range features in Phobos. It is essential to understand how ranges are used in Phobos.
)
$(P
Many Phobos algorithms return temporary range objects. For example, $(C filter()), which chooses elements that are greater than 10 in the following code, actually returns a range object, not an array:
)
---
import std.stdio;
import std.algorithm;
void main() {
int[] values = [ 1, 20, 7, 11 ];
writeln(values.filter!(value => value > 10));
}
---
$(P
$(C writeln) uses that range object lazily and accesses the elements as it needs them:
)
$(SHELL
[20, 11]
)
$(P
That output may suggest that $(C filter()) returns an $(C int[]) but this is not the case. We can see this from the fact that the following assignment produces a compilation error:
)
---
int[] chosen = values.filter!(value => value > 10); $(DERLEME_HATASI)
---
$(P
The error message contains the type of the range object:
)
$(SHELL
Error: cannot implicitly convert expression (filter(values))
of type $(HILITE FilterResult!(__lambda2, int[])) to int[]
)
$(P
$(I $(B Note:) The type may be different in the version of Phobos that you are using.)
)
$(P
It is possible to convert that temporary object to an actual array, as we will see later in the chapter.
)
$(H5 Traditional implementations of algorithms)
$(P
In traditional implementations of algorithms, the algorithms know how the data structures that they operate on are implemented. For example, the following function that prints the elements of a linked list must know that the nodes of the linked list have members named $(C element) and $(C next):
)
---
struct Node {
int element;
Node * next;
}
void print(const(Node) * list) {
for ( ; list; list = list.$(HILITE next)) {
write(' ', list.$(HILITE element));
}
}
---
$(P
Similarly, a function that prints the elements of an array must know that arrays have a $(C length) property and their elements are accessed by the $(C []) operator:
)
---
void print(const int[] array) {
for (int i = 0; i != array.$(HILITE length); ++i) {
write(' ', array$(HILITE [i]));
}
}
---
$(P
$(I $(B Note:) We know that $(C foreach) is more useful when iterating over arrays. As a demonstration of how traditional algorithms are tied to data structures, let's assume that the use of $(C for) is justified.)
)
$(P
Having algorithms tied to data structures makes it necessary to write them specially for each type. For example, the functions find(), sort(), swap(), etc. must be written separately for array, linked list, associative array, binary tree, heap, etc. As a result, N algorithms that support M data structures must be written NxM times. (Note: In reality, the count is less than NxM because not every algorithm can be applied to every data structure; for example, associative arrays cannot be sorted.)
)
$(P
Conversely, because ranges abstract algorithms away from data structures, implementing just N algorithms and M data structures would be sufficient. A newly implemented data structure can work with all of the existing algorithms that support the type of range that the new data structure provides, and a newly implemented algorithm can work with all of the existing data structures that support the range type that the new algorithm requires.
)
$(H5 Phobos ranges)
$(P
The ranges in this chapter are different from number ranges that are written in the form $(C begin..end). We have seen how number ranges are used with the $(C foreach) loop and with slices:
)
---
foreach (value; $(HILITE 3..7)) { // number range,
// NOT a Phobos range
int[] slice = array[$(HILITE 5..10)]; // number range,
// NOT a Phobos range
---
$(P
When I write $(I range) in this chapter, I mean a Phobos range .
)
$(P
Ranges form a $(I range hierarchy). At the bottom of this hierarchy is the simplest range $(C InputRange). The other ranges bring more requirements on top of the range on which they are based. The following are all of the ranges with their requirements, sorted from the simplest to the more capable:
)
$(UL
$(LI $(C InputRange): requires the $(C empty), $(C front) and $(C popFront()) member functions)
$(LI $(C ForwardRange): additionally requires the $(C save) member function)
$(LI $(C BidirectionalRange): additionally requires the $(C back) and $(C popBack()) member functions)
$(LI $(C RandomAccessRange): additionally requires the $(C []) operator (and another property depending on whether the range is finite or infinite))
)
$(P
This hierarchy can be shown as in the following graph. $(C RandomAccessRange) has finite and infinite versions:
)
$(MONO
InputRange
↑
ForwardRange
↗ ↖
BidirectionalRange RandomAccessRange (infinite)
↑
RandomAccessRange (finite)
)
$(P
The graph above is in the style of class hierarchies where the lowest level type is at the top.
)
$(P
Those ranges are about providing element access. There is one more range, which is about element $(I output):
)
$(UL
$(LI $(C OutputRange): requires support for the $(C put(range, element)) operation)
)
$(P
These five range types are sufficient to abstract algorithms from data structures.
)
$(H6 Iterating by shortening the range)
$(P
Normally, iterating over the elements of a container does not change the container itself. For example, iterating over a slice with $(C foreach) or $(C for) does not affect the slice:
)
---
int[] slice = [ 10, 11, 12 ];
for (int i = 0; i != slice.length; ++i) {
write(' ', slice[i]);
}
assert(slice.length == 3); // ← the length doesn't change
---
$(P
Another way of iteration requires a different way of thinking: iteration can be achieved by shortening the range from the beginning. In this method, always the first element is used for element access and the first element is $(I popped) from the beginning in order to get to the next element:
)
---
for ( ; slice.length; slice = slice[1..$]) {
write(' ', $(HILITE slice[0])); // ← always the first element
}
---
$(P
$(I Iteration) is achieved by removing the first element by the $(C slice = slice[1..$]) expression. The slice above is completely consumed by going through the following stages:
)
$(MONO
[ 10, 11, 12 ]
[ 11, 12 ]
[ 12 ]
[ ]
)
$(P
The iteration concept of Phobos ranges is based on this new thinking of shortening the range from the beginning. ($(C BidirectionalRange) and finite $(C RandomAccessRange) types can be shortened from the end as well.)
)
$(P
Please note that the code above is just to demonstrate this type of iteration; it should not be considered normal to iterate as in that example.
)
$(P
Since losing elements just to iterate over a range would not be desired in most cases, a surrogate range may be consumed instead. The following code uses a separate slice to preserve the elements of the original one:
)
---
int[] slice = [ 10, 11, 12 ];
int[] surrogate = slice;
for ( ; surrogate.length; $(HILITE surrogate = surrogate[1..$])) {
write(' ', surrogate[0]);
}
assert(surrogate.length == 0); // ← surrogate is consumed
assert(slice.length == 3); // ← slice remains the same
---
$(P
This is the method employed by most of the Phobos range functions: they return special range objects to be consumed in order to preserve the original containers.
)
$(H5 $(IX InputRange) $(C InputRange))
$(P
This type of range models the type of iteration where elements are accessed in sequence as we have seen in the $(C print()) functions above. Most algorithms only require that elements are iterated in the forward direction without needing to look at elements that have already been iterated over. $(C InputRange) models the standard input streams of programs as well, where elements are removed from the stream as they are read.
)
$(P
For completeness, here are the three functions that $(C InputRange) requires:
)
$(UL
$(LI $(IX empty) $(C empty): specifies whether the range is empty; it must return $(C true) when the range is considered to be empty, and $(C false) otherwise)
$(LI $(IX front) $(C front): provides access to the element at the beginning of the range)
$(LI $(IX popFront) $(C popFront()): shortens the range from the beginning by removing the first element)
)
$(P
$(I $(B Note:) I write $(C empty) and $(C front) without parentheses, as they can be seen as properties of the range; and $(C popFront()) with parentheses as it is a function with side effects.)
)
$(P
Here is how $(C print()) can be implemented by using these range functions:
)
---
void print(T)(T range) {
for ( ; !range$(HILITE .empty); range$(HILITE .popFront())) {
write(' ', range$(HILITE .front));
}
writeln();
}
---
$(P
Please also note that $(C print()) is now a function template to avoid limiting the range type arbitrarily. $(C print()) can now work with any type that provides the three $(C InputRange) functions.
)
$(H6 $(C InputRange) example)
$(P
Let's redesign the $(C School) type that we have seen before, this time as an $(C InputRange). We can imagine $(C School) as a $(C Student) container so when designed as a range, it can be seen as a range of $(C Student)s.
)
$(P
In order to keep the example short, let's disregard some important design aspects. Let's
)
$(UL
$(LI implement only the members that are related to this section)
$(LI design all types as structs)
$(LI ignore specifiers and qualifiers like $(C private), $(C public), and $(C const))
$(LI not take advantage of contract programming and unit testing)
)
---
import std.string;
struct Student {
string name;
int number;
string toString() const {
return format("%s(%s)", name, number);
}
}
struct School {
Student[] students;
}
void main() {
auto school = School( [ Student("Ebru", 1),
Student("Derya", 2) ,
Student("Damla", 3) ] );
}
---
$(P
To make $(C School) be accepted as an $(C InputRange), we must define the three $(C InputRange) member functions.
)
$(P
For $(C empty) to return $(C true) when the range is empty, we can use the length of the $(C students) array. When the length of that array is 0, the range is considered empty:
)
---
struct School {
// ...
@property bool empty() const {
return students.length == 0;
}
}
---
$(P
$(C empty) is defined as $(C @property) to be able to code it without parentheses, as in $(C school.empty).
)
$(P
For $(C front) to return the first element of the range, we can return the first element of the array:
)
---
struct School {
// ...
@property ref Student front() {
return students[0];
}
}
---
$(P
$(I $(B Note:) I have used the $(C ref) keyword to be able to provide access to the actual element instead of a copy of it. Otherwise the elements would be copied because $(C Student) is a struct.)
)
$(P
For $(C popFront()) to shorten the range from the beginning, we can shorten the $(C students) array from the beginning:
)
---
struct School {
// ...
void popFront() {
students = students[1 .. $];
}
}
---
$(P
$(I $(B Note:) As I have mentioned above, it is not normal to lose the original elements from the container just to iterate over them. We will address this issue below by introducing a special range type.)
)
$(P
These three functions are sufficient to make $(C School) to be used as an $(C InputRange). As an example, let's add the following line at the end of $(C main()) above to have our new $(C print()) function template to use $(C school) as a range:
)
---
print(school);
---
$(P
$(C print()) uses that object as an $(C InputRange) and prints its elements to the output:
)
$(SHELL
Ebru(1) Derya(2) Damla(3)
)
$(P
We have achieved our goal of defining a user type as an $(C InputRange); we have sent it to an algorithm that operates on $(C InputRange) types. $(C School) is actually ready to be used with algorithms of Phobos or any other library that work with $(C InputRange) types. We will see examples of this below.
)
$(H6 $(IX slice, as InputRange) The $(C std.array) module to use slices as ranges)
$(P
Merely importing the $(C std.array) module makes the most common container type conform to the most capable range type: slices can seamlessly be used as $(C RandomAccessRange) objects.
)
$(P
The $(C std.array) module provides the functions $(C empty), $(C front), $(C popFront()) and other range functions for slices. As a result, slices are ready to be used with any range function, for example with $(C print()):
)
---
import $(HILITE std.array);
// ...
print([ 1, 2, 3, 4 ]);
---
$(P
It is not necessary to import $(C std.array) if the $(C std.range) module has already been imported.
)
$(P
Since it is not possible to remove elements from fixed-length arrays, $(C popFront()) cannot be defined for them. For that reason, fixed-length arrays cannot be used as ranges themselves:
)
---
void print(T)(T range) {
for ( ; !range.empty; range.popFront()) { $(DERLEME_HATASI)
write(' ', range.front);
}
writeln();
}
void main() {
int[$(HILITE 4)] array = [ 1, 2, 3, 4 ];
print(array);
}
---
$(P
It would be better if the compilation error appeared on the line where $(C print()) is called. This is possible by adding a template constraint to $(C print()). The following template constraint takes advantage of $(C isInputRange), which we will see in the next chapter. By the help of the template constraint, now the compilation error is for the line where $(C print()) is called, not for a line where $(C print()) is defined:
)
---
void print(T)(T range)
if (isInputRange!T) { // template constraint
// ...
}
// ...
print(array); $(DERLEME_HATASI)
---
$(P
The elements of a fixed-length array can still be accessed by range functions. What needs to be done is to use a slice of the whole array, not the array itself:
)
---
print(array$(HILITE [])); // now compiles
---
$(P
Even though slices can be used as ranges, not every range type can be used as an array. When necessary, all of the elements can be copied one by one into an array. $(C std.array.array) is a helper function to simplify this task; $(C array()) iterates over $(C InputRange) ranges, copies the elements, and returns a new array:
)
---
import std.array;
// ...
// Note: Also taking advantage of UFCS
auto copiesOfStudents = school.$(HILITE array);
writeln(copiesOfStudents);
---
$(P
输出:
)
$(SHELL
[Ebru(1), Derya(2), Damla(3)]
)
$(P
Also note the use of $(LINK2 /ders/d.en/ufcs.html, UFCS) in the code above. UFCS goes very well with range algorithms by making code naturally match the execution order of expressions.
)
$(H6 $(IX string, as range) $(IX dchar, string range) $(IX decoding, automatic) Automatic decoding of strings as ranges of $(C dchar))
$(P
Being character arrays by definition, strings can also be used as ranges just by importing $(C std.array). However, $(C char) and $(C wchar) strings cannot be used as $(C RandomAccessRange).
)
$(P
$(C std.array) provides a special functionality with all types of strings: Iterating over strings becomes iterating over Unicode code points, not over UTF code units. As a result, strings appear as ranges of Unicode characters.
)
$(P
The following strings contain ç and é, which cannot be represented by a single $(C char), and 𝔸 (mathematical double-struck capital A), which cannot be represented by a single $(C wchar) (note that these characters may not be displayed correctly in the environment that you are reading this chapter):
)
---
import std.array;
// ...
print("abcçdeé𝔸"c);
print("abcçdeé𝔸"w);
print("abcçdeé𝔸"d);
---
$(P
The output of the program is what we would normally expect from a $(I range of letters):
)
$(XXX We use MONO_NOBOLD instead of SHELL to ensure that 𝔸 is displayed correctly.)
$(MONO_NOBOLD
a b c ç d e é 𝔸
a b c ç d e é 𝔸
a b c ç d e é 𝔸
)
$(P
As you can see, that output does not match what we saw in the $(LINK2 /ders/d.en/characters.html, Characters) and $(LINK2 /ders/d.en/strings.html, Strings) chapters. We have seen in those chapters that $(C string) is an alias to an array of $(C immutable(char)) and $(C wstring) is an alias to an array of $(C immutable(wchar)). Accordingly, one might expect to see UTF code units in the previous output instead of the properly decoded Unicode characters.
)
$(P
The reason why the characters are displayed correctly is because when used as ranges, string elements are automatically decoded. As we will see below, the decoded $(C dchar) values are not actual elements of the strings but $(LINK2 /ders/d.en/lvalue_rvalue.html, rvalues).
)
$(P
As a reminder, let's consider the following function that treats the strings as arrays of code units:
)
---
void $(HILITE printElements)(T)(T str) {
for (int i = 0; i != str.length; ++i) {
write(' ', str$(HILITE [i]));
}
writeln();
}
// ...
printElements("abcçdeé𝔸"c);
printElements("abcçdeé𝔸"w);
printElements("abcçdeé𝔸"d);
---
$(P
When the characters are accessed directly by indexing, the elements of the arrays are not decoded:
)
$(XXX We use MONO_NOBOLD instead of SHELL to ensure that 𝔸 is displayed correctly.)
$(MONO_NOBOLD
a b c � � d e � � � � � �
a b c ç d e é ��� ���
a b c ç d e é 𝔸
)
$(P
$(IX representation, std.string) Automatic decoding is not always the desired behavior. For example, the following program that is trying to assign to the first element of a string cannot be compiled because the return value of $(C .front) is an rvalue:
)
---
import std.array;
void main() {
char[] s = "hello".dup;
s.front = 'H'; $(DERLEME_HATASI)
}
---
$(SHELL
Error: front(s) is $(HILITE not an lvalue)
)
$(P
When a range algorithm needs to modify the actual code units of a string (and when doing so does not invalidate the UTF encoding), then the string can be used as a range of $(C ubyte) elements by $(C std.string.represention):
)
---
import std.array;
import std.string;
void main() {
char[] s = "hello".dup;
s$(HILITE .representation).front = 'H'; // compiles
assert(s == "Hello");
}
---
$(P
$(C representation) presents the actual elements of $(C char), $(C wchar), and $(C dchar) strings as ranges of $(C ubyte), $(C ushort), and $(C uint), respectively.
)
$(H6 Ranges without actual elements)
$(P
The elements of the $(C School) objects were actually stored in the $(C students) member slices. So, $(C School.front) returned a reference to an existing $(C Student) object.
)
$(P
One of the powers of ranges is the flexibility of not actually owning elements. $(C front) need not return an actual element of an actual container. The returned $(I element) can be calculated each time when $(C popFront()) is called, and can be used as the value that is returned by $(C front).
)
$(P
We have already seen a range without actual elements above: Since $(C char) and $(C wchar) cannot represent all Unicode characters, the Unicode characters that appear as range elements cannot be actual elements of any $(C char) or $(C wchar) array. In the case of strings, $(C front) returns a $(C dchar) that is $(I constructed) from the corresponding UTF code units of arrays:
)
---
import std.array;
void main() {
dchar letter = "é".front; // The dchar that is returned by
// front is constructed from the
// two chars that represent é
}
---
$(P
Although the element type of the array is $(C char), the return type of $(C front) above is $(C dchar). That $(C dchar) is not an element of the array but is an $(LINK2 /ders/d.en/lvalue_rvalue.html, rvalue) decoded as a Unicode character from the elements of the array.
)
$(P
Similarly, some ranges do not own any elements but are used for providing access to elements of other ranges. This is a solution to the problem of losing elements while iterating over $(C School) objects above. In order to preserve the elements of the actual $(C School) objects, a special $(C InputRange) can be used.
)
$(P
To see how this is done, let's define a new struct named $(C StudentRange) and move all of the range member functions from $(C School) to this new struct. Note that $(C School) itself is not a range anymore:
)
---
struct School {
Student[] students;
}
struct StudentRange {
Student[] students;
this(School school) {
$(HILITE this.students = school.students);
}
@property bool empty() const {
return students.length == 0;
}
@property ref Student front() {
return students[0];
}
void popFront() {
students = students[1 .. $];
}
}
---
$(P
The new range starts with a member slice that provides access to the students of $(C School) and consumes that member slice in $(C popFront()). As a result, the actual slice in $(C School) is preserved:
)
---
auto school = School( [ Student("Ebru", 1),
Student("Derya", 2) ,
Student("Damla", 3) ] );
print($(HILITE StudentRange)(school));
// The actual array is now preserved:
assert(school.students.length == 3);
---
$(P
$(I $(B Note:) Since all its work is dispatched to its member slice, $(C StudentRange) may not be seen as a good example of a range. In fact, assuming that $(C students) is an accessible member of $(C School), the user code could have created a slice of $(C School.students) directly and could have used that slice as a range.)
)
$(H6 $(IX infinite range) Infinite ranges)
$(P
Another benefit of not storing elements as actual members is the ability to create infinite ranges.
)
$(P
Making an infinite range is as simple as having $(C empty) always return $(C false). Since it is constant, $(C empty) need not even be a function and can be defined as an $(C enum) value:
)
---
enum empty = false; // ← infinite range
---
$(P
Another option is to use an immutable $(C static) member:
)
---
static immutable empty = false; // same as above
---
$(P
As an example of this, let's design a range that represents the Fibonacci series. Despite having only two $(C int) members, the following range can be used as the infinite Fibonacci series:
)
---
$(CODE_NAME FibonacciSeries_InputRange)$(CODE_COMMENT_OUT)struct FibonacciSeries
$(CODE_COMMENT_OUT){
int current = 0;
int next = 1;
enum empty = false; // ← infinite range
@property int front() const {
return current;
}
void popFront() {
const nextNext = current + next;
current = next;
next = nextNext;
}
$(CODE_COMMENT_OUT)}
---
$(P
$(I $(B Note:) Although it is infinite, because the members are of type $(C int), the elements of this Fibonacci series would be wrong beyond $(C int.max).)
)
$(P
Since $(C empty) is always $(C false) for $(C FibonacciSeries) objects, the $(C for) loop in $(C print()) never terminates for them:
)
---
print(FibonacciSeries()); // never terminates
---
$(P
An infinite range is useful when the range need not be consumed completely right away. We will see how to use only some of the elements of a $(C FibonacciSeries) below.
)
$(H6 Functions that return ranges)
$(P
Earlier, we have created a $(C StudentRange) object by explicitly writing $(C StudentRange(school)).
)
$(P
$(IX convenience function) In most cases, a convenience function that returns the object of such a range is used instead. For example, a function with the whole purpose of returning a $(C StudentRange) would simplify the code:
)
---
StudentRange studentsOf(ref School school) {
return StudentRange(school);
}
// ...
// Note: Again, taking advantage of UFCS
print(school.$(HILITE studentsOf));
---
$(P
This is a convenience over having to remember and spell out the names of range types explicitly, which can get quite complicated in practice.
)
$(P
$(IX take, std.range) We can see an example of this with the simple $(C std.range.take) function. $(C take()) is a function that provides access to a specified number of elements of a range, from the beginning. In reality, this functionality is not achieved by the $(C take()) function itself, but by a special range object that it returns. This fact need not be explicit when using $(C take()):
)
---
import std.range;
// ...
auto school = School( [ Student("Ebru", 1),
Student("Derya", 2) ,
Student("Damla", 3) ] );
print(school.studentsOf.$(HILITE take(2)));
---
$(P
$(C take()) returns a temporary range object above, which provides access to the first 2 elements of $(C school). In turn, $(C print()) uses that object and produces the following output:
)
$(SHELL
Ebru(1) Derya(2)
)
$(P
The operations above still don't make any changes to $(C school); it still has 3 elements:
)
---
print(school.studentsOf.take(2));
assert(school.students.length == 3);
---
$(P
The specific types of the range objects that are returned by functions like $(C take()) are not important. These types may sometimes be exposed in error messages, or we can print them ourselves with the help of $(C typeof) and $(C stringof):
)
---
writeln(typeof(school.studentsOf.take(2)).stringof);
---
$(P
According to the output, $(C take()) returns an instance of a template named $(C Take):
)
$(SHELL
Take!(StudentRange)
)
$(H6 $(IX std.range) $(IX std.algorithm) $(C std.range) and $(C std.algorithm) modules)
$(P
A great benefit of defining our types as ranges is being able to use them not only with our own functions, but with Phobos and other libraries as well.
)
$(P
$(C std.range) includes a large number of range functions, structs, and classes. $(C std.algorithm) includes many algorithms that are commonly found also in the standard libraries of other languages.
)
$(P
$(IX swapFront, std.algorithm) To see an example of how our types can be used with standard modules, let's use $(C School) with the $(C std.algorithm.swapFront) algorithm. $(C swapFront()) swaps the front elements of two $(C InputRange) ranges. (It requires that the front elements of the two ranges are swappable. Arrays satisfy that condition.)
)
$(P
)
---
import std.algorithm;
// ...
auto turkishSchool = School( [ Student("Ebru", 1),
Student("Derya", 2) ,
Student("Damla", 3) ] );
auto americanSchool = School( [ Student("Mary", 10),
Student("Jane", 20) ] );
$(HILITE swapFront)(turkishSchool.studentsOf,
americanSchool.studentsOf);
print(turkishSchool.studentsOf);
print(americanSchool.studentsOf);
---
$(P
The first elements of the two schools are swapped:
)
$(SHELL
$(HILITE Mary(10)) Derya(2) Damla(3)
$(HILITE Ebru(1)) Jane(20)
)
$(P
$(IX filter, std.algorithm) As another example, let's now look at the $(C std.algorithm.filter) algorithm. $(C filter()) returns a special range that filters out elements that do not satisfy a specific condition (a $(I predicate)). The operation of filtering out the elements only affects accessing the elements; the original range is preserved.
)
$(P
$(IX predicate) Predicates are expressions that must evaluate to $(C true) for the elements that are considered to satisfy a condition, and $(C false) for the elements that do not. There are a number of ways of specifying the predicate that $(C filter()) should use. As we have seen in earlier examples, one way is to use a lambda expression. The parameter $(C a) below represents each student:
)
---
school.studentsOf.filter!(a => a.number % 2)
---
$(P
The predicate above selects the elements of the range $(C school.studentsOf) that have odd numbers.
)
$(P
Like $(C take()), $(C filter()) returns a special range object as well. That range object in turn can be passed to other range functions. For example, it can be passed to $(C print()):
)
---
print(school.studentsOf.filter!(a => a.number % 2));
---
$(P
That expression can be explained as $(I start with the range $(C school.studentsOf), construct a range object that will filter out the elements of that initial range, and pass the new range object to $(C print())).
)
$(P
The output consists of students with odd numbers:
)
$(SHELL
Ebru(1) Damla(3)
)
$(P
As long as it returns $(C true) for the elements that satisfy the condition, the predicate can also be specified as a function:
)
---
import std.array;
// ...
bool startsWithD(Student student) {
return student.name.front == 'D';
}
print(school.studentsOf.filter!startsWithD);
---
$(P
The predicate function above returns $(C true) for students having names starting with the letter D, and $(C false) for the others.
)
$(P
$(I $(B Note:) Using $(C student.name[0]) would have meant the first UTF-8 code unit, not the first letter. As I have mentioned above, $(C front) uses $(C name) as a range and always returns the first Unicode character.)
)
$(P
This time the students whose names start with D are selected and printed:
)
$(SHELL
Derya(2) Damla(3)
)
$(P
$(IX generate, std.range) $(C generate()), a convenience function template of the $(C std.range) module, makes it easy to present values returned from a function as the elements of an $(C InputRange). It takes any callable entity (function pointer, delegate, etc.) and returns an $(C InputRange) object conceptually consisting of the values that are returned from that callable entity.
)
$(P
The returned range object is infinite. Every time the $(C front) property of that range object is accessed, the original callable entity is called to get a new $(I element) from it. The $(C popFront()) function of the range object does not perform any work.
)
$(P
For example, the following range object $(C diceThrower) can be used as an infinite range:
)
---
import std.stdio;
import std.range;
import std.random;
void main() {
auto diceThrower = $(HILITE generate)!(() => uniform(0, 6));
writeln(diceThrower.take(10));
}
---