HashTable   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 277
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 60
c 1
b 0
f 0
dl 0
loc 277
ccs 79
cts 79
cp 1
rs 9.84
wmc 32

23 Methods

Rating   Name   Duplication   Size   Complexity  
A toArray() 0 13 3
A keys() 0 3 1
A getIterator() 0 3 1
A get() 0 9 2
A replace() 0 9 2
A clear() 0 4 1
A stream() 0 3 1
A values() 0 3 1
A forEach() 0 4 2
A unset() 0 12 2
A add() 0 3 1
A remove() 0 12 2
A offsetExists() 0 3 1
A addAll() 0 3 1
A hasKey() 0 3 1
A empty() 0 3 1
A __construct() 0 3 2
A set() 0 6 1
A contains() 0 3 1
A offsetGet() 0 4 1
A count() 0 3 1
A offsetUnset() 0 3 1
A offsetSet() 0 6 2
1
<?php
2
3
namespace Bdf\Collection;
4
5
use BadMethodCallException;
6
use Bdf\Collection\Stream\ArrayCombineStream;
7
use Bdf\Collection\Stream\StreamInterface;
8
use Bdf\Collection\Util\Hash;
9
use OutOfBoundsException;
10
use function array_combine;
11
use function array_search;
12
use function array_values;
13
use function count;
14
15
/**
16
 * Table implementation using an hash table
17
 *
18
 * This table allow key of any type, including array or object
19
 * Like HashSet, a custom hash function can be provided for custom key comparison. Can be useful for create a case insensitive key table
20
 *
21
 * By default the used hash function is Hash::compute()
22
 *
23
 * This table can be used like any other table implementations (including foreach, and array access), but without key type limitation
24
 *
25
 * /!\ Be careful with transformation to array, which can have an unexpected behavior with complex keys
26
 *     For some operations you may remove keys from the table
27
 *
28
 * <code>
29
 * // Create a case insensitive table
30
 * $ciTable = new HashTable('strtolower'); // Transform keys to lower case
31
 *
32
 * $ciTable->set('Foo', 'bar');
33
 * $ciTable->get('FOO'); // 'bar'
34
 *
35
 * // Use HashTable with multiple-keys indexing using array
36
 * $table = new HashTable();
37
 *
38
 * $table[[123, 'aze']] = new Entity(1);
39
 * $table[[456, 'rty']] = new Entity(2);
40
 *
41
 * $table[123, 'aze']; // Returns Entity(1)
42
 *
43
 * // Use object as key
44
 * $table[new Key()] = 'value';
45
 * </code>
46
 *
47
 * (i) About performance :
48
 *     - The HashTable is about 2 times slower than ArrayCollection with string key
49
 *     - Using a complex key (like and array or an object) has no significant impact on performance than using string key
50
 *     - The memory usage is very dependent on the key (custom hash has the lower usage, array or objects has the higher one)
51
 *
52
 * @template K
53
 * @template T
54
 * @implements TableInterface<K, T>
55
 */
