PriorityQueue.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. <?php
  2. /**
  3. * Zend Framework (http://framework.zend.com/)
  4. *
  5. * @link http://github.com/zendframework/zf2 for the canonical source repository
  6. * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com)
  7. * @license http://framework.zend.com/license/new-bsd New BSD License
  8. */
  9. namespace Zend\Stdlib;
  10. use Countable;
  11. use IteratorAggregate;
  12. use Serializable;
  13. /**
  14. * Re-usable, serializable priority queue implementation
  15. *
  16. * SplPriorityQueue acts as a heap; on iteration, each item is removed from the
  17. * queue. If you wish to re-use such a queue, you need to clone it first. This
  18. * makes for some interesting issues if you wish to delete items from the queue,
  19. * or, as already stated, iterate over it multiple times.
  20. *
  21. * This class aggregates items for the queue itself, but also composes an
  22. * "inner" iterator in the form of an SplPriorityQueue object for performing
  23. * the actual iteration.
  24. */
  25. class PriorityQueue implements Countable, IteratorAggregate, Serializable
  26. {
  27. const EXTR_DATA = 0x00000001;
  28. const EXTR_PRIORITY = 0x00000002;
  29. const EXTR_BOTH = 0x00000003;
  30. /**
  31. * Inner queue class to use for iteration
  32. * @var string
  33. */
  34. protected $queueClass = 'Zend\Stdlib\SplPriorityQueue';
  35. /**
  36. * Actual items aggregated in the priority queue. Each item is an array
  37. * with keys "data" and "priority".
  38. * @var array
  39. */
  40. protected $items = array();
  41. /**
  42. * Inner queue object
  43. * @var SplPriorityQueue
  44. */
  45. protected $queue;
  46. /**
  47. * Insert an item into the queue
  48. *
  49. * Priority defaults to 1 (low priority) if none provided.
  50. *
  51. * @param mixed $data
  52. * @param int $priority
  53. * @return PriorityQueue
  54. */
  55. public function insert($data, $priority = 1)
  56. {
  57. $priority = (int) $priority;
  58. $this->items[] = array(
  59. 'data' => $data,
  60. 'priority' => $priority,
  61. );
  62. $this->getQueue()->insert($data, $priority);
  63. return $this;
  64. }
  65. /**
  66. * Remove an item from the queue
  67. *
  68. * This is different than {@link extract()}; its purpose is to dequeue an
  69. * item.
  70. *
  71. * This operation is potentially expensive, as it requires
  72. * re-initialization and re-population of the inner queue.
  73. *
  74. * Note: this removes the first item matching the provided item found. If
  75. * the same item has been added multiple times, it will not remove other
  76. * instances.
  77. *
  78. * @param mixed $datum
  79. * @return bool False if the item was not found, true otherwise.
  80. */
  81. public function remove($datum)
  82. {
  83. $found = false;
  84. foreach ($this->items as $key => $item) {
  85. if ($item['data'] === $datum) {
  86. $found = true;
  87. break;
  88. }
  89. }
  90. if ($found) {
  91. unset($this->items[$key]);
  92. $this->queue = null;
  93. if (!$this->isEmpty()) {
  94. $queue = $this->getQueue();
  95. foreach ($this->items as $item) {
  96. $queue->insert($item['data'], $item['priority']);
  97. }
  98. }
  99. return true;
  100. }
  101. return false;
  102. }
  103. /**
  104. * Is the queue empty?
  105. *
  106. * @return bool
  107. */
  108. public function isEmpty()
  109. {
  110. return (0 === $this->count());
  111. }
  112. /**
  113. * How many items are in the queue?
  114. *
  115. * @return int
  116. */
  117. public function count()
  118. {
  119. return count($this->items);
  120. }
  121. /**
  122. * Peek at the top node in the queue, based on priority.
  123. *
  124. * @return mixed
  125. */
  126. public function top()
  127. {
  128. return $this->getIterator()->top();
  129. }
  130. /**
  131. * Extract a node from the inner queue and sift up
  132. *
  133. * @return mixed
  134. */
  135. public function extract()
  136. {
  137. return $this->getQueue()->extract();
  138. }
  139. /**
  140. * Retrieve the inner iterator
  141. *
  142. * SplPriorityQueue acts as a heap, which typically implies that as items
  143. * are iterated, they are also removed. This does not work for situations
  144. * where the queue may be iterated multiple times. As such, this class
  145. * aggregates the values, and also injects an SplPriorityQueue. This method
  146. * retrieves the inner queue object, and clones it for purposes of
  147. * iteration.
  148. *
  149. * @return SplPriorityQueue
  150. */
  151. public function getIterator()
  152. {
  153. $queue = $this->getQueue();
  154. return clone $queue;
  155. }
  156. /**
  157. * Serialize the data structure
  158. *
  159. * @return string
  160. */
  161. public function serialize()
  162. {
  163. return serialize($this->items);
  164. }
  165. /**
  166. * Unserialize a string into a PriorityQueue object
  167. *
  168. * Serialization format is compatible with {@link Zend\Stdlib\SplPriorityQueue}
  169. *
  170. * @param string $data
  171. * @return void
  172. */
  173. public function unserialize($data)
  174. {
  175. foreach (unserialize($data) as $item) {
  176. $this->insert($item['data'], $item['priority']);
  177. }
  178. }
  179. /**
  180. * Serialize to an array
  181. *
  182. * By default, returns only the item data, and in the order registered (not
  183. * sorted). You may provide one of the EXTR_* flags as an argument, allowing
  184. * the ability to return priorities or both data and priority.
  185. *
  186. * @param int $flag
  187. * @return array
  188. */
  189. public function toArray($flag = self::EXTR_DATA)
  190. {
  191. switch ($flag) {
  192. case self::EXTR_BOTH:
  193. return $this->items;
  194. break;
  195. case self::EXTR_PRIORITY:
  196. return array_map(function ($item) {
  197. return $item['priority'];
  198. }, $this->items);
  199. case self::EXTR_DATA:
  200. default:
  201. return array_map(function ($item) {
  202. return $item['data'];
  203. }, $this->items);
  204. }
  205. }
  206. /**
  207. * Specify the internal queue class
  208. *
  209. * Please see {@link getIterator()} for details on the necessity of an
  210. * internal queue class. The class provided should extend SplPriorityQueue.
  211. *
  212. * @param string $class
  213. * @return PriorityQueue
  214. */
  215. public function setInternalQueueClass($class)
  216. {
  217. $this->queueClass = (string) $class;
  218. return $this;
  219. }
  220. /**
  221. * Does the queue contain the given datum?
  222. *
  223. * @param mixed $datum
  224. * @return bool
  225. */
  226. public function contains($datum)
  227. {
  228. foreach ($this->items as $item) {
  229. if ($item['data'] === $datum) {
  230. return true;
  231. }
  232. }
  233. return false;
  234. }
  235. /**
  236. * Does the queue have an item with the given priority?
  237. *
  238. * @param int $priority
  239. * @return bool
  240. */
  241. public function hasPriority($priority)
  242. {
  243. foreach ($this->items as $item) {
  244. if ($item['priority'] === $priority) {
  245. return true;
  246. }
  247. }
  248. return false;
  249. }
  250. /**
  251. * Get the inner priority queue instance
  252. *
  253. * @throws Exception\DomainException
  254. * @return SplPriorityQueue
  255. */
  256. protected function getQueue()
  257. {
  258. if (null === $this->queue) {
  259. $this->queue = new $this->queueClass();
  260. if (!$this->queue instanceof \SplPriorityQueue) {
  261. throw new Exception\DomainException(sprintf(
  262. 'PriorityQueue expects an internal queue of type SplPriorityQueue; received "%s"',
  263. get_class($this->queue)
  264. ));
  265. }
  266. }
  267. return $this->queue;
  268. }
  269. /**
  270. * Add support for deep cloning
  271. *
  272. * @return void
  273. */
  274. public function __clone()
  275. {
  276. if (null !== $this->queue) {
  277. $this->queue = clone $this->queue;
  278. }
  279. }
  280. }