SplPriorityQueue.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Stdlib
  17. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  18. * @license http://framework.zend.com/license/new-bsd New BSD License
  19. */
  20. if (version_compare(PHP_VERSION, '5.3.0', '<')) {
  21. /**
  22. * SplPriorityQueue
  23. *
  24. * PHP 5.2.X userland implementation of PHP's SplPriorityQueue
  25. */
  26. class SplPriorityQueue implements Iterator , Countable
  27. {
  28. /**
  29. * Extract data only
  30. */
  31. const EXTR_DATA = 0x00000001;
  32. /**
  33. * Extract priority only
  34. */
  35. const EXTR_PRIORITY = 0x00000002;
  36. /**
  37. * Extract an array of ('data' => $value, 'priority' => $priority)
  38. */
  39. const EXTR_BOTH = 0x00000003;
  40. /**
  41. * Count of items in the queue
  42. * @var int
  43. */
  44. protected $count = 0;
  45. /**
  46. * Flag indicating what should be returned when iterating or extracting
  47. * @var int
  48. */
  49. protected $extractFlags = self::EXTR_DATA;
  50. /**
  51. * @var bool|array
  52. */
  53. protected $preparedQueue = false;
  54. /**
  55. * All items in the queue
  56. * @var array
  57. */
  58. protected $queue = array();
  59. /**
  60. * Constructor
  61. *
  62. * Creates a new, empty queue
  63. *
  64. * @return void
  65. */
  66. public function __construct()
  67. {
  68. }
  69. /**
  70. * Compare two priorities
  71. *
  72. * Returns positive integer if $priority1 is greater than $priority2, 0
  73. * if equal, negative otherwise.
  74. *
  75. * Unused internally, and only included in order to retain the same
  76. * interface as PHP's SplPriorityQueue.
  77. *
  78. * @param mixed $priority1
  79. * @param mixed $priority2
  80. * @return int
  81. */
  82. public function compare($priority1, $priority2)
  83. {
  84. if ($priority1 > $priority2) {
  85. return 1;
  86. }
  87. if ($priority1 == $priority2) {
  88. return 0;
  89. }
  90. return -1;
  91. }
  92. /**
  93. * Countable: return number of items composed in the queue
  94. *
  95. * @return int
  96. */
  97. public function count()
  98. {
  99. return $this->count;
  100. }
  101. /**
  102. * Iterator: return current item
  103. *
  104. * @return mixed
  105. */
  106. public function current()
  107. {
  108. if (!$this->preparedQueue) {
  109. $this->rewind();
  110. }
  111. if (!$this->count) {
  112. throw new OutOfBoundsException('Cannot iterate SplPriorityQueue; no elements present');
  113. }
  114. if (!is_array($this->preparedQueue)) {
  115. throw new DomainException(sprintf(
  116. "Queue was prepared, but is empty?\n PreparedQueue: %s\n Internal Queue: %s\n",
  117. var_export($this->preparedQueue, 1),
  118. var_export($this->queue, 1)
  119. ));
  120. }
  121. $return = array_shift($this->preparedQueue);
  122. $priority = $return['priority'];
  123. $priorityKey = $return['priorityKey'];
  124. $key = $return['key'];
  125. unset($return['key']);
  126. unset($return['priorityKey']);
  127. unset($this->queue[$priorityKey][$key]);
  128. switch ($this->extractFlags) {
  129. case self::EXTR_DATA:
  130. return $return['data'];
  131. case self::EXTR_PRIORITY:
  132. return $return['priority'];
  133. case self::EXTR_BOTH:
  134. default:
  135. return $return;
  136. };
  137. }
  138. /**
  139. * Extract a node from top of the heap and sift up
  140. *
  141. * Returns either the value, the priority, or both, depending on the extract flag.
  142. *
  143. * @return mixed;
  144. */
  145. public function extract()
  146. {
  147. if (!$this->count) {
  148. return null;
  149. }
  150. if (!$this->preparedQueue) {
  151. $this->prepareQueue();
  152. }
  153. if (empty($this->preparedQueue)) {
  154. return null;
  155. }
  156. $return = array_shift($this->preparedQueue);
  157. $priority = $return['priority'];
  158. $priorityKey = $return['priorityKey'];
  159. $key = $return['key'];
  160. unset($return['key']);
  161. unset($return['priorityKey']);
  162. unset($this->queue[$priorityKey][$key]);
  163. $this->count--;
  164. switch ($this->extractFlags) {
  165. case self::EXTR_DATA:
  166. return $return['data'];
  167. case self::EXTR_PRIORITY:
  168. return $return['priority'];
  169. case self::EXTR_BOTH:
  170. default:
  171. return $return;
  172. };
  173. }
  174. /**
  175. * Insert a value into the heap, at the specified priority
  176. *
  177. * @param mixed $value
  178. * @param mixed $priority
  179. * @return void
  180. */
  181. public function insert($value, $priority)
  182. {
  183. if (!is_scalar($priority)) {
  184. $priority = serialize($priority);
  185. }
  186. if (!isset($this->queue[$priority])) {
  187. $this->queue[$priority] = array();
  188. }
  189. $this->queue[$priority][] = $value;
  190. $this->count++;
  191. $this->preparedQueue = false;
  192. }
  193. /**
  194. * Is the queue currently empty?
  195. *
  196. * @return bool
  197. */
  198. public function isEmpty()
  199. {
  200. return (0 == $this->count);
  201. }
  202. /**
  203. * Iterator: return current key
  204. *
  205. * @return mixed Usually an int or string
  206. */
  207. public function key()
  208. {
  209. return $this->count;
  210. }
  211. /**
  212. * Iterator: Move pointer forward
  213. *
  214. * @return void
  215. */
  216. public function next()
  217. {
  218. $this->count--;
  219. }
  220. /**
  221. * Recover from corrupted state and allow further actions on the queue
  222. *
  223. * Unimplemented, and only included in order to retain the same interface as PHP's
  224. * SplPriorityQueue.
  225. *
  226. * @return void
  227. */
  228. public function recoverFromCorruption()
  229. {
  230. }
  231. /**
  232. * Iterator: Move pointer to first item
  233. *
  234. * @return void
  235. */
  236. public function rewind()
  237. {
  238. if (!$this->preparedQueue) {
  239. $this->prepareQueue();
  240. }
  241. }
  242. /**
  243. * Set the extract flags
  244. *
  245. * Defines what is extracted by SplPriorityQueue::current(),
  246. * SplPriorityQueue::top() and SplPriorityQueue::extract().
  247. *
  248. * - SplPriorityQueue::EXTR_DATA (0x00000001): Extract the data
  249. * - SplPriorityQueue::EXTR_PRIORITY (0x00000002): Extract the priority
  250. * - SplPriorityQueue::EXTR_BOTH (0x00000003): Extract an array containing both
  251. *
  252. * The default mode is SplPriorityQueue::EXTR_DATA.
  253. *
  254. * @param int $flags
  255. * @return void
  256. */
  257. public function setExtractFlags($flags)
  258. {
  259. $expected = array(
  260. self::EXTR_DATA => true,
  261. self::EXTR_PRIORITY => true,
  262. self::EXTR_BOTH => true,
  263. );
  264. if (!isset($expected[$flags])) {
  265. throw new InvalidArgumentException(sprintf('Expected an EXTR_* flag; received %s', $flags));
  266. }
  267. $this->extractFlags = $flags;
  268. }
  269. /**
  270. * Return the value or priority (or both) of the top node, depending on
  271. * the extract flag
  272. *
  273. * @return mixed
  274. */
  275. public function top()
  276. {
  277. $this->sort();
  278. $keys = array_keys($this->queue);
  279. $key = array_shift($keys);
  280. if (preg_match('/^(a|O):/', $key)) {
  281. $key = unserialize($key);
  282. }
  283. if ($this->extractFlags == self::EXTR_PRIORITY) {
  284. return $key;
  285. }
  286. if ($this->extractFlags == self::EXTR_DATA) {
  287. return $this->queue[$key][0];
  288. }
  289. return array(
  290. 'data' => $this->queue[$key][0],
  291. 'priority' => $key,
  292. );
  293. }
  294. /**
  295. * Iterator: is the current position valid for the queue
  296. *
  297. * @return bool
  298. */
  299. public function valid()
  300. {
  301. return (bool) $this->count;
  302. }
  303. /**
  304. * Sort the queue
  305. *
  306. * @return void
  307. */
  308. protected function sort()
  309. {
  310. krsort($this->queue);
  311. }
  312. /**
  313. * Prepare the queue for iteration and/or extraction
  314. *
  315. * @return void
  316. */
  317. protected function prepareQueue()
  318. {
  319. $this->sort();
  320. $count = $this->count;
  321. $queue = array();
  322. foreach ($this->queue as $priority => $values) {
  323. $priorityKey = $priority;
  324. if (preg_match('/^(a|O):/', $priority)) {
  325. $priority = unserialize($priority);
  326. }
  327. foreach ($values as $key => $value) {
  328. $queue[$count] = array(
  329. 'data' => $value,
  330. 'priority' => $priority,
  331. 'priorityKey' => $priorityKey,
  332. 'key' => $key,
  333. );
  334. $count--;
  335. }
  336. }
  337. $this->preparedQueue = $queue;
  338. }
  339. }
  340. }
  341. /**
  342. * Serializable version of SplPriorityQueue
  343. *
  344. * Also, provides predictable heap order for datums added with the same priority
  345. * (i.e., they will be emitted in the same order they are enqueued).
  346. *
  347. * @category Zend
  348. * @package Zend_Stdlib
  349. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  350. * @license http://framework.zend.com/license/new-bsd New BSD License
  351. */
  352. class Zend_Stdlib_SplPriorityQueue extends SplPriorityQueue implements Serializable
  353. {
  354. /**
  355. * @var int Seed used to ensure queue order for items of the same priority
  356. */
  357. protected $serial = PHP_INT_MAX;
  358. /**
  359. * @var bool
  360. */
  361. protected $isPhp53;
  362. /**
  363. * Constructor
  364. *
  365. * @return void
  366. */
  367. public function __construct()
  368. {
  369. $this->isPhp53 = version_compare(PHP_VERSION, '5.3', '>=');
  370. }
  371. /**
  372. * Insert a value with a given priority
  373. *
  374. * Utilizes {@var $serial} to ensure that values of equal priority are
  375. * emitted in the same order in which they are inserted.
  376. *
  377. * @param mixed $datum
  378. * @param mixed $priority
  379. * @return void
  380. */
  381. public function insert($datum, $priority)
  382. {
  383. // If using the native PHP SplPriorityQueue implementation, we need to
  384. // hack around it to ensure that items registered at the same priority
  385. // return in the order registered. In the userland version, this is not
  386. // necessary.
  387. if ($this->isPhp53) {
  388. if (!is_array($priority)) {
  389. $priority = array($priority, $this->serial--);
  390. }
  391. }
  392. parent::insert($datum, $priority);
  393. }
  394. /**
  395. * Serialize to an array
  396. *
  397. * Array will be priority => data pairs
  398. *
  399. * @return array
  400. */
  401. public function toArray()
  402. {
  403. $this->setExtractFlags(self::EXTR_BOTH);
  404. $array = array();
  405. while ($this->valid()) {
  406. $array[] = $this->current();
  407. $this->next();
  408. }
  409. $this->setExtractFlags(self::EXTR_DATA);
  410. // Iterating through a priority queue removes items
  411. foreach ($array as $item) {
  412. $this->insert($item['data'], $item['priority']);
  413. }
  414. // Return only the data
  415. $return = array();
  416. foreach ($array as $item) {
  417. $return[] = $item['data'];
  418. }
  419. return $return;
  420. }
  421. /**
  422. * Serialize
  423. *
  424. * @return string
  425. */
  426. public function serialize()
  427. {
  428. $data = array();
  429. $this->setExtractFlags(self::EXTR_BOTH);
  430. while ($this->valid()) {
  431. $data[] = $this->current();
  432. $this->next();
  433. }
  434. $this->setExtractFlags(self::EXTR_DATA);
  435. // Iterating through a priority queue removes items
  436. foreach ($data as $item) {
  437. $this->insert($item['data'], $item['priority']);
  438. }
  439. return serialize($data);
  440. }
  441. /**
  442. * Deserialize
  443. *
  444. * @param string $data
  445. * @return void
  446. */
  447. public function unserialize($data)
  448. {
  449. foreach (unserialize($data) as $item) {
  450. $this->insert($item['data'], $item['priority']);
  451. }
  452. }
  453. }