Line data Source code
1 : // Copyright 2026 The Authors
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // https://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : /// Handy function for grabbing a [Range] and iterating over it.
16 : ///
17 : /// ```dart
18 : /// for (var i in range(stop: 10)) {
19 : /// print(i);
20 : /// }
21 : /// ```
22 1 : Iterable<num> range({
23 : num start = 0,
24 : required num stop,
25 : num step = 1,
26 : bool stopExclusive = true,
27 : }) {
28 1 : return Range(
29 : start: start,
30 : stop: stop,
31 : step: step,
32 : stopExclusive: stopExclusive,
33 1 : ).generate();
34 : }
35 :
36 : /// A numerical progression.
37 : ///
38 : /// For basic usage, just provide the (exclusive) [stop] value - note that,
39 : /// by default, the range is _exclusive_ of the stop value:
40 : ///
41 : /// ```dart
42 : /// final range = Range(stop:5);
43 : /// print(range.toList());
44 : /// ```
45 : ///
46 : /// Result: `[0, 1, 2, 3, 4]`
47 : ///
48 : /// Providing a [start] value allows for a non-zero starting point:
49 : ///
50 : /// ```dart
51 : /// final range = Range(start: 1, stop:5);
52 : /// print(range.toList());
53 : /// ```
54 : ///
55 : /// Result: `[1, 2, 3, 4]`
56 : ///
57 : /// Consider using the [range()] function if you just want to use a one-off
58 : /// range in a loop.
59 : class Range {
60 : /// The starting value of the range (inclusive), defaults to 0
61 : final num start;
62 :
63 : /// The end value of the range (exclusive)
64 : final num stop;
65 :
66 : /// The step progression, defaults to 1. Must be greater than 0
67 : final num step;
68 :
69 : /// If false, the [stop] value is inclusive. Defaults to true
70 : final bool stopExclusive;
71 :
72 : /// Range constructor
73 : ///
74 : /// Create a range that begins at [start]. By default, [start] is 0.
75 : ///
76 : /// Ordinarily, the Range will end at [stop], exclusive of [stop].
77 : /// If [stopExclusive] is false, the value of [stop] is included in the range.
78 : ///
79 : /// The [step] determines the amount by which the range progresses on each
80 : /// iteration. By default [step] is 1.
81 : ///
82 : /// ```dart
83 : /// final r = range(stop: 10, step: 2);
84 : /// print(r.toList());
85 : /// ```
86 : ///
87 : /// Result: `[0, 2, 4, 6, 8]`
88 : ///
89 : /// [step] is always a positive number - an [ArgumentError] is thrown
90 : /// if [step] is <= 0.
91 : ///
92 : /// If the range is moving backwards (e.g. 10 -> 0), [Range] will correctly
93 : /// step in the reverse direction.
94 : ///
95 : /// For example, [start] at 10 and [stop] before 0, with a [step] of 2:
96 : ///
97 : /// ```dart
98 : /// final r = range(start: 10, stop: 0, step: 2);
99 : /// print(r.toList());
100 : /// ```
101 : ///
102 : /// Result: `[10, 8, 6, 4, 2]`
103 : ///
104 : /// To get to 0, set [stopExclusive] to `false`:
105 : ///
106 : /// ```dart
107 : /// final r = range(start: 10, stop: 0, step: 2, stopExclusive: false);
108 : /// print(r.toList());
109 : /// ```
110 : ///
111 : /// Result: `[10, 8, 6, 4, 2, 0]`
112 2 : Range({
113 : this.start = 0,
114 : required this.stop,
115 : this.step = 1,
116 : this.stopExclusive = true,
117 : }) {
118 6 : if (step <= 0) throw ArgumentError.value(step, 'step');
119 : }
120 :
121 : /// Generate all list of all values in the range.
122 : ///
123 : /// Note that this method creates a new list each call.
124 : /// Consider maintaining a copy of the result in your own code
125 : /// rather than calling [toList] multiple times.
126 : ///
127 : /// _Why not cache the result in the object?_
128 : /// The range could be quite large.
129 2 : List<num> toList() {
130 2 : final result = <num>[];
131 :
132 4 : for (var i in generate()) {
133 2 : result.add(i);
134 : }
135 :
136 : return result;
137 : }
138 :
139 : /// Generate the values for the range.
140 : ///
141 : /// Handy with `for` loops.
142 : ///
143 : /// Example:
144 : ///
145 : /// ```dart
146 : /// final list = <int>[];
147 : /// final range = Range(stop: 10);
148 : /// for (var i in range.generate()) {
149 : /// list.add(i);
150 : /// }
151 : /// print(list);
152 : /// ```
153 : ///
154 : /// Result: `[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]`
155 2 : Iterable<num> generate() sync* {
156 6 : if (start <= stop) {
157 10 : final lastValue = stopExclusive ? stop : stop + step;
158 8 : for (num i = start; i < lastValue; i += step) {
159 : yield i;
160 : }
161 : } else {
162 5 : final lastValue = stopExclusive ? stop : stop - step;
163 4 : for (num i = start; i > lastValue; i -= step) {
164 : yield i;
165 : }
166 : }
167 : }
168 :
169 : /// True if [input] is between [start] (inclusive) and [stop].
170 : ///
171 : /// If [stopExclusive] is false, [stop] is included in the range,
172 : /// otherwise it is not.
173 : ///
174 : /// Note that [step] is considered in the evaluation - the [input]
175 : /// must be an increment of the range.
176 : ///
177 : /// As this method calls [toList], it can be expensive and you
178 : /// should consider caching the result of [toList] and calling
179 : /// that list's `contains` method.
180 6 : bool contains(num input) => toList().contains(input);
181 :
182 2 : @override
183 : bool operator ==(Object other) {
184 2 : if (other is Range) {
185 6 : return other.start == start &&
186 6 : other.stop == stop &&
187 6 : other.step == step &&
188 6 : other.stopExclusive == stopExclusive;
189 : }
190 : return false;
191 : }
192 :
193 2 : @override
194 10 : int get hashCode => Object.hash(start, stop, step, stopExclusive);
195 :
196 4 : Map<String, dynamic> toMap() => {
197 2 : 'start': start,
198 2 : 'stop': stop,
199 2 : 'step': step,
200 2 : 'stopExclusive': stopExclusive,
201 : };
202 : }
|