Passed
Push — 1.x ( 32e822...b940fd )
by Ulises Jeremias
02:43
created

Map::__debugInfo()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php namespace Mbh\Collection;
2
3
/**
4
 * MBHFramework
5
 *
6
 * @link      https://github.com/MBHFramework/mbh-framework
7
 * @copyright Copyright (c) 2017 Ulises Jeremias Cornejo Fandos
8
 * @license   https://github.com/MBHFramework/mbh-framework/blob/master/LICENSE (MIT License)
9
 */
10
11
use Mbh\Collection\Interfaces\Collection as CollectionInterface;
12
use Mbh\Collection\Interfaces\Hashable as HashableInterface;
13
use Mbh\Collection\Interfaces\Sequenceable as SequenceableInterface;
14
use Traversable;
15
use ArrayAccess;
16
use IteratorAggregate;
17
use OutOfBoundsException;
18
use OutOfRangeException;
19
use UnderflowException;
20
21
/**
22
 * A Map is a sequential collection of key-value pairs, almost identical to an
23
 * array used in a similar context. Keys can be any type, but must be unique.
24
 *
25
 * @package structures
26
 * @author Ulises Jeremias Cornejo Fandos <[email protected]>
27
 */
28
class Map implements ArrayAccess, CollectionInterface, IteratorAggregate
29
{
30
    use Traits\Collection;
31
    use Traits\Functional;
32
    use Traits\SquaredCapacity;
33
34
    const MIN_CAPACITY = 8.0;
35
36
    /**
37
     * @var FixedArray internal array to store pairs
38
     */
39
    private $pairs;
40
41
    /**
42
     * Creates a new instance.
43
     *
44
     * @param array|Traversable $pairs
45
     */
46
    public function __construct($pairs = [])
47
    {
48
        FixedArray::fromArray([]);
49
50
        $this->putAll($pairs);
51
    }
52
53
    /**
54
     * @inheritDoc
55
     */
56
    public function clear()
57
    {
58
        $this->pairs->clear();
59
        $this->capacity = self::MIN_CAPACITY;
60
    }
61
62
    /**
63
     * @inheritDoc
64
     */
65
    public function count(): int
66
    {
67
        return count($this->pairs);
68
    }
69
70
    /**
71
     * Completely removes a pair from the internal array by position. It is
72
     * important to remove it from the array and not just use 'unset'.
73
     */
74
    private function delete(int $position)
75
    {
76
        $pair  = $this->pairs->remove($position);
77
78
        $this->checkCapacity();
79
        return $pair->value;
80
    }
81
82
    /**
83
     * Return the first Pair from the Map
84
     *
85
     * @return Pair
86
     *
87
     * @throws UnderflowException
88
     */
89
    public function first(): Pair
90
    {
91
        if ($this->isEmpty()) {
92
            throw new UnderflowException();
93
        }
94
95
        return $this->pairs->first();
96
    }
97
98
    /**
99
     * Returns the value associated with a key, or an optional default if the
100
     * key is not associated with a value.
101
     *
102
     * @param mixed $key
103
     * @param mixed $default
104
     *
105
     * @return mixed The associated value or fallback default if provided.
106
     *
107
     * @throws OutOfBoundsException if no default was provided and the key is
108
     *                               not associated with a value.
109
     */
110
    public function get($key, $default = null)
111
    {
112
        if (($pair = $this->lookupKey($key))) {
113
            return $pair->value;
114
        }
115
116
        // Check if a default was provided.
117
        if (func_num_args() === 1) {
118
            throw new OutOfBoundsException();
119
        }
120
121
        return $default;
122
    }
123
124
    /**
125
     * Returns whether an association a given key exists.
126
     *
127
     * @param mixed $key
128
     *
129
     * @return bool
130
     */
131
    public function hasKey($key): bool
132
    {
133
        return $this->lookupKey($key) !== null;
134
    }
135
136
    /**
137
     * Returns whether an association for a given value exists.
138
     *
139
     * @param mixed $value
140
     *
141
     * @return bool
142
     */
143
    public function hasValue($value): bool
144
    {
145
        return $this->lookupValue($value) !== null;
146
    }
147
148
    /**
149
     * Returns a set of all the keys in the map.
150
     *
151
     * @return Set
152
     */
153
    public function keys(): Set
154
    {
155
        return new Set($this->pairs->map(function($pair) {
156
            return $pair->key;
157
        }));
158
    }
159
160
    /**
161
     * Determines whether two keys are equal.
162
     *
163
     * @param mixed $a
164
     * @param mixed $b
165
     *
166
     * @return bool
167
     */
168
    private function keysAreEqual($a, $b): bool
169
    {
170
        if (is_object($a) && $a instanceof HashableInterface) {
171
            return get_class($a) === get_class($b) && $a->equals($b);
172
        }
173
174
        return $a === $b;
175
    }
176
177
    /**
178
     * Return the last Pair from the Map
179
     *
180
     * @return Pair
181
     *
182
     * @throws UnderflowException
183
     */
184
    public function last(): Pair
185
    {
186
        if ($this->isEmpty()) {
187
            throw new UnderflowException();
188
        }
189
190
        return $this->pairs->last();
191
    }
192
193
194
    /**
195
     * Attempts to look up a key in the table.
196
     *
197
     * @param $key
198
     *
199
     * @return Pair|null
200
     */
201
    private function lookupKey($key)
202
    {
203
        foreach ($this->pairs as $pair) {
204
            if ($this->keysAreEqual($pair->key, $key)) {
205
                return $pair;
206
            }
207
        }
208
    }
209
210
    /**
211
     * Attempts to look up a key in the table.
212
     *
213
     * @param $value
214
     *
215
     * @return Pair|null
216
     */
217
    private function lookupValue($value)
218
    {
219
        foreach ($this->pairs as $pair) {
220
            if ($pair->value === $value) {
221
                return $pair;
222
            }
223
        }
224
    }
225
226
    /**
227
     * Returns a sequence of pairs representing all associations.
228
     *
229
     * @return SequenceableInterface
230
     */
231
    public function pairs(): SequenceableInterface
232
    {
233
        return $this->pairs->map(function($pair) {
234
            return $pair->copy();
235
        });
236
    }
237
238
    /**
239
     * Associates a key with a value, replacing a previous association if there
240
     * was one.
241
     *
242
     * @param mixed $key
243
     * @param mixed $value
244
     */
245
    public function put($key, $value)
246
    {
247
        $pair = $this->lookupKey($key);
248
        if ($pair) {
249
            $pair->value = $value;
250
        } else {
251
            $this->checkCapacity();
252
            $this->pairs[] = new Pair($key, $value);
253
        }
254
    }
255
256
    /**
257
     * Creates associations for all keys and corresponding values of either an
258
     * array or iterable object.
259
     *
260
     * @param Traversable|array $values
261
     */
262
    public function putAll($values)
263
    {
264
        foreach ($values as $key => $value) {
265
            $this->put($key, $value);
266
        }
267
    }
268
269
    /**
270
     * Removes a key's association from the map and returns the associated value
271
     * or a provided default if provided.
272
     *
273
     * @param mixed $key
274
     * @param mixed $default
275
     *
276
     * @return mixed The associated value or fallback default if provided.
277
     *
278
     * @throws OutOfBoundsException if no default was provided and the key is
279
     *                               not associated with a value.
280
     */
281
    public function remove($key, $default = null)
282
    {
283
        foreach ($this->pairs as $position => $pair) {
284
            if ($this->keysAreEqual($pair->key, $key)) {
285
                return $this->delete($position);
286
            }
287
        }
288
289
        // Check if a default was provided
290
        if (func_num_args() === 1) {
291
            throw new OutOfBoundsException();
292
        }
293
294
        return $default;
295
    }
296
297
    /**
298
     * Return the pair at a specified position in the Map
299
     *
300
     * @param int $position
301
     *
302
     * @return Pair
303
     *
304
     * @throws OutOfRangeException
305
     */
306
    public function skip(int $position): Pair
307
    {
308
        if ($position < 0 || $position >= count($this->pairs)) {
309
            throw new OutOfRangeException();
310
        }
311
312
        return $this->pairs[$position]->copy();
313
    }
314
315
    /**
316
     * @inheritDoc
317
     */
318
    public function toArray(): array
319
    {
320
        $array = [];
321
        foreach ($this->pairs as $pair) {
322
            $array[$pair->key] = $pair->value;
323
        }
324
325
        return $array;
326
    }
327
328
    /**
329
     * Returns a sequence of all the associated values in the Map.
330
     *
331
     * @return SequenceableInterface
332
     */
333
    public function values(): SequenceableInterface
334
    {
335
        return $this->pairs->map(function($pair) {
336
            return $pair->value;
337
        });
338
    }
339
340
    /**
341
     * @inheritDoc
342
     */
343
    public function getIterator()
344
    {
345
        foreach ($this->pairs as $pair) {
346
            yield $pair->key => $pair->value;
347
        }
348
    }
349
350
    /**
351
     * Returns a representation to be used for var_dump and print_r.
352
     */
353
    public function __debugInfo()
354
    {
355
        return $this->pairs()->toArray();
356
    }
357
358
    /**
359
     * @inheritdoc
360
     */
361
    public function offsetSet($offset, $value)
362
    {
363
        $this->put($offset, $value);
364
    }
365
366
    /**
367
     * @inheritdoc
368
     *
369
     * @throws OutOfBoundsException
370
     */
371
    public function &offsetGet($offset)
372
    {
373
        $pair = $this->lookupKey($offset);
374
        if ($pair) {
375
            return $pair->value;
376
        }
377
        throw new OutOfBoundsException();
378
    }
379
380
    /**
381
     * @inheritdoc
382
     */
383
    public function offsetUnset($offset)
384
    {
385
        $this->remove($offset, null);
386
    }
387
388
    /**
389
     * @inheritdoc
390
     */
391
    public function offsetExists($offset)
392
    {
393
        return $this->get($offset, null) !== null;
394
    }
395
}
396