1
|
|
|
<?php |
2
|
|
|
namespace Narrowspark\Arr; |
3
|
|
|
|
4
|
|
|
use Narrowspark\Arr\Traits\SplitPathTrait; |
5
|
|
|
use Narrowspark\Arr\Traits\ValueTrait; |
6
|
|
|
|
7
|
|
|
class Transform |
8
|
|
|
{ |
9
|
|
|
/** |
10
|
|
|
* Dotted array cache. |
11
|
|
|
* |
12
|
|
|
* @var array |
13
|
|
|
*/ |
14
|
|
|
protected $dotted = []; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* A instance of Access |
18
|
|
|
* |
19
|
|
|
* @var \Narrowspark\Arr\Access |
20
|
|
|
*/ |
21
|
|
|
protected $access; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* A instance of Access |
25
|
|
|
* |
26
|
|
|
* @var \Narrowspark\Arr\Enumerator |
27
|
|
|
*/ |
28
|
|
|
protected $enumerator; |
29
|
|
|
|
30
|
|
|
public function __construct() |
31
|
|
|
{ |
32
|
|
|
$this->access = new Access(); |
33
|
|
|
$this->enumerator = new Enumerator(); |
34
|
|
|
} |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* Swap two elements between positions. |
38
|
|
|
* |
39
|
|
|
* @param array $array array to swap |
40
|
|
|
* @param string $swapA |
41
|
|
|
* @param string $swapB |
42
|
|
|
* |
43
|
|
|
* @return array|null |
44
|
|
|
*/ |
45
|
|
|
public function swap(array $array, $swapA, $swapB) |
46
|
|
|
{ |
47
|
|
|
list($array[$swapA], $array[$swapB]) = [$array[$swapB], $array[$swapA]]; |
48
|
|
|
|
49
|
|
|
return $array; |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* Create a new array consisting of every n-th element. |
54
|
|
|
* |
55
|
|
|
* @param array $array |
56
|
|
|
* @param int $step |
57
|
|
|
* @param int $offset |
58
|
|
|
* |
59
|
|
|
* @return array |
60
|
|
|
*/ |
61
|
|
|
public static function every($array, $step, $offset = 0) |
62
|
|
|
{ |
63
|
|
|
$new = []; |
64
|
|
|
|
65
|
|
|
$position = 0; |
66
|
|
|
|
67
|
|
|
foreach ($array as $key => $item) { |
68
|
|
|
if ($position % $step === $offset) { |
69
|
|
|
$new[] = $item; |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
$position++; |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
return $new; |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Indexes an array depending on the values it contains. |
80
|
|
|
* |
81
|
|
|
* @param array $array |
82
|
|
|
* @param callable $callback Function to combine values. |
83
|
|
|
* @param bool $overwrite Should duplicate keys be overwritten? |
84
|
|
|
* |
85
|
|
|
* @return array Indexed values. |
86
|
|
|
*/ |
87
|
|
|
public function combine(array $array, callable $callback, $overwrite = true) |
88
|
|
|
{ |
89
|
|
|
$combined = []; |
90
|
|
|
|
91
|
|
|
foreach ($array as $key => $value) { |
92
|
|
|
$combinator = call_user_func($callback, $value, $key); |
93
|
|
|
$index = $combinator->key(); |
94
|
|
|
|
95
|
|
|
if ($overwrite || !isset($combined[$index])) { |
96
|
|
|
$combined[$index] = $combinator->current(); |
97
|
|
|
} |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
return $combined; |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* Collapse an array of arrays into a single array. |
105
|
|
|
* |
106
|
|
|
* @param array $array |
107
|
|
|
* |
108
|
|
|
* @return array |
109
|
|
|
*/ |
110
|
|
|
public function collapse(array $array) |
111
|
|
|
{ |
112
|
|
|
$results = []; |
113
|
|
|
|
114
|
|
|
foreach ($array as $values) { |
115
|
|
|
if (!is_array($values)) { |
116
|
|
|
continue; |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
$results = array_merge($results, $values); |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
return $results; |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
/** |
126
|
|
|
* Divide an array into two arrays. One with keys and the other with values. |
127
|
|
|
* |
128
|
|
|
* @param array $array |
129
|
|
|
* |
130
|
|
|
* @return array[] |
131
|
|
|
*/ |
132
|
|
|
public function divide($array) |
133
|
|
|
{ |
134
|
|
|
return [array_keys($array), array_values($array)]; |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* Reindexes a list of values. |
139
|
|
|
* |
140
|
|
|
* @param array $array |
141
|
|
|
* @param array $map An map of correspondances of the form |
142
|
|
|
* ['currentIndex' => 'newIndex']. |
143
|
|
|
* @param bool $keepUnmapped Whether or not to keep keys that are not |
144
|
|
|
* remapped. |
145
|
|
|
* |
146
|
|
|
* @return array |
147
|
|
|
*/ |
148
|
|
|
public function reindex(array $array, array $map, $keepUnmapped = true) |
149
|
|
|
{ |
150
|
|
|
$reindexed = $keepUnmapped |
151
|
|
|
? $array |
152
|
|
|
: []; |
153
|
|
|
|
154
|
|
|
foreach ($map as $from => $to) { |
155
|
|
|
if (isset($array[$from])) { |
156
|
|
|
$reindexed[$to] = $array[$from]; |
157
|
|
|
} |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
return $reindexed; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* Merges two arrays recursively. |
165
|
|
|
* |
166
|
|
|
* @param array $first Original data. |
167
|
|
|
* @param array $second Data to be merged. |
168
|
|
|
* |
169
|
|
|
* @return array |
170
|
|
|
*/ |
171
|
|
|
public function merge(array $first, array $second) |
172
|
|
|
{ |
173
|
|
|
foreach ($second as $key => $value) { |
174
|
|
|
$shouldBeMerged = ( |
175
|
|
|
isset($first[$key]) |
176
|
|
|
&& is_array($first[$key]) |
177
|
|
|
&& is_array($value) |
178
|
|
|
); |
179
|
|
|
|
180
|
|
|
$first[$key] = $shouldBeMerged |
181
|
|
|
? $this->merge($first[$key], $value) |
182
|
|
|
: $value; |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
return $first; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* Extend one array with another. |
190
|
|
|
* |
191
|
|
|
* @param array $arrays |
192
|
|
|
* |
193
|
|
|
* @return array |
194
|
|
|
*/ |
195
|
|
|
public function extend(array $arrays) |
196
|
|
|
{ |
197
|
|
|
$merged = []; |
198
|
|
|
|
199
|
|
|
foreach (func_get_args() as $array) { |
200
|
|
|
foreach ($array as $key => $value) { |
201
|
|
|
if (is_array($value) && $this->access->has($merged, $key) && is_array($merged[$key])) { |
202
|
|
|
$merged[$key] = $this->extend($merged[$key], $value); |
203
|
|
|
} else { |
204
|
|
|
$merged[$key] = $value; |
205
|
|
|
} |
206
|
|
|
} |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
return $merged; |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
/** |
213
|
|
|
* Transforms a 1-dimensional array into a multi-dimensional one, |
214
|
|
|
* exploding keys according to a separator. |
215
|
|
|
* |
216
|
|
|
* @param array $array |
217
|
|
|
* |
218
|
|
|
* @return array |
219
|
|
|
*/ |
220
|
|
|
public function asHierarchy(array $array) |
221
|
|
|
{ |
222
|
|
|
$hierarchy = []; |
223
|
|
|
|
224
|
|
|
foreach ($array as $key => $value) { |
225
|
|
|
$segments = explode('.', $key); |
226
|
|
|
$valueSegment = array_pop($segments); |
227
|
|
|
$branch = &$hierarchy; |
228
|
|
|
|
229
|
|
|
foreach ($segments as $segment) { |
230
|
|
|
if (!isset($branch[$segment])) { |
231
|
|
|
$branch[$segment] = []; |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
$branch = &$branch[$segment]; |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
$branch[$valueSegment] = $value; |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
return $hierarchy; |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
/** |
244
|
|
|
* Separates elements from an array into groups. |
245
|
|
|
* The function maps an element to the key that will be used for grouping. |
246
|
|
|
* If no function is passed, the element itself will be used as key. |
247
|
|
|
* |
248
|
|
|
* @param array $array |
249
|
|
|
* @param callable|null $callback |
250
|
|
|
* |
251
|
|
|
* @return array |
252
|
|
|
*/ |
253
|
|
|
public function groupBy(array $array, callable $callback = null) |
254
|
|
|
{ |
255
|
|
|
$callback = $callback ?: function ($value) { |
256
|
|
|
return $value; |
257
|
|
|
}; |
258
|
|
|
|
259
|
|
|
return array_reduce( |
260
|
|
|
$array, |
261
|
|
|
function ($buckets, $value) use ($callback) { |
262
|
|
|
$key = call_user_func($callback, $value); |
263
|
|
|
|
264
|
|
|
if (!array_key_exists($key, $buckets)) { |
265
|
|
|
$buckets[$key] = []; |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
$buckets[$key][] = $value; |
269
|
|
|
|
270
|
|
|
return $buckets; |
271
|
|
|
}, |
272
|
|
|
[] |
273
|
|
|
); |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
/** |
277
|
|
|
* Flatten a multi-dimensional associative array with dots. |
278
|
|
|
* |
279
|
|
|
* @param array $array |
280
|
|
|
* @param string $prepend |
281
|
|
|
* |
282
|
|
|
* @return array |
283
|
|
|
*/ |
284
|
|
|
public function dot($array, $prepend = '') |
285
|
|
|
{ |
286
|
|
|
$cache = serialize(['array' => $array, 'prepend' => $prepend]); |
287
|
|
|
|
288
|
|
|
if (array_key_exists($cache, $this->dotted)) { |
289
|
|
|
return $this->dotted[$cache]; |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
$results = []; |
293
|
|
|
|
294
|
|
View Code Duplication |
foreach ($array as $key => $value) { |
|
|
|
|
295
|
|
|
if (is_array($value)) { |
296
|
|
|
$results = array_merge($results, $this->dot($value, $prepend . $key . '.')); |
297
|
|
|
} else { |
298
|
|
|
$results[$prepend . $key] = $value; |
299
|
|
|
} |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
return $this->dotted[$cache] = $results; |
303
|
|
|
} |
304
|
|
|
|
305
|
|
|
/** |
306
|
|
|
* Flatten a nested array to a separated key. |
307
|
|
|
* |
308
|
|
|
* @param array $array |
309
|
|
|
* @param string|null $separator |
310
|
|
|
* @param string $prepend |
311
|
|
|
* |
312
|
|
|
* @return array |
313
|
|
|
*/ |
314
|
|
|
public function flatten(array $array, $separator = null, $prepend = '') |
315
|
|
|
{ |
316
|
|
|
$flattened = []; |
317
|
|
|
|
318
|
|
View Code Duplication |
foreach ($array as $key => $value) { |
|
|
|
|
319
|
|
|
if (is_array($value)) { |
320
|
|
|
$flattened = array_merge($flattened, $this->flatten($value, $separator, $prepend . $key . $separator)); |
321
|
|
|
} else { |
322
|
|
|
$flattened[$prepend . $key] = $value; |
323
|
|
|
} |
324
|
|
|
} |
325
|
|
|
|
326
|
|
|
return $flattened; |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
/** |
330
|
|
|
* Expand a flattened array with dots to a multi-dimensional associative array. |
331
|
|
|
* |
332
|
|
|
* @param array $array |
333
|
|
|
* @param string $prepend |
334
|
|
|
* |
335
|
|
|
* @return array |
336
|
|
|
*/ |
337
|
|
|
public function expand(array $array, $prepend = '') |
338
|
|
|
{ |
339
|
|
|
$results = []; |
340
|
|
|
|
341
|
|
|
if ($prepend) { |
342
|
|
|
$prepend .= '.'; |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
foreach ($array as $key => $value) { |
346
|
|
|
if ($prepend) { |
347
|
|
|
$pos = strpos($key, $prepend); |
348
|
|
|
|
349
|
|
|
if ($pos === 0) { |
350
|
|
|
$key = substr($key, strlen($prepend)); |
351
|
|
|
} |
352
|
|
|
} |
353
|
|
|
|
354
|
|
|
$results = $this->access->set($results, $key, $value); |
355
|
|
|
} |
356
|
|
|
|
357
|
|
|
return $results; |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
/** |
361
|
|
|
* Reset all numerical indexes of an array (start from zero). |
362
|
|
|
* Non-numerical indexes will stay untouched. Returns a new array. |
363
|
|
|
* |
364
|
|
|
* @param array $array |
365
|
|
|
* @param bool|false $deep |
366
|
|
|
* |
367
|
|
|
* @return array |
368
|
|
|
*/ |
369
|
|
|
public function reset(array $array, $deep = false) |
370
|
|
|
{ |
371
|
|
|
$target = []; |
372
|
|
|
|
373
|
|
|
foreach ($array as $key => $value) { |
374
|
|
|
if ($deep && is_array($value)) { |
375
|
|
|
$value = $this->reset($value); |
376
|
|
|
} |
377
|
|
|
|
378
|
|
|
if (is_numeric($key)) { |
379
|
|
|
$target[] = $value; |
380
|
|
|
} else { |
381
|
|
|
$target[$key] = $value; |
382
|
|
|
} |
383
|
|
|
} |
384
|
|
|
|
385
|
|
|
return $target; |
386
|
|
|
} |
387
|
|
|
|
388
|
|
|
/** |
389
|
|
|
* Extend one array with another. Non associative arrays will not be merged |
390
|
|
|
* but rather replaced. |
391
|
|
|
* |
392
|
|
|
* @param array $arrays |
393
|
|
|
* |
394
|
|
|
* @return array |
395
|
|
|
*/ |
396
|
|
|
public function extendDistinct(array $arrays) |
|
|
|
|
397
|
|
|
{ |
398
|
|
|
$merged = []; |
399
|
|
|
|
400
|
|
|
foreach (func_get_args() as $array) { |
401
|
|
|
foreach ($array as $key => $value) { |
402
|
|
|
if (is_array($value) && $this->access->has($merged, $key) && is_array($merged[$key])) { |
403
|
|
|
if ($this->enumerator->isAssoc($value) && $this->enumerator->isAssoc($merged[$key])) { |
404
|
|
|
$merged[$key] = $this->extendDistinct($merged[$key], $value); |
405
|
|
|
|
406
|
|
|
continue; |
407
|
|
|
} |
408
|
|
|
} |
409
|
|
|
|
410
|
|
|
$merged[$key] = $value; |
411
|
|
|
} |
412
|
|
|
} |
413
|
|
|
|
414
|
|
|
return $merged; |
415
|
|
|
} |
416
|
|
|
|
417
|
|
|
/** |
418
|
|
|
* Recursively sort an array by keys and values. |
419
|
|
|
* |
420
|
|
|
* @param array $array |
421
|
|
|
* |
422
|
|
|
* @return array |
423
|
|
|
*/ |
424
|
|
|
public function sortRecursive(array $array) |
425
|
|
|
{ |
426
|
|
|
foreach ($array as &$value) { |
427
|
|
|
if (is_array($value)) { |
428
|
|
|
$value = $this->sortRecursive($value); |
429
|
|
|
} |
430
|
|
|
} |
431
|
|
|
|
432
|
|
|
// sort associative array |
433
|
|
|
if ($this->enumerator->isAssoc($array)) { |
434
|
|
|
ksort($array); |
435
|
|
|
// sort regular array |
436
|
|
|
} else { |
437
|
|
|
sort($array); |
438
|
|
|
} |
439
|
|
|
|
440
|
|
|
return $array; |
441
|
|
|
} |
442
|
|
|
|
443
|
|
|
public function zip(array $array, array $arrays) |
|
|
|
|
444
|
|
|
{ |
445
|
|
|
$args = func_get_args(); |
446
|
|
|
array_shift($args); |
447
|
|
|
|
448
|
|
|
foreach ($array as $key => $value) { |
449
|
|
|
$array[$key] = array($value); |
450
|
|
|
|
451
|
|
|
foreach ($args as $k => $v) { |
452
|
|
|
$array[$key][] = current($args[$k]); |
453
|
|
|
|
454
|
|
|
if (next($args[$k]) === false && $args[$k] !== array(null)) { |
455
|
|
|
$args[$k] = array(null); |
456
|
|
|
} |
457
|
|
|
} |
458
|
|
|
} |
459
|
|
|
|
460
|
|
|
return $array; |
461
|
|
|
} |
462
|
|
|
} |
463
|
|
|
|
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.