Completed
Push — master ( 0835af...9df346 )
by Fabio
06:12
created

TPriorityMap::count()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * TPriorityMap, TPriorityMapIterator classes
4
 *
5
 * @author Brad Anderson <[email protected]>
6
 * @link https://github.com/pradosoft/prado
7
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
8
 * @package Prado\Collections
9
 */
10
11
namespace Prado\Collections;
12
13
use Prado\TPropertyValue;
14
use Prado\Exceptions\TInvalidOperationException;
15
use Prado\Exceptions\TInvalidDataTypeException;
16
17
/**
18
 * TPriorityMap class
19
 *
20
 * TPriorityMap implements a collection that takes key-value pairs with
21
 * a priority to allow key-value pairs to be ordered.  This ordering is
22
 * important when flattening the map. When flattening the map, if some
23
 * key-value pairs are required to be before or after others, use this
24
 * class to keep order to your map.
25
 *
26
 * You can access, add or remove an item with a key by using
27
 * {@link itemAt}, {@link add}, and {@link remove}.  These functions
28
 * can optionally take a priority parameter to allow access to specific
29
 * priorities.  TPriorityMap is functionally backward compatible
30
 * with {@link TMap}.
31
 *
32
 * To get the number of the items in the map, use {@link getCount}.
33
 * TPriorityMap can also be used like a regular array as follows,
34
 * <code>
35
 * $map[$key]=$value; // add a key-value pair
36
 * unset($map[$key]); // remove the value with the specified key
37
 * if(isset($map[$key])) // if the map contains the key
38
 * foreach($map as $key=>$value) // traverse the items in the map
39
 * $n=count($map);  // returns the number of items in the map
40
 * </code>
41
 * Using standard array access method like these will always use
42
 * the default priority.
43
 *
44
 * An item that doesn't specify a priority will receive the default
45
 * priority.  The default priority is set during the instantiation
46
 * of a new TPriorityMap. If no custom default priority is specified,
47
 * the standard default priority of 10 is used.
48
 *
49
 * Priorities with significant digits below precision will be rounded.
50
 *
51
 * A priority may also be a numeric with decimals.  This is set
52
 * during the instantiation of a new TPriorityMap.
53
 * The default is 8 decimal places for a priority.  If a negative number
54
 * is used, rounding occurs into the integer space rather than in
55
 * the decimal space.  See {@link round}.
56
 *
57
 * @author Brad Anderson <[email protected]>
58
 * @package Prado\Collections
59
 * @since 3.2a
60
 */
