1
|
|
|
<?php |
2
|
|
|
/* |
3
|
|
|
* Copyright (c) Nate Brunette. |
4
|
|
|
* Distributed under the MIT License (http://opensource.org/licenses/MIT) |
5
|
|
|
*/ |
6
|
|
|
|
7
|
|
|
namespace Tebru\Collection; |
8
|
|
|
|
9
|
|
|
use OutOfRangeException; |
10
|
|
|
|
11
|
|
|
/** |
12
|
|
|
* Class HashMap |
13
|
|
|
* |
14
|
|
|
* [@see MapInterface] implementation |
15
|
|
|
* |
16
|
|
|
* @author Nate Brunette <[email protected]> |
17
|
|
|
*/ |
18
|
|
|
class HashMap extends AbstractMap |
19
|
|
|
{ |
20
|
|
|
/** |
21
|
|
|
* The mapping keys |
22
|
|
|
* |
23
|
|
|
* @var array |
24
|
|
|
*/ |
25
|
|
|
protected $keys = []; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* The mapping values |
29
|
|
|
* |
30
|
|
|
* @var array |
31
|
|
|
*/ |
32
|
|
|
protected $values = []; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* Removes all mappings from map |
36
|
|
|
* |
37
|
|
|
* @return void |
38
|
|
|
*/ |
39
|
1 |
|
public function clear() |
40
|
|
|
{ |
41
|
1 |
|
$this->keys = []; |
42
|
1 |
|
$this->values = []; |
43
|
1 |
|
} |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* Returns true if the key exists in the lookup table |
47
|
|
|
* |
48
|
|
|
* @param mixed $key |
49
|
|
|
* @return bool |
50
|
|
|
*/ |
51
|
23 |
|
public function containsKey($key): bool |
52
|
|
|
{ |
53
|
23 |
|
return array_key_exists($this->hashCode($key), $this->keys); |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* Returns true if the value exists in the map |
58
|
|
|
* |
59
|
|
|
* By default this method will use strict comparison checking, passing false |
60
|
|
|
* in will use a double equals (==) instead. |
61
|
|
|
* |
62
|
|
|
* @param mixed $value |
63
|
|
|
* @param bool $strict |
64
|
|
|
* @return bool |
65
|
|
|
*/ |
66
|
3 |
|
public function containsValue($value, bool $strict = true): bool |
67
|
|
|
{ |
68
|
3 |
|
return in_array($value, $this->values, $strict); |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* Return a set representation of map |
73
|
|
|
* |
74
|
|
|
* If a set is passed in, that set will be populated, otherwise |
75
|
|
|
* a default set will be used. |
76
|
|
|
* |
77
|
|
|
* @param SetInterface $set |
78
|
|
|
* @return SetInterface |
79
|
|
|
*/ |
80
|
5 |
|
public function entrySet(SetInterface $set = null): SetInterface |
81
|
|
|
{ |
82
|
5 |
|
$set = $set ?? new HashSet(); |
83
|
5 |
|
foreach ($this->keys as $hashCode => $key) { |
84
|
5 |
|
$set->add(new MapEntry($key, $this->values[$hashCode])); |
85
|
|
|
} |
86
|
|
|
|
87
|
5 |
|
return $set; |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* Get the value at the specified key |
92
|
|
|
* |
93
|
|
|
* @param mixed $key |
94
|
|
|
* @return mixed |
95
|
|
|
* @throws \OutOfRangeException if the key doesn't exist |
96
|
|
|
*/ |
97
|
4 |
|
public function get($key) |
98
|
|
|
{ |
99
|
4 |
|
$hashedKey = $this->hashCode($key); |
100
|
4 |
|
if (!$this->containsKey($key)) { |
101
|
1 |
|
throw new OutOfRangeException(sprintf('Tried to access array at key "%s"', $hashedKey)); |
102
|
|
|
} |
103
|
|
|
|
104
|
3 |
|
return $this->values[$hashedKey]; |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* Returns true if the map is empty |
109
|
|
|
* |
110
|
|
|
* @return bool |
111
|
|
|
*/ |
112
|
2 |
|
public function isEmpty(): bool |
113
|
|
|
{ |
114
|
2 |
|
return 0 === $this->count(); |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
/** |
118
|
|
|
* Returns a set of they keys in the map |
119
|
|
|
* |
120
|
|
|
* If a set is passed in, that set will be populated, otherwise |
121
|
|
|
* a default set will be used. |
122
|
|
|
* |
123
|
|
|
* @param SetInterface $set |
124
|
|
|
* @return SetInterface |
125
|
|
|
*/ |
126
|
2 |
|
public function keySet(SetInterface $set = null): SetInterface |
127
|
|
|
{ |
128
|
2 |
|
if (null === $set) { |
129
|
1 |
|
return new HashSet(new ArrayList($this->keys)); |
130
|
|
|
} |
131
|
|
|
|
132
|
1 |
|
$set->addAll(new ArrayList($this->keys)); |
133
|
|
|
|
134
|
1 |
|
return $set; |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* Returns the previous value or null if there was no value |
139
|
|
|
* |
140
|
|
|
* @param mixed $key |
141
|
|
|
* @param mixed $value |
142
|
|
|
* @return mixed |
143
|
|
|
*/ |
144
|
23 |
|
public function put($key, $value) |
145
|
|
|
{ |
146
|
23 |
|
$oldValue = null; |
147
|
23 |
|
$hashedKey = $this->hashCode($key); |
148
|
23 |
|
if ($this->containsKey($key)) { |
149
|
1 |
|
$oldValue = $this->get($key); |
150
|
1 |
|
$this->remove($hashedKey); |
151
|
|
|
} |
152
|
|
|
|
153
|
23 |
|
$this->keys[$hashedKey] = $key; |
154
|
23 |
|
$this->values[$hashedKey] = $value; |
155
|
|
|
|
156
|
23 |
|
return $oldValue; |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
/** |
160
|
|
|
* Remove the mapping for the key and returns the previous value |
161
|
|
|
* or null |
162
|
|
|
* |
163
|
|
|
* @param mixed $key |
164
|
|
|
* @return mixed |
165
|
|
|
*/ |
166
|
3 |
|
public function remove($key) |
167
|
|
|
{ |
168
|
3 |
|
$hashedKey = $this->hashCode($key); |
169
|
3 |
|
if (!$this->containsKey($key)) { |
170
|
2 |
|
return false; |
171
|
|
|
} |
172
|
|
|
|
173
|
1 |
|
$oldValue = $this->get($key); |
174
|
1 |
|
unset($this->keys[$hashedKey], $this->values[$hashedKey]); |
175
|
|
|
|
176
|
1 |
|
return $oldValue; |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
/** |
180
|
|
|
* Returns the number of mappings in the map |
181
|
|
|
* |
182
|
|
|
* @return int |
183
|
|
|
*/ |
184
|
6 |
|
public function count(): int |
185
|
|
|
{ |
186
|
6 |
|
return count($this->keys); |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* Returns the keys as a collection |
191
|
|
|
* |
192
|
|
|
* @param CollectionInterface $collection |
193
|
|
|
* @return CollectionInterface |
194
|
|
|
*/ |
195
|
9 |
View Code Duplication |
public function keys(CollectionInterface $collection = null): CollectionInterface |
|
|
|
|
196
|
|
|
{ |
197
|
9 |
|
if (null === $collection) { |
198
|
8 |
|
return new ArrayList($this->keys); |
199
|
|
|
} |
200
|
|
|
|
201
|
1 |
|
$collection->addAll(new ArrayList($this->keys)); |
202
|
|
|
|
203
|
1 |
|
return $collection; |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
/** |
207
|
|
|
* Returns the values as a collection |
208
|
|
|
* |
209
|
|
|
* @param CollectionInterface $collection |
210
|
|
|
* @return CollectionInterface |
211
|
|
|
*/ |
212
|
2 |
View Code Duplication |
public function values(CollectionInterface $collection = null): CollectionInterface |
|
|
|
|
213
|
|
|
{ |
214
|
2 |
|
if (null === $collection) { |
215
|
1 |
|
return new ArrayList($this->values); |
216
|
|
|
} |
217
|
|
|
|
218
|
1 |
|
$collection->addAll(new ArrayList($this->values)); |
219
|
|
|
|
220
|
1 |
|
return $collection; |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
/** |
224
|
|
|
* Filter the collection using closure |
225
|
|
|
* |
226
|
|
|
* The closure will get passed a [@see MapEntry]. Returning true from the |
227
|
|
|
* closure will include that entry in the new map. |
228
|
|
|
* |
229
|
|
|
* @param callable $filter |
230
|
|
|
* @return MapInterface |
231
|
|
|
*/ |
232
|
1 |
|
public function filter(callable $filter): MapInterface |
233
|
|
|
{ |
234
|
1 |
|
$map = new HashMap(); |
235
|
|
|
|
236
|
|
|
/** @var MapEntry $mapEntry */ |
237
|
1 |
|
foreach ($this->entrySet() as $mapEntry) { |
238
|
1 |
|
if (false !== $filter($mapEntry)) { |
239
|
1 |
|
$map->put($mapEntry->key, $mapEntry->value); |
240
|
|
|
} |
241
|
|
|
} |
242
|
|
|
|
243
|
1 |
|
return $map; |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
/** |
247
|
|
|
* Generate a hashcode for a php value |
248
|
|
|
* |
249
|
|
|
* @param mixed $value |
250
|
|
|
* @return string |
251
|
|
|
*/ |
252
|
23 |
|
protected function hashCode($value): string |
253
|
|
|
{ |
254
|
23 |
|
$type = gettype($value); |
255
|
|
|
switch ($type) { |
256
|
23 |
|
case 'object': |
257
|
5 |
|
return spl_object_hash($value); |
258
|
21 |
|
case 'array'; |
|
|
|
|
259
|
1 |
|
return md5(serialize($value)); |
260
|
|
|
default: |
261
|
20 |
|
return $type . md5($value); |
262
|
|
|
} |
263
|
|
|
} |
264
|
|
|
} |
265
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.