3 * TPriorityMap, TPriorityMapIterator classes
5 * @author Brad Anderson <javalizard@mac.com>
6 * @link http://www.pradosoft.com/
7 * @copyright Copyright © 2005-2014 PradoSoft
8 * @license http://www.pradosoft.com/license/
9 * @package System.Collections
15 * TPriorityMap implements a collection that takes key-value pairs with
16 * a priority to allow key-value pairs to be ordered. This ordering is
17 * important when flattening the map. When flattening the map, if some
18 * key-value pairs are required to be before or after others, use this
19 * class to keep order to your map.
21 * You can access, add or remove an item with a key by using
22 * {@link itemAt}, {@link add}, and {@link remove}. These functions
23 * can optionally take a priority parameter to allow access to specific
24 * priorities. TPriorityMap is functionally backward compatible
27 * To get the number of the items in the map, use {@link getCount}.
28 * TPriorityMap can also be used like a regular array as follows,
30 * $map[$key]=$value; // add a key-value pair
31 * unset($map[$key]); // remove the value with the specified key
32 * if(isset($map[$key])) // if the map contains the key
33 * foreach($map as $key=>$value) // traverse the items in the map
34 * $n=count($map); // returns the number of items in the map
36 * Using standard array access method like these will always use
37 * the default priority.
39 * An item that doesn't specify a priority will receive the default
40 * priority. The default priority is set during the instantiation
41 * of a new TPriorityMap. If no custom default priority is specified,
42 * the standard default priority of 10 is used.
44 * Priorities with significant digits below precision will be rounded.
46 * A priority may also be a numeric with decimals. This is set
47 * during the instantiation of a new TPriorityMap.
48 * The default is 8 decimal places for a priority. If a negative number
49 * is used, rounding occurs into the integer space rather than in
50 * the decimal space. See {@link round}.
52 * @author Brad Anderson <javalizard@mac.com>
53 * @package System.Collections
56 Prado::using('System.Collections.TMap');
58 class TPriorityMap extends TMap
61 * @var array internal data storage
65 * @var boolean whether this list is read-only
69 * @var boolean indicates if the _d is currently ordered.
73 * @var array cached flattened internal data storage
77 * @var integer number of items contain within the map
81 * @var numeric the default priority of items without specified priorities
85 * @var integer the precision of the numeric priorities within this priority list.
91 * Initializes the array with an array or an iterable object.
92 * @param map|array|Iterator|TPriorityMap the intial data. Default is null, meaning no initialization.
93 * @param boolean whether the list is read-only
94 * @param numeric the default priority of items without specified priorities.
95 * @param integer the precision of the numeric priorities
96 * @throws TInvalidDataTypeException If data is not null and neither an array nor an iterator.
98 public function __construct($data=null,$readOnly=false,$defaultPriority=10,$precision=8)
101 $this->copyFrom($data);
102 $this->setReadOnly($readOnly);
103 $this->setPrecision($precision);
104 $this->setDefaultPriority($defaultPriority);
108 * @return boolean whether this map is read-only or not. Defaults to false.
110 public function getReadOnly()
116 * @param boolean whether this list is read-only or not
118 protected function setReadOnly($value)
120 $this->_r=TPropertyValue::ensureBoolean($value);
124 * @return numeric gets the default priority of inserted items without a specified priority
126 public function getDefaultPriority()
132 * This must be called internally or when instantiated.
133 * @param numeric sets the default priority of inserted items without a specified priority
135 protected function setDefaultPriority($value)
137 $this->_dp = (string)round(TPropertyValue::ensureFloat($value), $this->_p);
141 * @return integer The precision of numeric priorities, defaults to 8
143 public function getPrecision()
149 * This must be called internally or when instantiated.
150 * @param integer The precision of numeric priorities.
152 protected function setPrecision($value)
154 $this->_p=TPropertyValue::ensureInteger($value);
158 * Returns an iterator for traversing the items in the map.
159 * This method is required by the interface IteratorAggregate.
160 * @return Iterator an iterator for traversing the items in the map.
162 public function getIterator()
164 return new ArrayIterator($this->flattenPriorities());
169 * Orders the priority list internally.
171 protected function sortPriorities() {
173 ksort($this->_d, SORT_NUMERIC);
179 * This flattens the priority map into a flat array [0,...,n-1]
180 * @return array array of items in the list in priority and index order
182 protected function flattenPriorities() {
183 if(is_array($this->_fd))
186 $this->sortPriorities();
187 $this->_fd = array();
188 foreach($this->_d as $priority => $itemsatpriority)
189 $this->_fd = array_merge($this->_fd, $itemsatpriority);
194 * Returns the number of items in the map.
195 * This method is required by Countable interface.
196 * @return integer number of items in the map.
198 public function count()
200 return $this->getCount();
204 * @return integer the number of items in the map
206 public function getCount()
212 * Gets the number of items at a priority within the map.
213 * @param numeric optional priority at which to count items. if no parameter,
214 * it will be set to the default {@link getDefaultPriority}
215 * @return integer the number of items in the map at the specified priority
217 public function getPriorityCount($priority=null)
220 $priority=$this->getDefaultPriority();
221 $priority=(string)round(TPropertyValue::ensureFloat($priority),$this->_p);
223 if(!isset($this->_d[$priority])||!is_array($this->_d[$priority]))
225 return count($this->_d[$priority]);
229 * This returns a list of the priorities within this map, ordered lowest to highest.
230 * @return array the array of priority numerics in decreasing priority order
232 public function getPriorities()
234 $this->sortPriorities();
235 return array_keys($this->_d);
239 * Returns the keys within the map ordered through the priority of each key-value pair
240 * @return array the key list
242 public function getKeys()
244 return array_keys($this->flattenPriorities());
248 * Returns the item with the specified key. If a priority is specified, only items
249 * within that specific priority will be selected
250 * @param mixed the key
251 * @param mixed the priority. null is the default priority, false is any priority,
252 * and numeric is a specific priority. default: false, any priority.
253 * @return mixed the element at the offset, null if no element is found at the offset
255 public function itemAt($key,$priority=false)
257 if($priority===false){
258 $map=$this->flattenPriorities();
259 return isset($map[$key])?$map[$key]:null;
262 $priority=$this->getDefaultPriority();
263 $priority=(string)round(TPropertyValue::ensureFloat($priority),$this->_p);
264 return (isset($this->_d[$priority])&&isset($this->_d[$priority][$key]))?$this->_d[$priority][$key]:null;
269 * This changes an item's priority. Specify the item and the new priority.
270 * This method is exactly the same as {@link offsetGet}.
271 * @param mixed the key
272 * @param numeric|null the priority. default: null, filled in with the default priority numeric.
273 * @return numeric old priority of the item
275 public function setPriorityAt($key,$priority=null)
278 $priority=$this->getDefaultPriority();
279 $priority=(string)round(TPropertyValue::ensureFloat($priority),$this->_p);
281 $oldpriority=$this->priorityAt($key);
282 if($oldpriority!==false&&$oldpriority!=$priority) {
283 $value=$this->remove($key,$oldpriority);
284 $this->add($key,$value,$priority);
290 * Gets all the items at a specific priority.
291 * @param numeric priority of the items to get. Defaults to null, filled in with the default priority, if left blank.
292 * @return array all items at priority in index order, null if there are no items at that priority
294 public function itemsAtPriority($priority=null)
297 $priority=$this->getDefaultPriority();
298 $priority=(string)round(TPropertyValue::ensureFloat($priority),$this->_p);
300 return isset($this->_d[$priority])?$this->_d[$priority]:null;
304 * Returns the priority of a particular item within the map. This searches the map for the item.
305 * @param mixed item to look for within the map
306 * @return numeric priority of the item in the map
308 public function priorityOf($item)
310 $this->sortPriorities();
311 foreach($this->_d as $priority=>$items)
312 if(($index=array_search($item,$items,true))!==false)
318 * Retutrns the priority of an item at a particular flattened index.
319 * @param integer index of the item within the map
320 * @return numeric priority of the item in the map
322 public function priorityAt($key)
324 $this->sortPriorities();
325 foreach($this->_d as $priority=>$items)
326 if(array_key_exists($key,$items))
332 * Adds an item into the map. A third parameter may be used to set the priority
333 * of the item within the map. Priority is primarily used during when flattening
334 * the map into an array where order may be and important factor of the key-value
335 * pairs within the array.
336 * Note, if the specified key already exists, the old value will be overwritten.
337 * No duplicate keys are allowed regardless of priority.
340 * @param numeric|null priority, default: null, filled in with default priority
341 * @return numeric priority at which the pair was added
342 * @throws TInvalidOperationException if the map is read-only
344 public function add($key,$value,$priority=null)
347 $priority=$this->getDefaultPriority();
348 $priority=(string)round(TPropertyValue::ensureFloat($priority),$this->_p);
352 foreach($this->_d as $innerpriority=>$items)
353 if(array_key_exists($key,$items))
355 unset($this->_d[$innerpriority][$key]);
357 if(count($this->_d[$innerpriority])===0)
358 unset($this->_d[$innerpriority]);
360 if(!isset($this->_d[$priority])) {
361 $this->_d[$priority]=array($key=>$value);
365 $this->_d[$priority][$key]=$value;
370 throw new TInvalidOperationException('map_readonly',get_class($this));
375 * Removes an item from the map by its key. If no priority, or false, is specified
376 * then priority is irrelevant. If null is used as a parameter for priority, then
377 * the priority will be the default priority. If a priority is specified, or
378 * the default priority is specified, only key-value pairs in that priority
380 * @param mixed the key of the item to be removed
381 * @param numeric|false|null priority. False is any priority, null is the
382 * default priority, and numeric is a specific priority
383 * @return mixed the removed value, null if no such key exists.
384 * @throws TInvalidOperationException if the map is read-only
386 public function remove($key,$priority=false)
391 $priority=$this->getDefaultPriority();
393 if($priority===false)
395 $this->sortPriorities();
396 foreach($this->_d as $priority=>$items)
397 if(array_key_exists($key,$items))
399 $value=$this->_d[$priority][$key];
400 unset($this->_d[$priority][$key]);
402 if(count($this->_d[$priority])===0)
404 unset($this->_d[$priority]);
414 $priority=(string)round(TPropertyValue::ensureFloat($priority),$this->_p);
415 if(isset($this->_d[$priority])&&(isset($this->_d[$priority][$key])||array_key_exists($key,$this->_d[$priority])))
417 $value=$this->_d[$priority][$key];
418 unset($this->_d[$priority][$key]);
420 if(count($this->_d[$priority])===0) {
421 unset($this->_d[$priority]);
432 throw new TInvalidOperationException('map_readonly',get_class($this));
436 * Removes all items in the map. {@link remove} is called on all items.
438 public function clear()
440 foreach($this->_d as $priority=>$items)
441 foreach(array_keys($items) as $key)
446 * @param mixed the key
447 * @return boolean whether the map contains an item with the specified key
449 public function contains($key)
451 $map=$this->flattenPriorities();
452 return isset($map[$key])||array_key_exists($key,$map);
456 * When the map is flattened into an array, the priorities are taken into
457 * account and elements of the map are ordered in the array according to
459 * @return array the list of items in array
461 public function toArray()
463 return $this->flattenPriorities();
467 * Combines the map elements which have a priority below the parameter value
468 * @param numeric the cut-off priority. All items of priority less than this are returned.
469 * @param boolean whether or not the input cut-off priority is inclusive. Default: false, not inclusive.
470 * @return array the array of priorities keys with values of arrays of items that are below a specified priority.
471 * The priorities are sorted so important priorities, lower numerics, are first.
473 public function toArrayBelowPriority($priority,$inclusive=false)
475 $this->sortPriorities();
477 foreach($this->_d as $itemspriority=>$itemsatpriority)
479 if((!$inclusive&&$itemspriority>=$priority)||$itemspriority>$priority)
481 $items=array_merge($items,$itemsatpriority);
487 * Combines the map elements which have a priority above the parameter value
488 * @param numeric the cut-off priority. All items of priority greater than this are returned.
489 * @param boolean whether or not the input cut-off priority is inclusive. Default: true, inclusive.
490 * @return array the array of priorities keys with values of arrays of items that are above a specified priority.
491 * The priorities are sorted so important priorities, lower numerics, are first.
493 public function toArrayAbovePriority($priority,$inclusive=true)
495 $this->sortPriorities();
497 foreach($this->_d as $itemspriority=>$itemsatpriority)
499 if((!$inclusive&&$itemspriority<=$priority)||$itemspriority<$priority)
501 $items=array_merge($items,$itemsatpriority);
507 * Copies iterable data into the map.
508 * Note, existing data in the map will be cleared first.
509 * @param mixed the data to be copied from, must be an array, object implementing
510 * Traversable, or a TPriorityMap
511 * @throws TInvalidDataTypeException If data is neither an array nor an iterator.
513 public function copyFrom($data)
515 if($data instanceof TPriorityMap)
517 if($this->getCount()>0)
519 foreach($data->getPriorities() as $priority) {
520 foreach($data->itemsAtPriority($priority) as $key => $value) {
521 $this->add($key,$value,$priority);
525 else if(is_array($data)||$data instanceof Traversable)
527 if($this->getCount()>0)
529 foreach($data as $key=>$value)
530 $this->add($key,$value);
532 else if($data!==null)
533 throw new TInvalidDataTypeException('map_data_not_iterable');
537 * Merges iterable data into the map.
538 * Existing data in the map will be kept and overwritten if the keys are the same.
539 * @param mixed the data to be merged with, must be an array, object implementing
540 * Traversable, or a TPriorityMap
541 * @throws TInvalidDataTypeException If data is neither an array nor an iterator.
543 public function mergeWith($data)
545 if($data instanceof TPriorityMap)
547 foreach($data->getPriorities() as $priority)
549 foreach($data->itemsAtPriority($priority) as $key => $value)
550 $this->add($key,$value,$priority);
553 else if(is_array($data)||$data instanceof Traversable)
555 foreach($data as $key=>$value)
556 $this->add($key,$value);
558 else if($data!==null)
559 throw new TInvalidDataTypeException('map_data_not_iterable');
563 * Returns whether there is an element at the specified offset.
564 * This method is required by the interface ArrayAccess.
565 * @param mixed the offset to check on
568 public function offsetExists($offset)
570 return $this->contains($offset);
574 * Returns the element at the specified offset.
575 * This method is required by the interface ArrayAccess.
576 * @param integer the offset to retrieve element.
577 * @return mixed the element at the offset, null if no element is found at the offset
579 public function offsetGet($offset)
581 return $this->itemAt($offset);
585 * Sets the element at the specified offset.
586 * This method is required by the interface ArrayAccess.
587 * @param integer the offset to set element
588 * @param mixed the element value
590 public function offsetSet($offset,$item)
592 $this->add($offset,$item);
596 * Unsets the element at the specified offset.
597 * This method is required by the interface ArrayAccess.
598 * @param mixed the offset to unset element
600 public function offsetUnset($offset)
602 $this->remove($offset);