Skip to content

Commit 32c407d

Browse files
committed
Add "before trigger" to SplitIterator
Expand the SplitIterator, so it can split before or after the item that triggered the split (only split after trigger was supported before). This is, as we unfortunately exactly needed the other way round...
1 parent 9c88851 commit 32c407d

File tree

4 files changed

+262
-8
lines changed

4 files changed

+262
-8
lines changed

SplitIterator.php

+38-3
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,32 @@
8383
* @see SplitCallbackIterator SplitInnerIterator
8484
*/
8585
abstract class SplitIterator extends IteratorIterator {
86+
/**
87+
* Flag to denote that splitting should take place after the element
88+
* that fired on {@see needsSplit()} (i.e. this very element will
89+
* still be returned from the previous split-off iterator).
90+
*
91+
* @see SPLIT_BEFORE
92+
*/
93+
const SPLIT_AFTER = 0;
94+
95+
/**
96+
* Flag to denote that splitting should take place before the
97+
* element that fired on {@see needsSplit()} (i.e. this very element
98+
* will already be returned by the next split-off iterator).
99+
*
100+
* @see SPLIT_AFTER
101+
*/
102+
const SPLIT_BEFORE = 1;
103+
104+
/**
105+
* Flag to denote the time of splitting with regards to {@see
106+
* needsSplit()} firing.
107+
*
108+
* @see SPLIT_AFTER, SPLIT_BEFORE
109+
*/
110+
protected $splitWhen = self::SPLIT_AFTER;
111+
86112
/**
87113
* The current item (which is the split-off iterator) returned from
88114
* this class.
@@ -145,7 +171,6 @@ public function next() {
145171
&& $this->currentSplitIterator->valid())
146172
$this->currentSplitIterator->next();
147173

148-
parent::next();
149174
if($this->splitOff())
150175
$this->key++;
151176
}
@@ -180,9 +205,19 @@ private function splitOff() {
180205
$this->currentSplitIterator = NULL;
181206
}
182207

183-
if(parent::valid()) {
208+
// INVESTIGATE: We had parent::valid() here before, which should
209+
// actually end up at the same code as
210+
// $this->getInnerIterator()->valid(), but doesn't for some
211+
// reason. Maybe some form of PHP internal cache? Or other
212+
// mess-up in the IteratorIterator? Or did I really loose
213+
// my mind?
214+
if($this->getInnerIterator()->valid()) {
184215
$this->currentSplitIterator
185-
= new SplitInnerIterator($this->getInnerIterator(), array($this, 'needsSplit'));
216+
= new SplitInnerIterator($this->getInnerIterator(),
217+
array($this, 'needsSplit'),
218+
$this->splitWhen == self::SPLIT_AFTER
219+
? SplitInnerIterator::STOP_AFTER
220+
: SplitInnerIterator::STOP_BEFORE);
186221
return TRUE;
187222
} else {
188223
return FALSE;

SplitIterator/SplitInnerIterator.php

+76-5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33
* This is the type of iterator that {@see SplitIterator} returns for
44
* its split-off parts.
55
*
6+
* It takes a callback that decides when to stop, possibly
7+
* "prematurely". A flag tells if it should stop before or after the
8+
* item on which the condition holds true.
9+
*
10+
* Note that the position of the inner iterator will always be
11+
* advanced to the next position after this iterator stops it
12+
* prematurely.
13+
*
614
* This iterator can only be traversed once (it can't be rewound, as
715
* it is actually just a "view" on parts of the outer, unsplit
816
* iterator, and rewinding it would also mess with the state of the
@@ -29,12 +37,62 @@
2937
* for the {@see SplitIterator} right now.
3038
*/
3139
class SplitInnerIterator extends NoRewindIterator {
40+
/**
41+
* Flag to denote stopping after the element that fired on the stop
42+
* callback (i.e. this very element will still be returned from the
43+
* iterator).
44+
*
45+
* @see __construct()
46+
*/
47+
const STOP_AFTER = 0;
48+
49+
/**
50+
* Flag to denote stopping before the element that fired on the stop
51+
* callback (i.e. this very element will not be returned anymore
52+
* from the iterator).
53+
*
54+
* @see __construct()
55+
*/
56+
const STOP_BEFORE = 1;
57+
3258
private $stopCallback;
3359

3460
private $valid = TRUE;
3561

36-
final public function __construct(Traversable $iterator, callable $stopCallback) {
62+
/**
63+
* When this iterator should stop, one of the constants {@see
64+
* STOP_BEFORE} or {@see STOP_AFTER}.
65+
*
66+
* @val integer
67+
*/
68+
private $when;
69+
70+
/**
71+
* The constructor takes the callback/configuration on when to stop
72+
* the inner iterator prematurely.
73+
*
74+
* @param Traversable The inner iterator, that should possibly being
75+
* stopped prematurely.
76+
*
77+
* @param callable A callback (called with two arguments, the key
78+
* and the item returned by the inner iterator) to decide if the
79+
* inner iterator should be stopped prematurely (TRUE means
80+
* stop, FALSE continue).
81+
*
82+
* @param integer One of the constants {@see STOP_AFTER} (the
83+
* default) or {@see STOP_BEFORE}, deciding if a TRUE value
84+
* returned from the callback should stop the iterator before or
85+
* after the item that triggered the callback.
86+
*
87+
* @return SplitInnerIterator
88+
*/
89+
final public function __construct(Traversable $iterator,
90+
callable $stopCallback,
91+
$when = self::STOP_AFTER) {
3792
$this->stopCallback = $stopCallback;
93+
$this->when = ($when == self::STOP_BEFORE
94+
? self::STOP_BEFORE
95+
: self::STOP_AFTER);
3896
parent::__construct($iterator);
3997
}
4098

@@ -47,18 +105,31 @@ final public function current() {
47105
}
48106

49107
final public function next() {
50-
if(!$this->valid)
108+
if(!$this->valid) {
51109
return;
110+
}
111+
112+
if($this->when == self::STOP_BEFORE) {
113+
parent::next();
114+
if(!parent::valid()) {
115+
$this->valid = FALSE;
116+
return;
117+
}
118+
}
52119

53120
$stopCallback = $this->stopCallback; // Due to PHP's syntax restrictions
54121
if($stopCallback($this->key(), $this->current())) {
55122
$this->valid = FALSE;
123+
if($this->when == self::STOP_AFTER && parent::valid())
124+
parent::next();
56125
return;
57126
}
58127

59-
parent::next();
60-
$this->valid = parent::valid();
61-
return;
128+
if($this->when == self::STOP_AFTER) {
129+
if(parent::valid())
130+
parent::next();
131+
$this->valid = parent::valid();
132+
}
62133
}
63134

64135
final public function valid() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
--TEST--
2+
SplitIterator/SplitInnerIterator: Stop before vs. after condition TRUE
3+
--FILE--
4+
<?php
5+
require_once("SplitIterator/SplitInnerIterator.php");
6+
7+
foreach(array(SplitInnerIterator::STOP_AFTER,
8+
SplitInnerIterator::STOP_BEFORE)
9+
as $when) {
10+
$inner = new ArrayIterator(range(1, 10));
11+
$stopper = TestHelpers::pnewdebug("SplitInnerIterator",
12+
$inner,
13+
function ($k, $v) { return $v == 5; },
14+
$when);
15+
16+
foreach($stopper as $value)
17+
print " $value\n";
18+
print "Inner at (must be one more than last): ".$inner->current()."\n\n";
19+
}
20+
?>
21+
--EXPECT--
22+
new SplitInnerIterator(object(ArrayIterator), object(Closure), '0') returns object(SplitInnerIterator)
23+
1
24+
2
25+
3
26+
4
27+
5
28+
Inner at (must be one more than last): 6
29+
30+
new SplitInnerIterator(object(ArrayIterator), object(Closure), '1') returns object(SplitInnerIterator)
31+
1
32+
2
33+
3
34+
4
35+
Inner at (must be one more than last): 5

tests/SplitIterator_007.phpt

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
--TEST--
2+
SplitIterator: Basic test (split before)
3+
--DESCRIPTION--
4+
Same as
5+
--FILE--
6+
<?php
7+
require_once("SplitIterator.php");
8+
require_once("test_inc/SplitIterator_inc.php");
9+
10+
class SplitBeforeDivisibleIterator extends SplitIfDivisibleIterator {
11+
protected $splitWhen = self::SPLIT_BEFORE;
12+
}
13+
14+
foreach(array(array('range' => range(1, 10),
15+
'modulus' => 3),
16+
array('range' => range(1, 10),
17+
'modulus' => 5),
18+
array('range' => range(1, 10),
19+
'modulus' => 999),
20+
array('range' => array(),
21+
'modulus' => 5),
22+
array('range' => range(1, 10),
23+
'modulus' => 1),
24+
) as $test) {
25+
$it = TestHelpers::pnewdebug('SplitBeforeDivisibleIterator',
26+
new ArrayIterator($test['range']),
27+
$test['modulus']);
28+
foreach($it as $outerKey => $split) {
29+
printf(" New split iterator %s => %s:\n",
30+
TestHelpers::valdebug($outerKey),
31+
TestHelpers::valdebug($split));
32+
foreach($split as $key => $entry) {
33+
printf(" %s => %s\n",
34+
TestHelpers::valdebug($key),
35+
TestHelpers::valdebug($entry));
36+
}
37+
}
38+
print "Done.\n\n";
39+
}
40+
?>
41+
--EXPECT--
42+
new SplitBeforeDivisibleIterator(object(ArrayIterator), '3') returns object(SplitBeforeDivisibleIterator)
43+
New split iterator '0' => object(SplitInnerIterator):
44+
'0' => '1'
45+
'1' => '2'
46+
New split iterator '1' => object(SplitInnerIterator):
47+
'2' => '3'
48+
'3' => '4'
49+
'4' => '5'
50+
New split iterator '2' => object(SplitInnerIterator):
51+
'5' => '6'
52+
'6' => '7'
53+
'7' => '8'
54+
New split iterator '3' => object(SplitInnerIterator):
55+
'8' => '9'
56+
'9' => '10'
57+
Done.
58+
59+
new SplitBeforeDivisibleIterator(object(ArrayIterator), '5') returns object(SplitBeforeDivisibleIterator)
60+
New split iterator '0' => object(SplitInnerIterator):
61+
'0' => '1'
62+
'1' => '2'
63+
'2' => '3'
64+
'3' => '4'
65+
New split iterator '1' => object(SplitInnerIterator):
66+
'4' => '5'
67+
'5' => '6'
68+
'6' => '7'
69+
'7' => '8'
70+
'8' => '9'
71+
New split iterator '2' => object(SplitInnerIterator):
72+
'9' => '10'
73+
Done.
74+
75+
new SplitBeforeDivisibleIterator(object(ArrayIterator), '999') returns object(SplitBeforeDivisibleIterator)
76+
New split iterator '0' => object(SplitInnerIterator):
77+
'0' => '1'
78+
'1' => '2'
79+
'2' => '3'
80+
'3' => '4'
81+
'4' => '5'
82+
'5' => '6'
83+
'6' => '7'
84+
'7' => '8'
85+
'8' => '9'
86+
'9' => '10'
87+
Done.
88+
89+
new SplitBeforeDivisibleIterator(object(ArrayIterator), '5') returns object(SplitBeforeDivisibleIterator)
90+
Done.
91+
92+
new SplitBeforeDivisibleIterator(object(ArrayIterator), '1') returns object(SplitBeforeDivisibleIterator)
93+
New split iterator '0' => object(SplitInnerIterator):
94+
'0' => '1'
95+
New split iterator '1' => object(SplitInnerIterator):
96+
'1' => '2'
97+
New split iterator '2' => object(SplitInnerIterator):
98+
'2' => '3'
99+
New split iterator '3' => object(SplitInnerIterator):
100+
'3' => '4'
101+
New split iterator '4' => object(SplitInnerIterator):
102+
'4' => '5'
103+
New split iterator '5' => object(SplitInnerIterator):
104+
'5' => '6'
105+
New split iterator '6' => object(SplitInnerIterator):
106+
'6' => '7'
107+
New split iterator '7' => object(SplitInnerIterator):
108+
'7' => '8'
109+
New split iterator '8' => object(SplitInnerIterator):
110+
'8' => '9'
111+
New split iterator '9' => object(SplitInnerIterator):
112+
'9' => '10'
113+
Done.

0 commit comments

Comments
 (0)