Skip to content

Commit 3b7f19f

Browse files
committed
fixes #32: implemented EnumSet in base of a real BitSet
1 parent d7eabb5 commit 3b7f19f

File tree

8 files changed

+324
-201
lines changed

8 files changed

+324
-201
lines changed

README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ corresponding instance of `UserStatus` else an exception will be thrown.)
125125
## EnumMap
126126

127127
An ```EnumMap``` maps enumerators of the same type to data assigned to.
128+
128129
Internally the ```EnumMap``` is based of ```SplObjectStorage```.
129130

130131
use MabeEnum\EnumMap;
@@ -145,14 +146,19 @@ Internally the ```EnumMap``` is based of ```SplObjectStorage```.
145146
var_dump(iterator_to_array($enumSet)); // array(0 => UserStatus{$value=1});
146147

147148
// define key and value used for iteration
148-
$enumSet->setFlags(EnumSet::KEY_AS_NAME | EnumSet::CURRENT_AS_DATA);
149+
$enumSet->setFlags(EnumMap::KEY_AS_NAME | EnumMap::CURRENT_AS_DATA);
149150
var_dump(iterator_to_array($enumSet)); // array('ACTIVE' => 'aktiv');
150151

151152

152153
## EnumSet
153154

154-
An ```EnumSet``` groups enumerators of the same type together.
155-
Internally it's based of a list (array) of ordinal values.
155+
An ```EnumSet``` groups enumerators of the same enumeration type together.
156+
157+
Internally it's based of an integer bit set.
158+
159+
The maximun number of enumerators are limited by the size of an integer.
160+
161+
Enumerators will be ordered by the ordinal number.
156162

157163
use MabeEnum\EnumSet;
158164

src/MabeEnum/EnumSet.php

Lines changed: 82 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
use Iterator;
66
use Countable;
77
use InvalidArgumentException;
8+
use OutOfRangeException;
89

910
/**
10-
* EnumSet implementation in base of an array
11+
* EnumSet implementation in base of an integer bit set
1112
*
1213
* @link http://github.com/marc-mabe/php-enum for the canonical source repository
1314
* @copyright Copyright (c) 2015 Marc Bennewitz
@@ -16,59 +17,60 @@
1617
class EnumSet implements Iterator, Countable
1718
{
1819
/**
19-
* Flag for a unique set of enumerators
20-
*/
21-
const UNIQUE = 1;
22-
23-
/**
24-
* Flag for an ordered set of enumerators by ordinal
25-
*/
26-
const ORDERED = 2;
27-
28-
/**
20+
* Enumeration class
2921
* @var string
3022
*/
3123
private $enumClass;
3224

3325
/**
34-
* @var array
26+
* BitSet of all attached enumerations
27+
* @var int
3528
*/
36-
private $list = array();
29+
private $bitset = 0;
3730

3831
/**
32+
* Ordinal number of current iterator position
3933
* @var int
4034
*/
41-
private $index = 0;
35+
private $ordinal = 0;
4236

4337
/**
38+
* Highest possible ordinal number
4439
* @var int
4540
*/
46-
private $flags = self::UNIQUE;
41+
private $ordinalMax = 0;
4742

4843
/**
4944
* Constructor
5045
*
51-
* @param string $enumClass The classname of an enumeration the set is for
52-
* @param null|int $flags Flags to define behaviours
46+
* @param string $enumClass Classname of an enumeration the set is for
5347
* @throws InvalidArgumentException
5448
*/
55-
public function __construct($enumClass, $flags = null)
49+
public function __construct($enumClass)
5650
{
5751
if (!is_subclass_of($enumClass, __NAMESPACE__ . '\Enum')) {
5852
throw new InvalidArgumentException(sprintf(
5953
"This EnumSet can handle subclasses of '%s' only",
6054
__NAMESPACE__ . '\Enum'
6155
));
6256
}
63-
$this->enumClass = $enumClass;
6457

65-
if ($flags !== null) {
66-
$this->flags = (int) $flags;
58+
$this->enumClass = $enumClass;
59+
$this->ordinalMax = count($enumClass::getConstants());
60+
61+
if (PHP_INT_SIZE * 8 < $this->ordinalMax) {
62+
throw new OutOfRangeException(sprintf(
63+
"Your system can handle up to %u enumeration values within an EnumSet"
64+
. " but the given enumeration class '%s' has defined %u enumeration values",
65+
PHP_INT_SIZE * 8,
66+
$enumClass,
67+
$this->ordinalMax
68+
));
6769
}
6870
}
6971

7072
/**
71-
* Get the classname of the enumeration this set is for
73+
* Get the classname of enumeration this set is for
7274
* @return string
7375
*/
7476
public function getEnumClass()
@@ -77,108 +79,119 @@ public function getEnumClass()
7779
}
7880

