Load XDB to workaround a segfault in PHP.
[platal.git] / classes / pliteratorutils.php
CommitLineData
63f00a3f
FB
1<?php
2/***************************************************************************
2ab75571 3 * Copyright (C) 2003-2010 Polytechnique.org *
63f00a3f
FB
4 * http://opensource.polytechnique.org/ *
5 * *
6 * This program is free software; you can redistribute it and/or modify *
7 * it under the terms of the GNU General Public License as published by *
8 * the Free Software Foundation; either version 2 of the License, or *
9 * (at your option) any later version. *
10 * *
11 * This program is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 * GNU General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU General Public License *
17 * along with this program; if not, write to the Free Software *
18 * Foundation, Inc., *
19 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
20 ***************************************************************************/
21
5177a1b5
FB
22class PlIteratorUtils
23{
24 /** Build an iterator over an array.
25 * @param array The array.
26 * @param depth The depth of iteration.
27 * @return an iterator that return entries in the form
28 * array(key => array(0 => key_for_depth0 [, 1 => key_for_depths1, ...]),
29 * value => the value);
30 */
e94c4a17 31 public static function fromArray(array $array, $depth = 1, $flat = false)
5177a1b5 32 {
e94c4a17 33 return new PlArrayIterator($array, $depth, $flat);
5177a1b5
FB
34 }
35
36
37 /** Sort an iterator using the given sort callback.
38 * @param iterator The iterator to sort.
39 * @param callback The callback for comparison.
40 * @return a new iterator with the entries sorted.
41 */
42 public static function sort(PlIterator $iterator, $callback)
43 {
44 $heap = new PlHeap($callback);
45 while ($item = $iterator->next()) {
46 $heap->push($item);
47 }
48 return $heap->iterator();
49 }
50
51
52 /** Merge several iterator into a unique one.
53 * @param iterators Array of iterators.
54 * @param callback The callback for comparison.
55 * @param sorted Tell wether the iterators are already sorted using the given callback.
56 * @return an iterator.
57 */
58 public static function merge(array $iterators, $callback, $sorted = true)
59 {
60 return new PlMergeIterator($iterators, $callback, $sorted);
61 }
8d37a362
FB
62
63
64 /** Build an iterator that contains only the elements of the given iterator that
65 * match the given condition. The condition should be a callback that takes an element
66 * and returns a boolean.
67 * @param iterator The source iterator
68 * @param callback The condition
69 * @return an iterator
70 */
71 public static function filter(PlIterator $iterator, $callback)
72 {
73 return new PlFilterIterator($iterator, $callback);
74 }
75
76
77 /** Build an iterator that transforms the element of another iterator. The callback
78 * takes an element and transform it into another one. Be careful: if the result
79 * of the callback is null, the iteration ends.
80 * @param iterator The source iterator
81 * @param callback The transformer.
82 */
83 public static function map(PlIterator $iterator, $callback)
84 {
85 return new PlMapIterator($iterator, $callback);
86 }
5177a1b5 87
22c8b530 88 /** Build an iterator whose values are iterators too; such a 'subIterator' will end
a52d0af8
RB
89 * when the value of $callback changes;
90 * WARNING: will fast-forward the current subiterator until it is over !
22c8b530 91 * @param iterator The source iterator
21f0c57c 92 * @param callback The callback for detecting changes. XXX: Might be called twice on a given object
22c8b530
RB
93 * @return an iterator
94 */
95 public static function subIterator(PlIterator $iterator, $callback)
96 {
21f0c57c 97 return new PlSubIterator($iterator, $callback);
22c8b530
RB
98 }
99
851962db
RB
100 /** Build an iterator that will iterate over the given set of iterators, returning consistent
101 * sets of values (i.e only the values for which the result of the callback is the same as that
102 * for the master)
103 * @param iterators The set of iterators
104 * @param callbacks A list of callbacks (one for each iterator), or a single, common, callback
105 * @param master The id of the "master" iterator in the first list
106 */
107 public static function parallelIterator(array $iterators, $callbacks, $master)
108 {
109 return new PlParallelIterator($iterators, $callbacks, $master);
110 }
111
22c8b530
RB
112 /** Returns the callback for '$x -> $x[$key]';
113 * @param $key the index to retrieve in arrays
114 * @return a callback
115 */
116 public static function arrayValueCallback($key)
117 {
118 $callback = new _GetArrayValueCallback($key);
119 return array($callback, 'get');
120 }
ce06a3ea
RB
121
122 /** Returns the callback for '$x -> $x->prop';
123 * @param $property The property to retrieve
124 * @return a callback
125 */
d40e45a2 126 public static function objectPropertyCallback($property)
ce06a3ea
RB
127 {
128 $callback = new _GetObjectPropertyCallback($property);
129 return array($callback, 'get');
130 }
22c8b530 131}
5177a1b5
FB
132
133/** Iterates over an array.
134 */
63f00a3f
FB
135class PlArrayIterator implements PlIterator
136{
137 private $array;
138 private $depth;
139
140 private $_its = array();
141
142 private $_total;
143 private $_first;
144 private $_last;
145 private $_pos;
e94c4a17 146 private $_flat;
63f00a3f 147
e94c4a17 148 public function __construct(array &$array, $depth = 1, $flat = false)
63f00a3f
FB
149 {
150 $this->array =& $array;
151 $this->depth = $depth;
152 $this->_total = $this->count($array, $depth - 1);
153 $this->_pos = 0;
154 $this->_first = false;
155 $this->_last = false;
e94c4a17 156 $this->_flat = $flat;
63f00a3f
FB
157
158 for ($i = 0 ; $i < $depth ; ++$i) {
159 if ($i == 0) {
160 $this->_its[] = $array;
161 } else {
162 $this->_its[] = current($this->_its[$i - 1]);
163 }
164 reset($this->_its[$i]);
165 }
166 }
167
168 private function count(array &$array, $depth)
169 {
170 if ($depth == 0) {
171 return count($array);
172 } else {
173 $sum = 0;
174 foreach ($array as &$item) {
175 $sum += $this->count($item, $depth - 1);
176 }
177 return $sum;
178 }
179 }
180
181 private function nextArray($depth)
182 {
183 if ($depth == 0) {
184 return;
185 }
186 $this->_its[$depth] = next($this->_its[$depth - 1]);
187 if ($this->_its[$depth] === false) {
188 $this->nextArray($depth - 1);
189 if ($this->_its[$depth - 1] === false) {
190 return;
191 }
192 $this->_its[$depth] = current($this->_its[$depth - 1]);
193 }
194 reset($this->_its[$depth]);
195 }
196
197 public function next()
198 {
199 ++$this->_pos;
200 $this->_first = ($this->_total > 0 && $this->_pos == 1);
201 $this->_last = ($this->_pos == $this->_total);
202 if ($this->_pos > $this->_total) {
203 return null;
204 }
205
206 $val = current($this->_its[$this->depth - 1]);
207 if ($val === false) {
208 $this->nextArray($this->depth - 1);
209 $val = current($this->_its[$this->depth - 1]);
210 if ($val === false) {
211 return null;
212 }
213 }
214 $keys = array();
215 for ($i = 0 ; $i < $this->depth ; ++$i) {
216 $keys[] = key($this->_its[$i]);
217 }
218 next($this->_its[$this->depth - 1]);
e94c4a17
FB
219 if ($this->_flat) {
220 return $val;
221 } else {
222 return array('keys' => $keys,
223 'value' => $val);
224 }
63f00a3f
FB
225 }
226
227 public function total()
228 {
229 return $this->_total;
230 }
231
232 public function first()
233 {
234 return $this->_first;
235 }
236
237 public function last()
238 {
239 return $this->_last;
240 }
241}
242
5177a1b5
FB
243
244/** Iterator that return the result of a merge of several iterators.
245 */
246class PlMergeIterator implements PlIterator
247{
248 /* The heap is field with entries with the form:
249 * array('it' => id of the iterator this entry come from,
250 * 'value' => value of the entry).
251 */
252 private $heap;
253 private $preComputed = false;
254 private $comparator;
255 private $iterators;
256 private $_total;
257 private $pos;
258
259 public function __construct(array $iterators, $callback, $sorted = true)
260 {
261 $this->heap = new PlHeap(array($this, 'compare'));
262 $this->_total = 0;
263 $this->comparator = $callback;
264 if ($sorted) {
265 $this->iterators = $iterators;
266 foreach ($this->iterators as $key => &$it) {
267 $this->_total += $it->total();
268 $item = $it->next();
269 if (!is_null($item)) {
270 $this->heap->push(array('it' => $key, 'value' => $item));
271 }
272 }
273 } else {
274 $this->preComputed = true;
275 foreach ($iterators as $key => &$it) {
276 $this->_total += $it->total();
277 while (!is_null($item = $it->next())) {
278 $this->heap->push(array('it' => $key, 'value' => $item));
279 }
280 }
281 }
282 $this->pos = 0;
283 }
284
285 /** Compare two entries of the heap using the comparator of the user.
286 */
287 public function compare($a, $b)
288 {
289 $cp = call_user_func($this->comparator, $a['value'], $b['value']);
290 if ($cp == 0) {
291 return $a['it'] - $b['it'];
292 }
293 return $cp;
294 }
295
296 public function total()
297 {
298 return $this->_total;
299 }
300
301 public function next()
302 {
303 ++$this->pos;
304 $entry = $this->heap->pop();
305 if (is_null($entry)) {
306 return null;
307 }
308 if ($this->preComputed) {
309 return $entry['value'];
310 }
311 $it = $entry['it'];
312 $item = $this->iterators[$it]->next();
313 if (!is_null($item)) {
314 $this->heap->push(array('it' => $it, 'value' => $item));
315 }
316 return $entry['value'];
317 }
318
319 public function last()
320 {
321 return $this->heap->count() == 0;
322 }
323
324 public function first()
325 {
326 return $this->pos == 1;
327 }
328}
329
8d37a362 330
8b48f8d7
FB
331class PlFilterIterator implements PlIterator
332{
333 private $pos;
8d37a362
FB
334 private $source;
335 private $callback;
336 private $element;
8d37a362
FB
337
338 public function __construct(PlIterator $source, $callback)
339 {
8b48f8d7 340 $this->source = $source;
8d37a362 341 $this->callback = $callback;
8b48f8d7
FB
342 $this->pos = 0;
343 $this->element = $this->fetchNext();
8d37a362
FB
344 }
345
346 private function fetchNext()
347 {
348 do {
349 $current = $this->source->next();
8b48f8d7
FB
350 if (is_null($current) || call_user_func($this->callback, $current)) {
351 return $current;
8d37a362
FB
352 }
353 } while (true);
354 }
355
356 public function next()
357 {
8b48f8d7 358 ++$this->pos;
8d37a362 359 $elt = $this->element;
8b48f8d7
FB
360 if (!is_null($this->element)) {
361 $this->element = $this->fetchNext();
8d37a362
FB
362 }
363 return $elt;
364 }
365
366 public function total()
367 {
368 /* This is an approximation since the correct total
369 * cannot be computed without fetching all the elements
370 */
371 return $this->source->total();
372 }
373
374 public function first()
375 {
8b48f8d7 376 return $this->pos == 1;
8d37a362
FB
377 }
378
379 public function last()
380 {
8b48f8d7 381 return is_null($this->element);
8d37a362
FB
382 }
383}
384
385
386class PlMapIterator implements PlIterator
387{
388 private $source;
389 private $callback;
390
391 public function __construct(PlIterator $source, $callback)
392 {
393 $this->source = $source;
394 $this->callback = $callback;
395 }
396
397 public function next()
398 {
399 $elt = $this->source->next();
400 if ($elt) {
401 return call_user_func($this->callback, $elt);
402 } else {
403 return null;
404 }
405 }
406
407 public function total()
408 {
409 return $this->source->total();
410 }
411
412 public function first()
413 {
414 return $this->source->first();
415 }
416
417 public function last()
418 {
419 return $this->source->last();
420 }
421}
422
22c8b530
RB
423class PlSubIterator implements PlIterator
424{
425 private $source;
426 private $callback;
427 private $next = null; // The next item, if it has been fetched too early by a subiterator
a52d0af8
RB
428 private $pos = 0;
429 private $sub = null;
22c8b530
RB
430
431 public function __construct(PlIterator $source, $callback)
432 {
433 $this->source = $source;
434 $this->callback = $callback;
435 }
436
a52d0af8
RB
437 /** WARNING: this will "fast-forward" the subiterator to its end
438 */
22c8b530
RB
439 public function next()
440 {
441 if ($this->last()) {
442 return null;
443 } else {
a52d0af8
RB
444 if ($this->sub != null) {
445 while (!$this->sub->last()) {
446 $this->sub->next();
447 }
448 }
449
450 if ($this->last()) {
451 return null;
452 }
453
454 ++$this->pos;
455 $this->sub = new PlInnerSubIterator($this->source, $this->callback, $this, $this->next);
456 return $this->sub;
22c8b530
RB
457 }
458 }
459
460 /** This will always be a too big number, but the actual result can't be easily computed
461 */
462 public function total()
463 {
464 return $this->source->total();
465 }
466
a52d0af8
RB
467 /** This will only return true if the current subiterator was the last one,
468 * and if it has been fully used
469 */
22c8b530
RB
470 public function last()
471 {
a52d0af8
RB
472 if ($this->sub != null && !$this->sub->last()) {
473 return false;
474 }
22c8b530
RB
475 return ($this->source->last() && $this->next == null);
476 }
477
9d865d03
FB
478 public function first()
479 {
a52d0af8 480 return $this->pos == 1;
9d865d03
FB
481 }
482
22c8b530
RB
483 // Called by a subiterator to "rewind" the core iterator
484 public function setNext($item)
485 {
486 $this->next = $item;
487 }
488}
489
490class PlInnerSubIterator implements PlIterator
491{
492 private $source;
493 private $callback;
494 private $parent;
495
496 private $next; // Not null if the source has to be "rewinded"
497
498 private $curval = null;
499 private $curelt = null;
a52d0af8
RB
500 private $val = null;
501 private $pos = 0;
22c8b530
RB
502 private $stepped = false;
503 private $over = false;
504
505 public function __construct(PlIterator $source, $callback, PlSubIterator $parent, $next = null)
506 {
507 $this->source = $source;
508 $this->callback = $callback;
509 $this->parent = $parent;
510 $this->next = $next;
a52d0af8 511 $this->parent->setNext(null);
22c8b530
RB
512 }
513
514 public function value()
515 {
516 $this->_step();
517 return $this->curval;
518 }
519
520 // Move one step, if the current element has been used
521 private function _step()
522 {
523 if ($this->stepped) {
524 return;
525 }
526
527 if ($this->next != null) {
528 $this->curelt = $this->next;
529 $this->next = null;
530 } else {
a52d0af8
RB
531 if ($this->source->last()) {
532 $this->over = true;
533 return;
534 }
21f0c57c 535 $this->curelt = $this->source->next();
22c8b530 536 }
21f0c57c 537
a52d0af8
RB
538 if ($this->pos == 0) {
539 $this->val = call_user_func($this->callback, $this->curelt);
540 $this->curval = $this->val;
541 } else {
21f0c57c 542 $this->curval = call_user_func($this->callback, $this->curelt);
21f0c57c
RB
543 }
544
22c8b530
RB
545 $this->stepped = true;
546 }
547
548 public function next()
549 {
a52d0af8
RB
550 if ($this->over) {
551 return null;
552 }
553
22c8b530 554 $this->_step();
a52d0af8
RB
555
556 if ($this->over) {
557 return null;
558 }
559
560 ++$this->pos;
22c8b530
RB
561 $this->stepped = false;
562
a52d0af8
RB
563 if ($this->val == $this->curval) {
564 return $this->curelt;
22c8b530 565 }
a52d0af8
RB
566
567 $this->parent->setNext($this->curelt);
22c8b530
RB
568 $this->over = true;
569 return null;
570 }
571
572 /** This will always be a too big number, but the actual result can't be easily computed
573 */
574 public function total()
575 {
576 return $this->source->total();
577 }
578
579 public function last()
580 {
a52d0af8
RB
581 if ($this->over) {
582 return true;
583 }
584 $this->_step();
585 return $this->over || ($this->val != $this->curval);
22c8b530
RB
586 }
587
9d865d03
FB
588 public function first()
589 {
a52d0af8 590 return $this->pos == 1;
9d865d03
FB
591 }
592
22c8b530
RB
593}
594
851962db
RB
595/** Builds an iterator over a set of iterators, from which one is given as 'master';
596 * The arguments are :
597 * - An array of iterators, to iterate simultaneously
598 * - An array of callbacks (one attached to each iterator), or a single callback (to
599 * use for all iterators)
600 * - The id of the 'master' iterator
601 *
602 * This ParallelIterator will iterate over the iterators, and, at each
603 * step of the master iterator, it will apply the callbacks to the corresponding
604 * iterators and return the values of the "slaves" for which the callback returned the
605 * same value as the callback of the master.
606 *
607 * The callback should compute some kind of index, and never return the same value
608 * twice for a given iterator
609 *
610 * It is assumed that, if the callback for a slave doesn't have the same value
611 * as the value for the master, this means that there is a "hole" in the values
612 * of that slave.
613 *
614 * Example :
615 * - The callback is 'get the first cell'
616 * - The master is :
617 * [0, 1], [1, 13], [2, 42]
618 * - The first slave (slave1) is :
619 * [0, 'a'], [2, 'b']
620 * - The second slave (slave2) is :
621 * [1, 42], [2, 0]
622 * The resulting iterator would yield :
623 * - array(master => [0, 1], slave1 => [0, 'a'], slave2 => null)
624 * - array(master => [1, 13], slave1 => null, slave2 => [1, 42])
625 * - array(master => [2, 42], slave1 => [1, 'b'], slave2 => [2, 0])
626 */
627class PlParallelIterator implements PlIterator
628{
629 private $iterators;
630 private $ids;
631 private $callbacks;
632
633 private $master_id;
634 private $master;
635
636 private $stepped;
637 private $current_elts = array();
638 private $callback_res = array();
639
640 public function __construct(array $iterators, $callbacks, $master)
641 {
642 $this->iterators = $iterators;
643 $this->master_id = $master;
644 $this->master = $iterators[$master];
645
646 $this->ids = array_keys($iterators);
647
ff539a07
RB
648 $v = array_values($callbacks);
649 if (is_array($v[0])) {
851962db
RB
650 $this->callbacks = $callbacks;
651 } else {
652 $this->callbacks = array();
653 foreach ($this->ids as $id) {
654 $this->callbacks[$id] = $callbacks;
655 }
656 }
657
658 foreach ($this->ids as $id) {
659 $this->stepped[$id] = false;
660 $this->current_elts[$id] = null;
661 $this->callback_res[$id] = null;
662 }
663 }
664
665 private function step($id)
666 {
667 if ($this->stepped[$id]) {
668 return;
669 }
670
671 $it = $this->iterators[$id];
672 $nxt = $it->next();
673 $res = call_user_func($this->callbacks[$id], $nxt);
674 $this->current_elts[$id] = $nxt;
675 $this->callback_res[$id] = $res;
676 $this->stepped[$id] = true;
677 }
678
679 private function stepAll()
680 {
681 foreach ($this->ids as $id) {
682 $this->step($id);
683 }
684 }
685
686 public function next()
687 {
688 $this->stepAll();
689 if ($this->current_elts[$this->master_id] == null) {
690 return null;
691 }
692
693 $res = array();
694 $master = $this->callback_res[$this->master_id];
695 foreach ($this->ids as $id) {
696 if ($this->callback_res[$id] == $master) {
697 $res[$id] = $this->current_elts[$id];
698 $this->stepped[$id] = false;
699 } else {
700 $res[$id] = null;
701 }
702 }
703
704 return $res;
705 }
706
707 public function first()
708 {
709 return $this->master->first();
710 }
711
712 public function total()
713 {
714 return $this->master->total();
715 }
716
717 public function last()
718 {
719 return $this->master->last();
720 }
721}
722
22c8b530
RB
723// Wrapper class for 'arrayValueCallback' (get field $key of the given array)
724class _GetArrayValueCallback
725{
726 private $key;
727
728 public function __construct($key)
729 {
730 $this->key = $key;
731 }
732
733 public function get(array $arr)
734 {
381a28e9
RB
735 if (array_key_exists($this->key, $arr)) {
736 return $arr[$this->key];
22c8b530
RB
737 } else {
738 return null;
739 }
740 }
741}
8d37a362 742
ce06a3ea
RB
743// Wrapper class for 'objectPropertyCallback' (get property ->$blah of the given object)
744class _GetObjectPropertyCallback
745{
746 private $property;
747
748 public function __construct($property)
749 {
750 $this->property = $property;
751 }
752
753 public function get($obj)
754 {
755 $p = $this->property;
756 return @$obj->$p;
757 }
758}
759
63f00a3f
FB
760// vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8:
761?>