5
5
use Iterator ;
6
6
use Countable ;
7
7
use InvalidArgumentException ;
8
+ use OutOfRangeException ;
8
9
9
10
/**
10
- * EnumSet implementation in base of an array
11
+ * EnumSet implementation in base of an integer bit set
11
12
*
12
13
* @link http://github.com/marc-mabe/php-enum for the canonical source repository
13
14
* @copyright Copyright (c) 2015 Marc Bennewitz
16
17
class EnumSet implements Iterator, Countable
17
18
{
18
19
/**
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
29
21
* @var string
30
22
*/
31
23
private $ enumClass ;
32
24
33
25
/**
34
- * @var array
26
+ * BitSet of all attached enumerations
27
+ * @var int
35
28
*/
36
- private $ list = array () ;
29
+ private $ bitset = 0 ;
37
30
38
31
/**
32
+ * Ordinal number of current iterator position
39
33
* @var int
40
34
*/
41
- private $ index = 0 ;
35
+ private $ ordinal = 0 ;
42
36
43
37
/**
38
+ * Highest possible ordinal number
44
39
* @var int
45
40
*/
46
- private $ flags = self :: UNIQUE ;
41
+ private $ ordinalMax = 0 ;
47
42
48
43
/**
49
44
* Constructor
50
45
*
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
53
47
* @throws InvalidArgumentException
54
48
*/
55
- public function __construct ($ enumClass, $ flags = null )
49
+ public function __construct ($ enumClass )
56
50
{
57
51
if (!is_subclass_of ($ enumClass , __NAMESPACE__ . '\Enum ' )) {
58
52
throw new InvalidArgumentException (sprintf (
59
53
"This EnumSet can handle subclasses of '%s' only " ,
60
54
__NAMESPACE__ . '\Enum '
61
55
));
62
56
}
63
- $ this ->enumClass = $ enumClass ;
64
57
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
+ ));
67
69
}
68
70
}
69
71
70
72
/**
71
- * Get the classname of the enumeration this set is for
73
+ * Get the classname of enumeration this set is for
72
74
* @return string
73
75
*/
74
76
public function getEnumClass ()
@@ -77,108 +79,119 @@ public function getEnumClass()
77
79
}
78
80
79
81
/**
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
90
83
* @param Enum|null|boolean|int|float|string $enum
91
84
* @return void
92
85
* @throws InvalidArgumentException On an invalid given enum
93
86
*/
94
87
public function attach ($ enum )
95
88
{
96
89
$ 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 ();
106
91
}
107
92
108
93
/**
109
- * Test if the given enumerator exists
94
+ * Detach all enumerations same as the given enum
110
95
* @param Enum|null|boolean|int|float|string $enum
111
- * @return boolean
96
+ * @return void
97
+ * @throws InvalidArgumentException On an invalid given enum
112
98
*/
113
- public function contains ($ enum )
99
+ public function detach ($ enum )
114
100
{
115
101
$ enumClass = $ this ->enumClass ;
116
- return in_array ( $ enumClass ::get ($ enum )->getOrdinal (), $ this -> list , true );
102
+ $ this -> bitset &= ~( 1 << $ enumClass ::get ($ enum )->getOrdinal ());
117
103
}
118
104
119
105
/**
120
- * Detach all enumerators same as the given enumerator
106
+ * Test if the given enumeration exists
121
107
* @param Enum|null|boolean|int|float|string $enum
122
- * @return void
123
- * @throws InvalidArgumentException On an invalid given enum
108
+ * @return boolean
124
109
*/
125
- public function detach ($ enum )
110
+ public function contains ($ enum )
126
111
{
127
112
$ 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 ()));
136
114
}
137
115
138
116
/* Iterator */
139
117
140
118
/**
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
143
121
*/
144
122
public function current ()
145
123
{
146
- if (!isset ($ this ->list [$ this ->index ])) {
147
- return null ;
124
+ if ($ this ->valid ()) {
125
+ $ enumClass = $ this ->enumClass ;
126
+ return $ enumClass ::getByOrdinal ($ this ->ordinal );
148
127
}
149
128
150
- $ enumClass = $ this ->enumClass ;
151
- return $ enumClass ::getByOrdinal ($ this ->list [$ this ->index ]);
129
+ return null ;
152
130
}
153
131
154
132
/**
155
- * Get the current iterator position
133
+ * Get ordinal number of current iterator position
156
134
* @return int
157
135
*/
158
136
public function key ()
159
137
{
160
- return $ this ->index ;
138
+ return $ this ->ordinal ;
161
139
}
162
140
141
+ /**
142
+ * Go to the next iterator position
143
+ * @return void
144
+ */
163
145
public function next ()
164
146
{
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
+ }
166
154
}
167
155
156
+ /**
157
+ * Go to the first iterator position
158
+ * @return void
159
+ */
168
160
public function rewind ()
169
161
{
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 )));
171
168
}
172
169
170
+ /**
171
+ * Test if the iterator in a valid state
172
+ * @return boolean
173
+ */
173
174
public function valid ()
174
175
{
175
- return isset ( $ this ->list [ $ this ->index ]) ;
176
+ return $ this ->bitset & ( 1 << $ this ->ordinal ) && $ this -> ordinal !== $ this -> ordinalMax ;
176
177
}
177
178
178
179
/* Countable */
179
180
181
+ /**
182
+ * Count the number of elements
183
+ * @return int
184
+ */
180
185
public function count ()
181
186
{
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 ;
183
196
}
184
197
}
0 commit comments