/
Set.php
331 lines (287 loc) · 8.05 KB
/
Set.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
<?php
namespace JesseSchalken;
use ArrayAccess;
use Countable;
use IteratorAggregate;
use Traversable;
/**
* Copy of java.utils.Set implementing ArrayAccess, Countable and IteratorAggregate with O(1) conversion to/from array
* keys. Only integers and strings (keys of an array) are supported.
*
* ArrayAccess is implemented as follows:
*
* - `$set[$value]` is the same as `$set->contains($value)`
* - `$set[$value] = true` is the same as `$set->add($value)`
* - `$set[$value] = false` is the same as `$set->remove($value)`
* - `isset($set[$value])` is the same an _error_
* - `unset($set[$value])` is the same an _error_
*
* IteratorAggregate::getIterator() returns an iterator producing the elements as values and 0,1,2...n as keys.
*
* @link https://docs.oracle.com/javase/7/docs/api/java/util/Set.html
*/
class Set implements ArrayAccess, Countable, IteratorAggregate {
/**
* @param (array|Traversable)[] $sets
* @return self
*/
public static function unionAll(array $sets) {
$self = new self();
foreach ($sets as $set) {
$self->addAll($set);
}
return $self;
}
/**
* @param array|Traversable $a
* @param array|Traversable $b
* @return self
*/
public static function intersect($a, $b) {
$a = new self($a);
$a->retainAll($b);
return $a;
}
/**
* _O(1)_ Create a set from the keys of an array. Note that `$array === Set::fromArrayKeys($array)->toArrayKeys()`.
* @param array $array
* @return Set
*/
public static function fromArrayKeys(array $array) {
$set = new self();
$set->set = $array;
return $set;
}
/**
* @param array|Traversable $t
* @return array
*/
private static function toKeys($t) {
if ($t instanceof self) {
return $t->set;
} else if (is_array($t)) {
return array_fill_keys($t, true);
} else if ($t instanceof Traversable) {
$s = array();
foreach ($t as $v) {
$s[$v] = true;
}
return $s;
} else {
$type = is_object($t) ? get_class($t) : gettype($t);
throw new \InvalidArgumentException("Cannot use a '$type' as a set");
}
}
/** @var array */
private $set;
/**
* _O(n)_ Create a set.
* @param array|Traversable $contents
*/
public function __construct($contents = array()) {
$this->set = self::toKeys($contents);
}
#region java.util.Set
/**
* _O(1)_ Adds the specified value to the set, if not already present.
* @param int|string $e
* @return void
*/
public function add($e) {
$this[$e] = true;
}
/**
* _O(n)_ Set union. Adds all the values in the specified array or Traversable to the set, if not already present.
* @param array|Traversable $c
* @return void
*/
public function addAll($c) {
$this->set = array_replace($this->set, self::toKeys($c));
}
/**
* _O(1)_ Removes all of the values from the set.
* @return void
*/
public function clear() {
$this->set = array();
}
/**
* _O(1)_ Returns true if this set contains the specified value.
* @param int|string $e
* @return bool
*/
public function contains($e) {
return $this[$e];
}
/**
* _O(n)_ Returns true if this set contains all of the values of the specified array or Traversable.
* @param array|Traversable $c
* @return bool
*/
public function containsAll($c) {
return !array_diff_key(self::toKeys($c), $this->set);
}
/**
* _O(n)_ Returns true if this set contains exactly the values of the specified array or Traversable.
* @param array|Traversable $c
* @return bool
*/
public function equals($c) {
$c = self::toKeys($c);
return
!array_diff_key($this->set, $c) &&
!array_diff_key($c, $this->set);
}
/**
* _O(1)_ Return true if this set is empty.
* @return bool
*/
public function isEmpty() {
return !$this->set;
}
/**
* Returns an iterator producing the elements as values and integers 0,1,2...n as keys.
* @return \Iterator
*/
public function iterator() {
return $this->getIterator();
}
/**
* _O(1)_ Removes the specified value from the set, if present.
* @param int|string $e
* @return void
*/
public function remove($e) {
$this[$e] = false;
}
/**
* _O(n)_ Set difference. Removes all of the values from the specified array or Traversable from this set, if present.
* @param array|Traversable $c
* @return void
*/
public function removeAll($c) {
$this->set = array_diff_key($this->set, self::toKeys($c));
}
/**
* _O(n)_ Set intersection. Removes all of the values *not* in the specified array or Traversable from this set.
* @param array|Traversable $c
* @return void
*/
public function retainAll($c) {
$this->set = array_intersect_key($this->set, self::toKeys($c));
}
/**
* _O(1)_ Returns the number of values in this set.
* @return int
*/
public function size() {
return $this->count();
}
/**
* _O(n)_ Returns an array containing the elements of this set as values.
* @return array
*/
public function toArray() {
return array_keys($this->set);
}
#endregion
/**
* _O(?)_ Sort the set.
* @param int $sort_flags Flags to pass to ksort(). SORT_NUMERIC or SORT_STRING are recommended.
* @param bool $reverse Sort in ascending (false, default) or descending (true).
* @return void
*/
public function sort($sort_flags = SORT_REGULAR, $reverse = false) {
if ($reverse) {
krsort($this->set, $sort_flags);
} else {
ksort($this->set, $sort_flags);
}
}
/**
* _O(n)_ Reverse the set.
* @return void
*/
public function reverse() {
$this->set = array_reverse($this->set, true);
}
/**
* _O(1)_ Returns an array containing the elements of this set as keys. The value may be anything (even null) and
* you should *not* depend on what is used as the value.
* @return array
*/
public function toArrayKeys() {
return $this->set;
}
#region ArrayAccess
/**
* _O(1)_
* @param int|string $key
* @return bool
*/
public function offsetGet($key) {
return array_key_exists($key, $this->set);
}
/**
* _O(1)_
* @param int|string $key
* @param bool $value
* @return void
*/
public function offsetSet($key, $value) {
if ($value) {
$this->set[$key] = true;
} else {
unset($this->set[$key]);
}
}
/**
* @param mixed $key
* @return bool|void
* @throws \BadMethodCallException
* @deprecated
*/
public function offsetExists($key) {
throw new \BadMethodCallException(__METHOD__ . ' is not supported');
}
/**
* @param mixed $key
* @throws \BadMethodCallException
* @deprecated
*/
public function offsetUnset($key) {
throw new \BadMethodCallException(__METHOD__ . ' is not supported');
}
#endregion
#region IteratorAggregate
public function getIterator() {
return new ArrayKeyIterator($this->set);
}
#endregion
#region Countable
/**
* _O(1)_ Returns the number of values in the set.
* @return int
*/
public function count() {
return count($this->set);
}
#endregion
}
class ArrayKeyIterator extends \ArrayIterator {
private $i = 0;
public function current() {
return parent::key();
}
public function next() {
parent::next();
$this->i++;
}
public function key() {
return $this->i;
}
public function rewind() {
parent::rewind();
$this->i = 0;
}
}