7981
/**
80-
* Get flags of defined behaviours
81-
* @return int
82-
*/
83-
public function getFlags()
84-
{
85-
return $this->flags;
86-
}
87-
88-
/**
89-
* Attach a new enumerator or overwrite an existing one
82+
* Attach a new enumeration or overwrite an existing one
9083
* @param Enum|null|boolean|int|float|string $enum
9184
* @return void
9285
* @throws InvalidArgumentException On an invalid given enum
9386
*/
9487
public function attach($enum)
9588
{
9689
$enumClass = $this->enumClass;
97-
$ordinal = $enumClass::get($enum)->getOrdinal();
98-
99-
if (!($this->flags & self::UNIQUE) || !in_array($ordinal, $this->list, true)) {
100-
$this->list[] = $ordinal;
101-
102-
if ($this->flags & self::ORDERED) {
103-
sort($this->list);
104-
}
105-
}
90+
$this->bitset |= 1 << $enumClass::get($enum)->getOrdinal();
10691
}
10792

10893
/**
109-
* Test if the given enumerator exists
94+
* Detach all enumerations same as the given enum
11095
* @param Enum|null|boolean|int|float|string $enum
111-
* @return boolean
96+
* @return void
97+
* @throws InvalidArgumentException On an invalid given enum
11298
*/
113-
public function contains($enum)
99+
public function detach($enum)
114100
{
115101
$enumClass = $this->enumClass;
116-
return in_array($enumClass::get($enum)->getOrdinal(), $this->list, true);
102+
$this->bitset &= ~(1 << $enumClass::get($enum)->getOrdinal());
117103
}
118104

119105
/**
120-
* Detach all enumerators same as the given enumerator
106+
* Test if the given enumeration exists
121107
* @param Enum|null|boolean|int|float|string $enum
122-
* @return void
123-
* @throws InvalidArgumentException On an invalid given enum
108+
* @return boolean
124109
*/
125-
public function detach($enum)
110+
public function contains($enum)
126111
{
127112
$enumClass = $this->enumClass;
128-
$ordinal = $enumClass::get($enum)->getOrdinal();
129-
130-
while (($index = array_search($ordinal, $this->list, true)) !== false) {
131-
unset($this->list[$index]);
132-
}
133-
134-
// reset index positions to have a real list
135-
$this->list = array_values($this->list);
113+
return (bool)($this->bitset & (1 << $enumClass::get($enum)->getOrdinal()));
136114
}
137115

138116
/* Iterator */
139117

140118
/**
141-
* Get the current enumerator
142-
* @return Enum|null Returns the current enumerator or NULL on an invalid iterator position
119+
* Get current Enum
120+
* @return Enum|null Returns current Enum or NULL on an invalid iterator position
143121
*/
144122
public function current()
145123
{
146-
if (!isset($this->list[$this->index])) {
147-
return null;
124+
if ($this->valid()) {
125+
$enumClass = $this->enumClass;
126+
return $enumClass::getByOrdinal($this->ordinal);
148127
}
149128

150-
$enumClass = $this->enumClass;
151-
return $enumClass::getByOrdinal($this->list[$this->index]);
129+
return null;
152130
}
153131

154132
/**
155-
* Get the current iterator position
133+
* Get ordinal number of current iterator position
156134
* @return int
157135
*/
158136
public function key()
159137
{
160-
return $this->index;
138+
return $this->ordinal;
161139
}
162140

141+
/**
142+
* Go to the next iterator position
143+
* @return void
144+
*/
163145
public function next()
164146
{
165-
++$this->index;
147+
if ($this->ordinal !== $this->ordinalMax) {
148+
do {
149+
if (++$this->ordinal === $this->ordinalMax) {
150+
return;
151+
}
152+
} while (!($this->bitset & (1 << $this->ordinal)));
153+
}
166154
}
167155

156+
/**
157+
* Go to the first iterator position
158+
* @return void
159+
*/
168160
public function rewind()
169161
{
170-
$this->index = 0;
162+
$this->ordinal = -1;
163+
do {
164+
if (++$this->ordinal === $this->ordinalMax) {
165+
return;
166+
}
167+
} while (!($this->bitset & (1 << $this->ordinal)));
171168
}
172169

170+
/**
171+
* Test if the iterator in a valid state
172+
* @return boolean
173+
*/
173174
public function valid()
174175
{
175-
return isset($this->list[$this->index]);
176+
return $this->bitset & (1 << $this->ordinal) && $this->ordinal !== $this->ordinalMax;
176177
}
177178

178179
/* Countable */
179180

181+
/**
182+
* Count the number of elements
183+
* @return int
184+
*/
180185
public function count()
181186
{
182-
return count($this->list);
187+
$cnt = 0;
188+
$ord = 0;
189+
do {
190+
if ($this->bitset & (1 << $ord++)) {
191+
++$cnt;
192+
}
193+
} while ($ord !== $this->ordinalMax);
194+
195+
return $cnt;
183196
}
184197
}

0 commit comments

Comments
 (0)