UNPKG

8.32 kBJavaScriptView Raw
1'use strict';
2
3var number = require('../../utils/number');
4
5function factory(type, config, load, typed) {
6 /**
7 * Create a range. A range has a start, step, and end, and contains functions
8 * to iterate over the range.
9 *
10 * A range can be constructed as:
11 *
12 * const range = new Range(start, end)
13 * const range = new Range(start, end, step)
14 *
15 * To get the result of the range:
16 * range.forEach(function (x) {
17 * console.log(x)
18 * })
19 * range.map(function (x) {
20 * return math.sin(x)
21 * })
22 * range.toArray()
23 *
24 * Example usage:
25 *
26 * const c = new Range(2, 6) // 2:1:5
27 * c.toArray() // [2, 3, 4, 5]
28 * const d = new Range(2, -3, -1) // 2:-1:-2
29 * d.toArray() // [2, 1, 0, -1, -2]
30 *
31 * @class Range
32 * @constructor Range
33 * @param {number} start included lower bound
34 * @param {number} end excluded upper bound
35 * @param {number} [step] step size, default value is 1
36 */
37 function Range(start, end, step) {
38 if (!(this instanceof Range)) {
39 throw new SyntaxError('Constructor must be called with the new operator');
40 }
41
42 var hasStart = start !== null && start !== undefined;
43 var hasEnd = end !== null && end !== undefined;
44 var hasStep = step !== null && step !== undefined;
45
46 if (hasStart) {
47 if (type.isBigNumber(start)) {
48 start = start.toNumber();
49 } else if (typeof start !== 'number') {
50 throw new TypeError('Parameter start must be a number');
51 }
52 }
53 if (hasEnd) {
54 if (type.isBigNumber(end)) {
55 end = end.toNumber();
56 } else if (typeof end !== 'number') {
57 throw new TypeError('Parameter end must be a number');
58 }
59 }
60 if (hasStep) {
61 if (type.isBigNumber(step)) {
62 step = step.toNumber();
63 } else if (typeof step !== 'number') {
64 throw new TypeError('Parameter step must be a number');
65 }
66 }
67
68 this.start = hasStart ? parseFloat(start) : 0;
69 this.end = hasEnd ? parseFloat(end) : 0;
70 this.step = hasStep ? parseFloat(step) : 1;
71 }
72
73 /**
74 * Attach type information
75 */
76 Range.prototype.type = 'Range';
77 Range.prototype.isRange = true;
78
79 /**
80 * Parse a string into a range,
81 * The string contains the start, optional step, and end, separated by a colon.
82 * If the string does not contain a valid range, null is returned.
83 * For example str='0:2:11'.
84 * @memberof Range
85 * @param {string} str
86 * @return {Range | null} range
87 */
88 Range.parse = function (str) {
89 if (typeof str !== 'string') {
90 return null;
91 }
92
93 var args = str.split(':');
94 var nums = args.map(function (arg) {
95 return parseFloat(arg);
96 });
97
98 var invalid = nums.some(function (num) {
99 return isNaN(num);
100 });
101 if (invalid) {
102 return null;
103 }
104
105 switch (nums.length) {
106 case 2:
107 return new Range(nums[0], nums[1]);
108 case 3:
109 return new Range(nums[0], nums[2], nums[1]);
110 default:
111 return null;
112 }
113 };
114
115 /**
116 * Create a clone of the range
117 * @return {Range} clone
118 */
119 Range.prototype.clone = function () {
120 return new Range(this.start, this.end, this.step);
121 };
122
123 /**
124 * Retrieve the size of the range.
125 * Returns an array containing one number, the number of elements in the range.
126 * @memberof Range
127 * @returns {number[]} size
128 */
129 Range.prototype.size = function () {
130 var len = 0;
131 var start = this.start;
132 var step = this.step;
133 var end = this.end;
134 var diff = end - start;
135
136 if (number.sign(step) === number.sign(diff)) {
137 len = Math.ceil(diff / step);
138 } else if (diff === 0) {
139 len = 0;
140 }
141
142 if (isNaN(len)) {
143 len = 0;
144 }
145 return [len];
146 };
147
148 /**
149 * Calculate the minimum value in the range
150 * @memberof Range
151 * @return {number | undefined} min
152 */
153 Range.prototype.min = function () {
154 var size = this.size()[0];
155
156 if (size > 0) {
157 if (this.step > 0) {
158 // positive step
159 return this.start;
160 } else {
161 // negative step
162 return this.start + (size - 1) * this.step;
163 }
164 } else {
165 return undefined;
166 }
167 };
168
169 /**
170 * Calculate the maximum value in the range
171 * @memberof Range
172 * @return {number | undefined} max
173 */
174 Range.prototype.max = function () {
175 var size = this.size()[0];
176
177 if (size > 0) {
178 if (this.step > 0) {
179 // positive step
180 return this.start + (size - 1) * this.step;
181 } else {
182 // negative step
183 return this.start;
184 }
185 } else {
186 return undefined;
187 }
188 };
189
190 /**
191 * Execute a callback function for each value in the range.
192 * @memberof Range
193 * @param {function} callback The callback method is invoked with three
194 * parameters: the value of the element, the index
195 * of the element, and the Range being traversed.
196 */
197 Range.prototype.forEach = function (callback) {
198 var x = this.start;
199 var step = this.step;
200 var end = this.end;
201 var i = 0;
202
203 if (step > 0) {
204 while (x < end) {
205 callback(x, [i], this);
206 x += step;
207 i++;
208 }
209 } else if (step < 0) {
210 while (x > end) {
211 callback(x, [i], this);
212 x += step;
213 i++;
214 }
215 }
216 };
217
218 /**
219 * Execute a callback function for each value in the Range, and return the
220 * results as an array
221 * @memberof Range
222 * @param {function} callback The callback method is invoked with three
223 * parameters: the value of the element, the index
224 * of the element, and the Matrix being traversed.
225 * @returns {Array} array
226 */
227 Range.prototype.map = function (callback) {
228 var array = [];
229 this.forEach(function (value, index, obj) {
230 array[index[0]] = callback(value, index, obj);
231 });
232 return array;
233 };
234
235 /**
236 * Create an Array with a copy of the Ranges data
237 * @memberof Range
238 * @returns {Array} array
239 */
240 Range.prototype.toArray = function () {
241 var array = [];
242 this.forEach(function (value, index) {
243 array[index[0]] = value;
244 });
245 return array;
246 };
247
248 /**
249 * Get the primitive value of the Range, a one dimensional array
250 * @memberof Range
251 * @returns {Array} array
252 */
253 Range.prototype.valueOf = function () {
254 // TODO: implement a caching mechanism for range.valueOf()
255 return this.toArray();
256 };
257
258 /**
259 * Get a string representation of the range, with optional formatting options.
260 * Output is formatted as 'start:step:end', for example '2:6' or '0:0.2:11'
261 * @memberof Range
262 * @param {Object | number | function} [options] Formatting options. See
263 * lib/utils/number:format for a
264 * description of the available
265 * options.
266 * @returns {string} str
267 */
268 Range.prototype.format = function (options) {
269 var str = number.format(this.start, options);
270
271 if (this.step !== 1) {
272 str += ':' + number.format(this.step, options);
273 }
274 str += ':' + number.format(this.end, options);
275 return str;
276 };
277
278 /**
279 * Get a string representation of the range.
280 * @memberof Range
281 * @returns {string}
282 */
283 Range.prototype.toString = function () {
284 return this.format();
285 };
286
287 /**
288 * Get a JSON representation of the range
289 * @memberof Range
290 * @returns {Object} Returns a JSON object structured as:
291 * `{"mathjs": "Range", "start": 2, "end": 4, "step": 1}`
292 */
293 Range.prototype.toJSON = function () {
294 return {
295 mathjs: 'Range',
296 start: this.start,
297 end: this.end,
298 step: this.step
299 };
300 };
301
302 /**
303 * Instantiate a Range from a JSON object
304 * @memberof Range
305 * @param {Object} json A JSON object structured as:
306 * `{"mathjs": "Range", "start": 2, "end": 4, "step": 1}`
307 * @return {Range}
308 */
309 Range.fromJSON = function (json) {
310 return new Range(json.start, json.end, json.step);
311 };
312
313 return Range;
314}
315
316exports.name = 'Range';
317exports.path = 'type';
318exports.factory = factory;
\No newline at end of file