56
class HashTable implements TableInterface
57
{
58
    /**
59
     * @var K[]
60
     */
61
    private $keys = [];
62
63
    /**
64
     * @var T[]
65
     */
66
    private $values = [];
67
68
    /**
69
     * @var callable(K):array-key
70
     */
71
    private $hashFunction;
72
73
74
    /**
75
     * HashTable constructor.
76
     *
77
     * @param callable(K):array-key|null $hashFunction The the hash function. Takes as parameter the element to hash, and should return a string
78
     */
79 25
    public function __construct(?callable $hashFunction = null)
80
    {
81 25
        $this->hashFunction = $hashFunction ?: [Hash::class, 'compute'];
82 25
    }
83
84
    /**
85
     * {@inheritdoc}
86
     */
87 1
    public function remove($element, bool $strict = false): bool
88
    {
89 1
        $key = array_search($element, $this->values, $strict);
90
91 1
        if ($key === false) {
92 1
            return false;
93
        }
94
95 1
        unset($this->keys[$key]);
96 1
        unset($this->values[$key]);
97
98 1
        return true;
99
    }
100
101
    /**
102
     * {@inheritdoc}
103
     */
104 21
    public function set($key, $value): void
105
    {
106 21
        $hash = ($this->hashFunction)($key);
107
108 21
        $this->values[$hash] = $value;
109 21
        $this->keys[$hash] = $key;
110 21
    }
111
112
    /**
113
     * {@inheritdoc}
114
     */
115 11
    public function &get($key)
116
    {
117 11
        $hash = ($this->hashFunction)($key);
118
119 11
        if (!isset($this->values[$hash])) {
120 1
            throw new OutOfBoundsException('The given key cannot be found into the table');
121
        }
122
123 10
        return $this->values[$hash];
124
    }
125
126
    /**
127
     * {@inheritdoc}
128
     */
129 2
    public function add($element): bool
130
    {
131 2
        throw new BadMethodCallException('HashTable do not supports adding an elements without specify a key');
132
    }
133
134
    /**
135
     * {@inheritdoc}
136
     */
137 1
    public function addAll(iterable $elements): bool
138
    {
139 1
        throw new BadMethodCallException('HashTable do not supports adding an elements without specify a key');
140
    }
141
142
    /**
143
     * {@inheritdoc}
144
     */
145 2
    public function replace(iterable $elements): bool
146
    {
147 2
        $this->clear();
148
149 2
        foreach ($elements as $key => $value) {
150 2
            $this->set($key, $value);
151
        }
152
153 2
        return true;
154
    }
155
156
    /**
157
     * {@inheritdoc}
158
     */
159 6
    public function unset($key): bool
160
    {
161 6
        $hash = ($this->hashFunction)($key);
162
163 6
        if (!isset($this->values[$hash])) {
164 5
            return false;
165
        }
166
167 6
        unset($this->values[$hash]);
168 6
        unset($this->keys[$hash]);
169
170 6
        return true;
171
    }
172
173
    /**
174
     * {@inheritdoc}
175
     */
176 2
    public function contains($element, bool $strict = false): bool
177
    {
178 2
        return array_search($element, $this->values, $strict) !== false;
179
    }
180
181
    /**
182
     * {@inheritdoc}
183
     */
184 9
    public function hasKey($key): bool
185
    {
186 9
        return isset($this->values[($this->hashFunction)($key)]);
187
    }
188
189
    /**
190
     * {@inheritdoc}
191
     */
192 1
    public function keys(): array
193
    {
194 1
        return array_values($this->keys);
195
    }
196
197
    /**
198
     * {@inheritdoc}
199
     */
200 1
    public function values(): array
201
    {
202 1
        return array_values($this->values);
203
    }
204
205
    /**
206
     * {@inheritdoc}
207
     */
208 3
    public function clear(): void
209
    {
210 3
        $this->values = [];
211 3
        $this->keys = [];
212 3
    }
213
214
    /**
215
     * {@inheritdoc}
216
     */
217 2
    public function empty(): bool
218
    {
219 2
        return empty($this->keys);
220
    }
221
222
    /**
223
     * {@inheritdoc}
224
     *
225
     * By default, this method try to create an associative array. But this purpose is discouraged because it have undefined behavior with non-scalar keys
226
     * With the parameter $assoc set to false, an array in form : [ [ Key, Value], ... ] is returned
227
     *
228
     * <code>
229
     * $table = new HashTable();
230
     * $table->set('foo', 'bar');
231
     * $table->set('oof', 'baz');
232
     *
233
     * $table->toArray();
234
     * // [ 'foo' => 'bar'
235
     * //   'oof' => 'baz' ]
236
     *
237
     * $table->toArray(false);
238
     * // [ ['foo', 'bar']
239
     * //   ['oof', 'baz'] ]
240
     * </code>
241
     *
242
     * @param boolean $assoc If set to true will make associative array, or false to return array in form [ [Key, Value], ... ]
243
     */
244 3
    public function toArray(bool $assoc = true): array
245
    {
246 3
        if ($assoc) {
247 3
            return array_combine($this->keys, $this->values);
248
        }
249
250 1
        $array = [];
251
252 1
        foreach ($this->keys as $hash => $key) {
253 1
            $array[] = [$key, $this->values[$hash]];
254
        }
255
256 1
        return $array;
0 ignored issues
show
introduced by
The expression return $array returns an array which contains values of type array<integer,Bdf\Collection\K|Bdf\Collection\T> which are incompatible with the return type Bdf\Collection\T mandated by Bdf\Collection\CollectionInterface::toArray().
Loading history...
257
    }
258
259
    /**
260
     * {@inheritdoc}
261
     */
262 2
    public function stream(): StreamInterface
263
    {
264 2
        return new ArrayCombineStream($this->keys, $this->values);
265
    }
266
267
    /**
268
     * {@inheritdoc}
269
     */
270 1
    public function forEach(callable $consumer): void
271
    {
272 1
        foreach ($this->keys as $hash => $key) {
273 1
            $consumer($this->values[$hash], $key);
274
        }
275 1
    }
276
277
    /**
278
     * {@inheritdoc}
279
     */
280 1
    public function getIterator(): \Traversable
281
    {
282 1
        return $this->stream();
283
    }
284
285
    /**
286
     * {@inheritdoc}
287
     */
288 2
    public function offsetExists($offset): bool
289
    {
290 2
        return $this->hasKey($offset);
291
    }
292
293
    /**
294
     * {@inheritdoc}
295
     */
296
    #[\ReturnTypeWillChange]
297 5
    public function &offsetGet($offset)
298
    {
299 5
        return $this->get($offset);
300
    }
301
302
    /**
303
     * {@inheritdoc}
304
     *
305
     * @param K $offset
0 ignored issues
show
Bug introduced by
The type Bdf\Collection\K was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
306
     * @param T $value
307
     *
308
     * @return void
309
     */
310 8
    public function offsetSet($offset, $value): void
311
    {
312 8
        if ($offset === null) {
313 1
            $this->add($value);
314
        } else {
315 7
            $this->set($offset, $value);
316
        }
317 7
    }
318
319
    /**
320
     * {@inheritdoc}
321
     */
322 1
    public function offsetUnset($offset): void
323
    {
324 1
        $this->unset($offset);
325 1
    }
326
327
    /**
328
     * {@inheritdoc}
329
     */
330 4
    public function count(): int
331
    {
332 4
        return count($this->values);
333
    }
334
}
335