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