61
62
class TPriorityMap extends TMap
63
{
64
	/**
65
	 * @var bool indicates if the _d is currently ordered.
66
	 */
67
	private $_o = false;
68
	/**
69
	 * @var array cached flattened internal data storage
70
	 */
71
	private $_fd;
72
	/**
73
	 * @var int number of items contain within the map
74
	 */
75
	private $_c = 0;
76
	/**
77
	 * @var numeric the default priority of items without specified priorities
78
	 */
79
	private $_dp = 10;
80
	/**
81
	 * @var int the precision of the numeric priorities within this priority list.
82
	 */
83
	private $_p = 8;
84
85
	/**
86
	 * Constructor.
87
	 * Initializes the array with an array or an iterable object.
88
	 * @param null|array|Iterator|map|TPriorityMap $data the intial data. Default is null, meaning no initialization.
89
	 * @param bool $readOnly whether the list is read-only
90
	 * @param numeric $defaultPriority the default priority of items without specified priorities.
91
	 * @param int $precision the precision of the numeric priorities
92
	 * @throws TInvalidDataTypeException If data is not null and neither an array nor an iterator.
93
	 */
94 53 View Code Duplication
	public function __construct($data = null, $readOnly = false, $defaultPriority = 10, $precision = 8)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
95
	{
96 53
		if ($data !== null) {
97 4
			$this->copyFrom($data);
98
		}
99 53
		$this->setReadOnly($readOnly);
100 53
		$this->setPrecision($precision);
101 53
		$this->setDefaultPriority($defaultPriority);
102 53
	}
103
104
	/**
105
	 * @return numeric gets the default priority of inserted items without a specified priority
106
	 */
107 53
	public function getDefaultPriority()
108
	{
109 53
		return $this->_dp;
110
	}
111
112
	/**
113
	 * This must be called internally or when instantiated.
114
	 * @param numeric $value sets the default priority of inserted items without a specified priority
115
	 */
116 53
	protected function setDefaultPriority($value)
117
	{
118 53
		$this->_dp = (string) round(TPropertyValue::ensureFloat($value), $this->_p);
0 ignored issues
show
Documentation Bug introduced by
It seems like (string) round(\Prado\TP...oat($value), $this->_p) of type string is incompatible with the declared type object<Prado\Collections\numeric> of property $_dp.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
119 53
	}
120
121
	/**
122
	 * @return int The precision of numeric priorities, defaults to 8
123
	 */
124 2
	public function getPrecision()
125
	{
126 2
		return $this->_p;
127
	}
128
129
	/**
130
	 * This must be called internally or when instantiated.
131
	 * @param int $value The precision of numeric priorities.
132
	 */
133 53
	protected function setPrecision($value)
134
	{
135 53
		$this->_p = TPropertyValue::ensureInteger($value);
136 53
	}
137
138
	/**
139
	 * Returns an iterator for traversing the items in the map.
140
	 * This method is required by the interface \IteratorAggregate.
141
	 * @return Iterator an iterator for traversing the items in the map.
142
	 */
143 2
	public function getIterator()
144
	{
145 2
		return new \ArrayIterator($this->flattenPriorities());
146
	}
147
148
149
	/**
150
	 * Orders the priority list internally.
151
	 */
152 48
	protected function sortPriorities()
153
	{
154 48
		if (!$this->_o) {
155 48
			ksort($this->_d, SORT_NUMERIC);
156 48
			$this->_o = true;
157
		}
158 48
	}
159
160
	/**
161
	 * This flattens the priority map into a flat array [0,...,n-1]
162
	 * @return array array of items in the list in priority and index order
163
	 */
164 42 View Code Duplication
	protected function flattenPriorities()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
165
	{
166 42
		if (is_array($this->_fd)) {
167 34
			return $this->_fd;
168
		}
169
170 42
		$this->sortPriorities();
171 42
		$this->_fd = [];
172 42
		foreach ($this->_d as $priority => $itemsatpriority) {
173 41
			$this->_fd = array_merge($this->_fd, $itemsatpriority);
174
		}
175 42
		return $this->_fd;
176
	}
177
178
	/**
179
	 * @return int the number of items in the map
180
	 */
181 20
	public function getCount()
182
	{
183 20
		return $this->_c;
184
	}
185
186
	/**
187
	 * Gets the number of items at a priority within the map.
188
	 * @param null|numeric $priority optional priority at which to count items.  if no parameter,
189
	 * it will be set to the default {@link getDefaultPriority}
190
	 * @return int the number of items in the map at the specified priority
191
	 */
192 View Code Duplication
	public function getPriorityCount($priority = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
193
	{
194
		if ($priority === null) {
195
			$priority = $this->getDefaultPriority();
196
		}
197
		$priority = (string) round(TPropertyValue::ensureFloat($priority), $this->_p);
198
199
		if (!isset($this->_d[$priority]) || !is_array($this->_d[$priority])) {
200
			return false;
201
		}
202
		return count($this->_d[$priority]);
203
	}
204
205
	/**
206
	 * This returns a list of the priorities within this map, ordered lowest to highest.
207
	 * @return array the array of priority numerics in decreasing priority order
208
	 */
209 3
	public function getPriorities()
210
	{
211 3
		$this->sortPriorities();
212 3
		return array_keys($this->_d);
213
	}
214
215
	/**
216
	 * Returns the keys within the map ordered through the priority of each key-value pair
217
	 * @return array the key list
218
	 */
219 1
	public function getKeys()
220
	{
221 1
		return array_keys($this->flattenPriorities());
222
	}
223
224
	/**
225
	 * Returns the item with the specified key.  If a priority is specified, only items
226
	 * within that specific priority will be selected
227
	 * @param mixed $key the key
228
	 * @param mixed $priority the priority.  null is the default priority, false is any priority,
229
	 * and numeric is a specific priority.  default: false, any priority.
230
	 * @return mixed the element at the offset, null if no element is found at the offset
231
	 */
232 29
	public function itemAt($key, $priority = false)
233
	{
234 29
		if ($priority === false) {
235 29
			$map = $this->flattenPriorities();
236 29
			return $map[$key] ?? null;
237
		} else {
238 1
			if ($priority === null) {
239
				$priority = $this->getDefaultPriority();
240
			}
241 1
			$priority = (string) round(TPropertyValue::ensureFloat($priority), $this->_p);
242 1
			return (isset($this->_d[$priority]) && isset($this->_d[$priority][$key])) ? $this->_d[$priority][$key] : null;
243
		}
244
	}
245
246
	/**
247
	 * This changes an item's priority.  Specify the item and the new priority.
248
	 * This method is exactly the same as {@link offsetGet}.
249
	 * @param mixed $key the key
250
	 * @param null|numeric $priority the priority.  default: null, filled in with the default priority numeric.
251
	 * @return numeric old priority of the item
252
	 */
253 1
	public function setPriorityAt($key, $priority = null)
254
	{
255 1
		if ($priority === null) {
256 1
			$priority = $this->getDefaultPriority();
257
		}
258 1
		$priority = (string) round(TPropertyValue::ensureFloat($priority), $this->_p);
259
260 1
		$oldpriority = $this->priorityAt($key);
261 1
		if ($oldpriority !== false && $oldpriority != $priority) {
262 1
			$value = $this->remove($key, $oldpriority);
263 1
			$this->add($key, $value, $priority);
264
		}
265 1
		return $oldpriority;
266
	}
267
268
	/**
269
	 * Gets all the items at a specific priority.
270
	 * @param null|numeric $priority priority of the items to get.  Defaults to null, filled in with the default priority, if left blank.
271
	 * @return array all items at priority in index order, null if there are no items at that priority
272
	 */
273 3 View Code Duplication
	public function itemsAtPriority($priority = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
274
	{
275 3
		if ($priority === null) {
276
			$priority = $this->getDefaultPriority();
277
		}
278 3
		$priority = (string) round(TPropertyValue::ensureFloat($priority), $this->_p);
279
280 3
		return isset($this->_d[$priority]) ? $this->_d[$priority] : null;
281
	}
282
283
	/**
284
	 * Returns the priority of a particular item within the map.  This searches the map for the item.
285
	 * @param mixed $item item to look for within the map
286
	 * @return numeric priority of the item in the map
287
	 */
288 2
	public function priorityOf($item)
289
	{
290 2
		$this->sortPriorities();
291 2
		foreach ($this->_d as $priority => $items) {
292 2
			if (($index = array_search($item, $items, true)) !== false) {
293 2
				return $priority;
294
			}
295
		}
296 1
		return false;
297
	}
298
299
	/**
300
	 * Retutrns the priority of an item at a particular flattened index.
301
	 * @param int $key index of the item within the map
302
	 * @return numeric priority of the item in the map
303
	 */
304 3
	public function priorityAt($key)
305
	{
306 3
		$this->sortPriorities();
307 3
		foreach ($this->_d as $priority => $items) {
308 3
			if (array_key_exists($key, $items)) {
309 3
				return $priority;
310
			}
311
		}
312 1
		return false;
313
	}
314
315
	/**
316
	 * Adds an item into the map.  A third parameter may be used to set the priority
317
	 * of the item within the map.  Priority is primarily used during when flattening
318
	 * the map into an array where order may be and important factor of the key-value
319
	 * pairs within the array.
320
	 * Note, if the specified key already exists, the old value will be overwritten.
321
	 * No duplicate keys are allowed regardless of priority.
322
	 * @param mixed $key
323
	 * @param mixed $value
324
	 * @param null|numeric $priority default: null, filled in with default priority
325
	 * @throws TInvalidOperationException if the map is read-only
326
	 * @return numeric priority at which the pair was added
327
	 */
328 53
	public function add($key, $value, $priority = null)
329
	{
330 53
		if ($priority === null) {
331 53
			$priority = $this->getDefaultPriority();
332
		}
333 53
		$priority = (string) round(TPropertyValue::ensureFloat($priority), $this->_p);
334
335 53
		if (!$this->_r) {
336 53
			foreach ($this->_d as $innerpriority => $items) {
337 36
				if (array_key_exists($key, $items)) {
338 4
					unset($this->_d[$innerpriority][$key]);
339 4
					$this->_c--;
340 4
					if (count($this->_d[$innerpriority]) === 0) {
341 36
						unset($this->_d[$innerpriority]);
342
					}
343
				}
344
			}
345 53
			if (!isset($this->_d[$priority])) {
346 53
				$this->_d[$priority] = [$key => $value];
347 53
				$this->_o = false;
348
			} else {
349 36
				$this->_d[$priority][$key] = $value;
350
			}
351 53
			$this->_c++;
352 53
			$this->_fd = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $_fd.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
353
		} else {
354 1
			throw new TInvalidOperationException('map_readonly', get_class($this));
355
		}
356 53
		return $priority;
357
	}
358
359
	/**
360
	 * Removes an item from the map by its key. If no priority, or false, is specified
361
	 * then priority is irrelevant. If null is used as a parameter for priority, then
362
	 * the priority will be the default priority.  If a priority is specified, or
363
	 * the default priority is specified, only key-value pairs in that priority
364
	 * will be affected.
365
	 * @param mixed $key the key of the item to be removed
366
	 * @param null|false|numeric $priority priority.  False is any priority, null is the
367
	 * default priority, and numeric is a specific priority
368
	 * @throws TInvalidOperationException if the map is read-only
369
	 * @return mixed the removed value, null if no such key exists.
370
	 */
371 26
	public function remove($key, $priority = false)
372
	{
373 26
		if (!$this->_r) {
374 25
			if ($priority === null) {
375 7
				$priority = $this->getDefaultPriority();
376
			}
377
378 25
			if ($priority === false) {
379 19
				$this->sortPriorities();
380 19
				foreach ($this->_d as $priority => $items) {
381 19
					if (array_key_exists($key, $items)) {
382 19
						$value = $this->_d[$priority][$key];
383 19
						unset($this->_d[$priority][$key]);
384 19
						$this->_c--;
385 19 View Code Duplication
						if (count($this->_d[$priority]) === 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
386 14
							unset($this->_d[$priority]);
387 14
							$this->_o = false;
388
						}
389 19
						$this->_fd = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $_fd.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
390 19
						return $value;
391
					}
392
				}
393 2
				return null;
394
			} else {
395 8
				$priority = (string) round(TPropertyValue::ensureFloat($priority), $this->_p);
396 8
				if (isset($this->_d[$priority]) && (isset($this->_d[$priority][$key]) || array_key_exists($key, $this->_d[$priority]))) {
397 7
					$value = $this->_d[$priority][$key];
398 7
					unset($this->_d[$priority][$key]);
399 7
					$this->_c--;
400 7 View Code Duplication
					if (count($this->_d[$priority]) === 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
401 6
						unset($this->_d[$priority]);
402 6
						$this->_o = false;
403
					}
404 7
					$this->_fd = null;
405 7
					return $value;
406
				} else {
407 1
					return null;
408
				}
409
			}
410
		} else {
411 1
			throw new TInvalidOperationException('map_readonly', get_class($this));
412
		}
413
	}
414
415
	/**
416
	 * Removes all items in the map.  {@link remove} is called on all items.
417
	 */
418 3
	public function clear()
419
	{
420 3
		foreach ($this->_d as $priority => $items) {
421 3
			foreach (array_keys($items) as $key) {
422 3
				$this->remove($key);
423
			}
424
		}
425 3
	}
426
427
	/**
428
	 * @param mixed $key the key
429
	 * @return bool whether the map contains an item with the specified key
430
	 */
431 31
	public function contains($key)
432
	{
433 31
		$map = $this->flattenPriorities();
434 31
		return isset($map[$key]) || array_key_exists($key, $map);
435
	}
436
437
	/**
438
	 * When the map is flattened into an array, the priorities are taken into
439
	 * account and elements of the map are ordered in the array according to
440
	 * their priority.
441
	 * @return array the list of items in array
442
	 */
443 30
	public function toArray()
444
	{
445 30
		return $this->flattenPriorities();
446
	}
447
448
	/**
449
	 * Combines the map elements which have a priority below the parameter value
450
	 * @param numeric $priority the cut-off priority.  All items of priority less than this are returned.
451
	 * @param bool $inclusive whether or not the input cut-off priority is inclusive.  Default: false, not inclusive.
452
	 * @return array the array of priorities keys with values of arrays of items that are below a specified priority.
453
	 *  The priorities are sorted so important priorities, lower numerics, are first.
454
	 */
455 1 View Code Duplication
	public function toArrayBelowPriority($priority, $inclusive = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
456
	{
457 1
		$this->sortPriorities();
458 1
		$items = [];
459 1
		foreach ($this->_d as $itemspriority => $itemsatpriority) {
460 1
			if ((!$inclusive && $itemspriority >= $priority) || $itemspriority > $priority) {
461 1
				break;
462
			}
463 1
			$items = array_merge($items, $itemsatpriority);
464
		}
465 1
		return $items;
466
	}
467
468
	/**
469
	 * Combines the map elements which have a priority above the parameter value
470
	 * @param numeric $priority the cut-off priority.  All items of priority greater than this are returned.
471
	 * @param bool $inclusive whether or not the input cut-off priority is inclusive.  Default: true, inclusive.
472
	 * @return array the array of priorities keys with values of arrays of items that are above a specified priority.
473
	 *  The priorities are sorted so important priorities, lower numerics, are first.
474
	 */
475 1 View Code Duplication
	public function toArrayAbovePriority($priority, $inclusive = true)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
476
	{
477 1
		$this->sortPriorities();
478 1
		$items = [];
479 1
		foreach ($this->_d as $itemspriority => $itemsatpriority) {
480 1
			if ((!$inclusive && $itemspriority <= $priority) || $itemspriority < $priority) {
481 1
				continue;
482
			}
483 1
			$items = array_merge($items, $itemsatpriority);
484
		}
485 1
		return $items;
486
	}
487
488
	/**
489
	 * Copies iterable data into the map.
490
	 * Note, existing data in the map will be cleared first.
491
	 * @param array|TPriorityMap|Traversable $data the data to be copied from, must be an array, object implementing
492
	 * @throws TInvalidDataTypeException If data is neither an array nor an iterator.
493
	 */
494 6 View Code Duplication
	public function copyFrom($data)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
495
	{
496 6
		if ($data instanceof TPriorityMap) {
497 2
			if ($this->getCount() > 0) {
498 1
				$this->clear();
499
			}
500 2
			foreach ($data->getPriorities() as $priority) {
501 2
				foreach ($data->itemsAtPriority($priority) as $key => $value) {
502 2
					$this->add($key, $value, $priority);
503
				}
504
			}
505 5
		} elseif (is_array($data) || $data instanceof \Traversable) {
506 5
			if ($this->getCount() > 0) {
507 1
				$this->clear();
508
			}
509 5
			foreach ($data as $key => $value) {
510 5
				$this->add($key, $value);
511
			}
512 1
		} elseif ($data !== null) {
513 1
			throw new TInvalidDataTypeException('map_data_not_iterable');
514
		}
515 6
	}
516
517
	/**
518
	 * Merges iterable data into the map.
519
	 * Existing data in the map will be kept and overwritten if the keys are the same.
520
	 * @param array|TPriorityMap|Traversable $data the data to be merged with, must be an array,
521
	 * object implementing Traversable, or a TPriorityMap
522
	 * @throws TInvalidDataTypeException If data is neither an array nor an iterator.
523
	 */
524 2 View Code Duplication
	public function mergeWith($data)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
525
	{
526 2
		if ($data instanceof TPriorityMap) {
527 1
			foreach ($data->getPriorities() as $priority) {
528 1
				foreach ($data->itemsAtPriority($priority) as $key => $value) {
529 1
					$this->add($key, $value, $priority);
530
				}
531
			}
532 1
		} elseif (is_array($data) || $data instanceof \Traversable) {
533 1
			foreach ($data as $key => $value) {
534 1
				$this->add($key, $value);
535
			}
536 1
		} elseif ($data !== null) {
537 1
			throw new TInvalidDataTypeException('map_data_not_iterable');
538
		}
539 2
	}
540
}
541