Skip to content

Commit af186da

Browse files
committed
Merge pull request #51 from TuxCoder/master-bitset
Dynamic BitSet size, Getter Setter for BitSet
2 parents 07dfc6c + 6a5e61a commit af186da

File tree

2 files changed

+133
-29
lines changed

2 files changed

+133
-29
lines changed

src/EnumSet.php

Lines changed: 92 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use Iterator;
66
use Countable;
77
use InvalidArgumentException;
8-
use OutOfRangeException;
98

109
/**
1110
* EnumSet implementation in base of an integer bit set
@@ -23,10 +22,10 @@ class EnumSet implements Iterator, Countable
2322
private $enumeration;
2423

2524
/**
26-
* BitSet of all attached enumerations
27-
* @var int
25+
* BitSet of all attached enumerations in little endian
26+
* @var string
2827
*/
29-
private $bitset = 0;
28+
private $bitset;
3029

3130
/**
3231
* Ordinal number of current iterator position
@@ -54,19 +53,12 @@ public function __construct($enumeration)
5453
__NAMESPACE__ . '\Enum'
5554
));
5655
}
57-
56+
5857
$this->enumeration = $enumeration;
5958
$this->ordinalMax = count($enumeration::getConstants());
60-
61-
if (PHP_INT_SIZE * 8 < $this->ordinalMax) {
62-
throw new OutOfRangeException(sprintf(
63-
"Your system can handle up to %u enumerators within an EnumSet"
64-
. " but the given enumeration '%s' has defined %u enumerators",
65-
PHP_INT_SIZE * 8,
66-
$enumeration,
67-
$this->ordinalMax
68-
));
69-
}
59+
60+
// init the bitset with zeros
61+
$this->bitset = str_repeat("\0", ceil($this->ordinalMax / 8));
7062
}
7163

7264
/**
@@ -97,7 +89,7 @@ public function getEnumeration()
9789
public function attach($enumerator)
9890
{
9991
$enumeration = $this->enumeration;
100-
$this->bitset |= 1 << $enumeration::get($enumerator)->getOrdinal();
92+
$this->setBit($enumeration::get($enumerator)->getOrdinal());
10193
}
10294

10395
/**
@@ -109,7 +101,7 @@ public function attach($enumerator)
109101
public function detach($enumerator)
110102
{
111103
$enumeration = $this->enumeration;
112-
$this->bitset &= ~(1 << $enumeration::get($enumerator)->getOrdinal());
104+
$this->unsetBit($enumeration::get($enumerator)->getOrdinal());
113105
}
114106

115107
/**
@@ -120,7 +112,7 @@ public function detach($enumerator)
120112
public function contains($enumerator)
121113
{
122114
$enumeration = $this->enumeration;
123-
return (bool)($this->bitset & (1 << $enumeration::get($enumerator)->getOrdinal()));
115+
return $this->getBit($enumeration::get($enumerator)->getOrdinal());
124116
}
125117

126118
/* Iterator */
@@ -156,11 +148,11 @@ public function key()
156148
public function next()
157149
{
158150
do {
159-
if (++$this->ordinal >= $this->ordinalMax) {
151+
if (++ $this->ordinal >= $this->ordinalMax) {
160152
$this->ordinal = $this->ordinalMax;
161153
return;
162154
}
163-
} while (!($this->bitset & (1 << $this->ordinal)));
155+
} while (!$this->getBit($this->ordinal));
164156
}
165157

