-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathNumberRange.php
More file actions
344 lines (326 loc) · 10.7 KB
/
Copy pathNumberRange.php
File metadata and controls
344 lines (326 loc) · 10.7 KB
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
<?php
/**
* PHP version of the PERL module Number::Range
*
* @author Mark Mitchell (mmitchell@riccagroup.com)
* @see http://search.cpan.org/dist/Number-Range/
*
* Number::Range will take a description of a range, and then allow you to
* test on if a number falls within the range. You can also add and delete from
* the range.
*
* @example RANGE FORMAT
*
* As a single string: '1..2'
* As multiple strings: '"1..2","3..4"'
* As a single srting of ranges comma seperated: '1..2,3..4,-10..-5'
*
* The format used for range is pretty straight forward. To separate sections of
* ranges it uses a , or whitespace. To create the range, it uses .. to do this,
* much like Perl's own binary .. range operator in list context.
*
*/
class NumberRange {
/**
* @var string
* Based off of PERL module version
*/
public $VERSION = '0.09';
/**
* @var array
* Range hash computed from the range input
*/
private $_rangehash = array();
/**
* @var boolean
* True if the range is a negated range
*/
public $negatedRange = false;
/**
* Class constuctor
*
* @param $range string Range string ie(1..2) || "1..2","3..4" || '1..2,3..4,-10..-5'
* @return void
*/
public function __construct($range) {
# Max size of range before its stored as a pointer instead of hashed
$this->{max_hash_size} = 1000;
$this->initialize("add", array($range));
}
/**
* Set the maximum hash size that will be stored.
* All other ranges are stored as "hash pointers"
*
* @param integer $size max_hash_size value
* @return mixed false if $size is non numberic, otherwise the new max_hash_size value
*/
public function set_max_hash_size($size) {
if(!is_numeric($size)) { return false; }
$this->{max_hash_size} = $size;
return $this->{max_hash_size};
}
/**
* Range initialize
*
* @param $type Range type ('add' || 'del')
* @param $range Range string
* @return void
*/
public function initialize($type, $range) {
if (is_string($range[0])) {
$this->negatedRange = preg_match('/^[\!,N,n]/', $range[0]);
}
// Clean up number ranges that use a dash range seperator
foreach($range as $i=>$v) {
if(!is_string($v)) { continue; } // Next if not not string
$range[$i] = $this->cleanDashedRange($v);
}
$rangesep = '/(?-xism:(?:\.\.))/';
$sectsep = '/(?-xism:(?:\s|,))/';
$validate = '/(?x-ism:(?:[^0-9,. -]|(?-xism:(?:\.\.))(?-xism:(?:\s|,))|(?-xism:(?:\s|,))(?-xism:(?:\.\.))|\d-\d|^(?-xism:(?:\s|,))|^(?-xism:(?:\.\.))|(?-xism:(?:\s|,))$|(?-xism:(?:\.\.))$))/';
foreach ($range as $item) {
foreach (preg_split($sectsep, $item) as $section) {
if (preg_match($rangesep,$section)) {
list ($start, $end) = preg_split($rangesep, $section, 2);
if ($start > $end) {
trigger_error("$start is > $end",E_NOTICE);
list ($start, $end) = array($end, $start);
}
if ($start == $end) {
trigger_error("$start:$end is pointless",E_NOTICE);
if ($type == "add") {
$this->_addnumbers($start);
}
elseif ($type == "del") {
$this->_delnumbers($start);
}
else {
trigger_error("Neither 'add' nor 'del' was passed initialize()",E_ERROR);
}
} else {
if ($type == "add") {
if(($end - $start) > $this->{max_hash_size}) {
$this->_addrange($start,$end);
} else {
$this->_addnumbers(range($start,$end));
}
} elseif ($type == "del") {
if($end - $start > $this->{max_hash_size}) {
$this->_delrange($start,$end);
} else {
$this->_delnumbers(range($start,$end));
}
} else {
trigger_error("Neither 'add' nor 'del' was passed initialize()",E_ERROR);
}
}
} else {
if ($type == "add") {
$this->_addnumbers($section);
}
elseif ($type == "del") {
$this->_delnumbers($section);
} else {
trigger_error( "Neither 'add' nor 'del' was passed initialize()",E_ERROR);
}
}
}
}
}
/**
* Add a range thats stored as a pointer
*
* @param integer $start Range start
* @param integer $end Range end
* @return void
*/
private function _addrange($start,$end) {
$this->{_largeRangehash}{"$start .. $end"} = array($start, $end);
}
/**
* Remove a range thats stored as a pointer
*
* @param integer $start Range start
* @param integer $end Range end
* @return void
*/
private function _delrange($start,$end) {
unset($this->{_largeRangehash}{"$start .. $end"});
}
/**
* Test to see if a value is in the large range hash
*
* @param integer $test Value to test for
* @return boolean True if its in any of the large ranges, otherwise false
*/
public function _testlarge($test) {
if(!isset($this->{_largeRangehash})) {
return 0;
}
foreach($this->{_largeRangehash} as $rangeID=>$range) {
if ($test >= $range[0]
&& $test <= $range[1]) {
return 1;
}
}
return 0;
}
/**
* Add number to the range array
*
* @param array $numbers Numbers to add
* @return void
*/
private function _addnumbers($numbers) {
if(is_numeric($numbers)) {
$numbers = array($numbers);
}
foreach ($numbers as $number) {
$this->_rangehash[$number] = 1;
}
}
/**
* Remove numbers from the range array
*
* @param array $numbers Numbers to remove
* @return void
*/
private function _delnumbers() {
foreach ($numbers as $number) {
unset($this->_rangehash[$number]);
}
}
/**
* Test if a number is in the range
*
* @param mixed either a single number, an array or multiple numbers each
* passed as their own parameter
*
* @example inrange(1)
* Return true if the number is in the range, otherwise false
*
* @example inrange(array(1,2))
* Returns an array where each values results in the same array position
* array(true,false)
*
* @example inrange(1,2,3)
* Returns true if all numbers are in the range, otherwise false
*
* @return mixed (true||false for single elements or array of true/false results for each input)
*/
public function inrange() {
$args = func_get_args();
if (sizeof($args) == 1) {
if ( !empty($this->_rangehash[$args[0]])
|| $this->_testlarge($args[0])) {
return 1;
} else {
return 0;
}
} else {
if (is_array($args)) {
$returncodes;
foreach ($args as $test) {
array_push($returncodes, ($this->inrange($test)) ? true : false);
}
return $returncodes;
} else {
foreach ($args as $test) {
if (!$this->inrange($test)) {
return true;
}
return false;
}
}
}
}
/**
* Add a value to the range
*
* @param $range Range string
* @return void
*/
public function addrange($range) {
$this->initialize("add", $range);
}
/**
* Remove a value from the range
*
* @param $range Range string
* @return void
*/
public function delrange($range) {
$this->initialize("del", $range);
}
/**
* Returns the range as a scalar value or as an array
*
* @param boolean $wantArray If true the returns the range as an array
* @return mixed Range as a string or as an array
*/
public function range($wantArray = false) {
if ($wantArray) {
$range = array_keys($this->_rangehash);
if(isset($this->{_largeRangehash})) {
foreach($this->{_largeRangehash} as $rangeID=>$range) {
if ( $range[0] > PHP_INT_MAX
|| $range[1] > PHP_INT_MAX
|| ( $range[1] - @$range[0]) > PHP_INT_MAX ) {
trigger_error("Range to large to return", E_NOTICE);
return 0;
}
$range = array_merge($range, range($range[0],$range[1]));
}
}
sort($range);
return $range;
} else {
$range = $this->range(true);
$previous = array_shift($range);
$format = "$previous";
foreach ($range as $current) {
if ($current == ($previous + 1)) {
$format = preg_replace("/\.\.$previous$/",'', $format);
$format .= "..$current";
} else {
$format .= ",$current";
}
$previous = $current;
}
$negated = null;
if($this->negated()) {
$negated = '!';
}
return $negated.$format;
}
}
/**
* Returns the size of the range
*/
public function size() {
$size = sizeof($this->range(true));
if(isset($this->{_largeRangehash})) {
foreach($this->{_largeRangehash} as $rangeID=>$range) {
$size += ($range[1] - $range[0]) + 1;
}
}
return $size;
}
/**
* Returns true if this is a negated range
*/
public function negated() {
return $this->negatedRange;
}
/**
* Convert range formats "1-10" to "1..10"
*
* @param string $range Range to clean up that uses the format "#-#"
* @return string Range cleaned up as "#..#"
*/
public function cleanDashedRange($range) {
$range = preg_replace('/(\d+)-(\d+)/','\1..\2',$range);
return $range;
}
}
?>