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