166158
/**
@@ -170,7 +162,7 @@ public function next()
170162
*/
171163
public function rewind()
172164
{
173-
if ($this->bitset) {
165+
if (trim($this->bitset, "\0") !== '') {
174166
$this->ordinal = -1;
175167
$this->next();
176168
} else {
@@ -184,7 +176,7 @@ public function rewind()
184176
*/
185177
public function valid()
186178
{
187-
return $this->bitset & (1 << $this->ordinal) && $this->ordinal !== $this->ordinalMax;
179+
return $this->ordinal !== $this->ordinalMax && $this->getBit($this->ordinal);
188180
}
189181

190182
/* Countable */
@@ -198,11 +190,88 @@ public function count()
198190
$cnt = 0;
199191
$ord = 0;
200192
do {
201-
if ($this->bitset & (1 << $ord++)) {
193+
if ($this->getBit($ord++)) {
202194
++$cnt;
203195
}
204196
} while ($ord !== $this->ordinalMax);
205197

206198
return $cnt;
207199
}
200+
201+
/**
202+
* Get the binary bitset
203+
*
204+
* @return string Returns the binary bitset in big-endian order
205+
*/
206+
public function getBitset()
207+
{
208+
return strrev($this->bitset);
209+
}
210+
211+
/**
212+
* Set the bitset.
213+
* NOTE: It resets the current position of the iterator
214+
*
215+
* @param string $bitset The binary bitset in big-endian order
216+
* @return void
217+
* @throws InvalidArgumentException On a non string is given as Parameter
218+
*/
219+
public function setBitset($bitset)
220+
{
221+
if (! is_string($bitset)) {
222+
throw new InvalidArgumentException("bitset must be a string");
223+
}
224+
225+
$bitset = strrev($bitset);
226+
$size = ceil($this->ordinalMax / 8);
227+
$sizeIn = strlen($bitset);
228+
229+
if ($sizeIn < $size) {
230+
// add "\0" if the given bitset is not long enough
231+
$bitset .= str_repeat("\0", $size - $sizeIn);
232+
} elseif ($sizeIn > $size) {
233+
$bitset = substr($bitset, 0, $size);
234+
}
235+
236+
$this->bitset = $bitset;
237+
238+
$this->rewind();
239+
}
240+
241+
/**
242+
* get a bit at the given ordinal
243+
*
244+
* @param $ordinal int Number of bit to get
245+
* @return boolean
246+
*/
247+
private function getBit($ordinal)
248+
{
249+
return (ord($this->bitset[(int) ($ordinal / 8)]) & 1 << ($ordinal % 8)) !== 0;
250+
}
251+
252+
/**
253+
* set a bit at the given ordinal
254+
*
255+
* @param $ordinal int
256+
* number of bit to manipulate
257+
* @return void
258+
*/
259+
private function setBit($ordinal)
260+
{
261+
$byte = (int) ($ordinal / 8);
262+
$this->bitset[$byte] = $this->bitset[$byte] | chr(1 << ($ordinal % 8));
263+
}
264+
265+
/**
266+
* reset a bit at the given ordinal
267+
*
268+
* @param $ordinal int
269+
* number of bit to set to false
270+
* @return void
271+
*/
272+
private function unsetBit($ordinal)
273+
{
274+
$byte = (int) ($ordinal / 8);
275+
$this->bitset[$byte] = $this->bitset[$byte] & chr(~ (1 << ($ordinal % 8)));
276+
}
208277
}

tests/MabeEnumTest/EnumSetTest.php

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use MabeEnumTest\TestAsset\EnumInheritance;
99
use MabeEnumTest\TestAsset\Enum32;
1010
use MabeEnumTest\TestAsset\Enum64;
11+
use MabeEnumTest\TestAsset\Enum65;
1112
use PHPUnit_Framework_TestCase as TestCase;
1213

1314
/**
@@ -224,10 +225,6 @@ public function test32EnumerationsSet()
224225

225226
public function test64EnumerationsSet()
226227
{
227-
if (PHP_INT_SIZE === 4) {
228-
$this->setExpectedException('OutOfRangeException');
229-
}
230-
231228
$enumSet = new EnumSet('MabeEnumTest\TestAsset\Enum64');
232229
foreach (Enum64::getConstants() as $name => $value) {
233230
$this->assertFalse($enumSet->contains($value));
@@ -247,7 +244,45 @@ public function test64EnumerationsSet()
247244

248245
public function test65EnumerationsSet()
249246
{
250-
$this->setExpectedException('OutOfRangeException');
251-
new EnumSet('MabeEnumTest\TestAsset\Enum65');
247+
$enum = new EnumSet('MabeEnumTest\TestAsset\Enum65');
248+
249+
$this->assertNull($enum->attach(Enum65::getByOrdinal(64)));
250+
$enum->next();
251+
$this->assertTrue($enum->valid());
252+
}
253+
254+
public function testBitset()
255+
{
256+
$enumSet1 = new EnumSet('MabeEnumTest\TestAsset\Enum65');
257+
258+
$enum1 = Enum65::ONE;
259+
$enum2 = Enum65::TWO;
260+
$enum3 = Enum65::SIXTYFIVE;
261+
$enum4 = Enum65::SIXTYFOUR;
262+
263+
$this->assertNull($enumSet1->attach($enum1));
264+
$this->assertNull($enumSet1->attach($enum2));
265+
$this->assertNull($enumSet1->attach($enum3));
266+
$this->assertNull($enumSet1->attach($enum4));
267+
268+
$this->assertNull($enumSet1->detach($enum1));
269+
$bitset = $enumSet1->getBitset();
270+
$this->assertTrue(strlen($bitset) > 8);
271+
$enumSet2 = new EnumSet('MabeEnumTest\TestAsset\Enum65');
272+
$enumSet2->setBitset($bitset);
273+
274+
$this->assertFalse($enumSet2->contains($enum1));
275+
$this->assertTrue($enumSet2->contains($enum2));
276+
$this->assertTrue($enumSet2->contains($enum3));
277+
$this->assertTrue($enumSet2->contains($enum4));
278+
$this->assertTrue($enumSet2->count() == 3);
279+
}
280+
281+
public function testFalseBitsetArgumentExceptionIfNotString()
282+
{
283+
$this->setExpectedException('InvalidArgumentException');
284+
285+
$enum = new EnumSet('MabeEnumTest\TestAsset\Enum65');
286+
$enum->setBitset(0);
252287
}
253288
}

0 commit comments

Comments
 (0)