Passed
Push — master ( 2be3f6...c25ba4 )
by Pauli
03:39
created

ArrayUtil::find()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 4
c 1
b 0
f 0
nc 3
nop 2
dl 0
loc 7
rs 10
1
<?php declare(strict_types=1);
2
3
/**
4
 * ownCloud - Music app
5
 *
6
 * This file is licensed under the Affero General Public License version 3 or
7
 * later. See the COPYING file.
8
 *
9
 * @author Pauli Järvinen <[email protected]>
10
 * @copyright Pauli Järvinen 2025
11
 */
12
13
namespace OCA\Music\Utility;
14
15
/**
16
 * Static utility functions to work with arrays
17
 */
18
class ArrayUtil {
19
20
	/**
21
	 * Extract ID of each array element by calling getId and return
22
	 * the IDs as an array
23
	 * @param mixed[] $arr
24
	 * @return int[]
25
	 */
26
	public static function extractIds(array $arr) : array {
27
		return \array_map(fn($i) => $i->getId(), $arr);
28
	}
29
30
	/**
31
	 * Extract User ID of each array element by calling getUserId and return
32
	 * the IDs as an array
33
	 * @param mixed[] $arr
34
	 * @return string[]
35
	 */
36
	public static function extractUserIds(array $arr) : array {
37
		return \array_map(fn($i) => $i->getUserId(), $arr);
38
	}
39
40
	/**
41
	 * Create a look-up table from given array of items which have a `getId` function.
42
	 * @param mixed[] $array
43
	 * @return mixed[] where keys are the values returned by `getId` of each item
44
	 */
45
	public static function createIdLookupTable(array $array) : array {
46
		$lut = [];
47
		foreach ($array as $item) {
48
			$lut[$item->getId()] = $item;
49
		}
50
		return $lut;
51
	}
52
53
	/**
54
	 * Create a look-up table from given array so that keys of the table are obtained by calling
55
	 * the given method on each array entry and the values are arrays of entries having the same
56
	 * value returned by that method.
57
	 * @param mixed[] $array
58
	 * @param string $getKeyMethod Name of a method found on $array entries which returns a string or an int
59
	 * @return array<int|string, mixed[]>
60
	 */
61
	public static function groupBy(array $array, string $getKeyMethod) : array {
62
		$lut = [];
63
		foreach ($array as $item) {
64
			$lut[$item->$getKeyMethod()][] = $item;
65
		}
66
		return $lut;
67
	}
68
69
	/**
70
	 * Get difference of two arrays, i.e. elements belonging to $b but not $a.
71
	 * This function is faster than the built-in array_diff for large arrays but
72
	 * at the expense of higher RAM usage and can be used only for arrays of
73
	 * integers or strings.
74
	 * From https://stackoverflow.com/a/8827033
75
	 * @param array<int|string> $a
76
	 * @param array<int|string> $b
77
	 * @return array<int|string>
78
	 */
79
	public static function diff(array $b, array $a) : array {
80
		$at = \array_flip($a);
81
		$d = [];
82
		foreach ($b as $i) {
83
			if (!isset($at[$i])) {
84
				$d[] = $i;
85
			}
86
		}
87
		return $d;
88
	}
89
90
	/**
91
	 * Get unique items from an array of integers and/or strings. This is more performant than the built-in
92
	 * \array_unique but doesn't work on just any kind of array.
93
	 * @param array<int|string> $array
94
	 * @return array<int|string>
95
	 */
96
	public static function unique(array $array) : array {
97
		$unique = [];
98
		foreach ($array as $val) {
99
			$unique[$val] = true;
100
		}
101
		return \array_keys($unique);
102
	}
103
104
	/**
105
	 * Get a value matching a key from a dictionary, comparing the key in case-insensitive manner.
106
	 * @param array<string, mixed> $dictionary
107
	 * @return ?mixed Value matching the key or null if not found
108
	 */
109
	public static function getCaseInsensitive(array $dictionary, string $key) {
110
		return self::find($dictionary, fn($_, $k) => StringUtil::caselessEqual((string)$k, $key));
111
	}
112
113
	/**
114
	 * Replacement for \array_find which is available only in PHP 8.4 and later
115
	 *
116
	 * @template K Array key
117
	 * @template V Array value
118
	 * @phpstan-param array<K,V> $array
119
	 * @phpstan-param callable(V,K):bool $predicate
120
	 * @phpstan-return ?V
121
	 */
122
	public static function find(array $array, callable $predicate) {
123
		foreach ($array as $k => $v) {
124
			if ($predicate($v, $k)) {
125
				return $v;
126
			}
127
		}
128
		return null;
129
	}
130
131
	/**
132
	 * Get multiple items from @a $array, as indicated by a second array @a $keys.
133
	 * If @a $preserveKeys is given as true, the result will have the original keys, otherwise
134
	 * the result is re-indexed with keys 0, 1, 2, ...
135
	 * @param array<int|string, mixed> $array
136
	 * @param array<int|string> $keys
137
	 * @return array<int|string, mixed>
138
	 */
139
	public static function multiGet(array $array, array $keys, bool $preserveKeys=false) : array {
140
		$result = [];
141
		foreach ($keys as $key) {
142
			if ($preserveKeys) {
143
				$result[$key] = $array[$key];
144
			} else {
145
				$result[] = $array[$key];
146
			}
147
		}
148
		return $result;
149
	}
150
151
	/**
152
	 * Get multiple columns from the multidimensional @a $array. This is similar to the built-in
153
	 * function \array_column except that this can return multiple columns and not just one.
154
	 * @param array<array<int|string, mixed>> $array
155
	 * @param array<int|string> $columns
156
	 * @param int|string|null $indexColumn
157
	 * @return array<array<int|string, mixed>>
158
	 */
159
	public static function columns(array $array, array $columns, $indexColumn=null) : array {
160
		if ($indexColumn !== null) {
161
			$array = \array_column($array, null, $indexColumn);
162
		}
163
164
		return \array_map(fn($row) => self::multiGet($row, $columns, true), $array);
165
	}
166
167
	/**
168
	 * Like the built-in function \array_filter but this one works recursively on nested arrays.
169
	 * Another difference is that this function always requires an explicit callback condition.
170
	 * Both inner nodes and leafs nodes are passed to the $condition.
171
	 * @param mixed[] $array
172
	 * @param callable(mixed):bool $condition
173
	 * @return mixed[]
174
	 */
175
	public static function filterRecursive(array $array, callable $condition) : array {
176
		$result = [];
177
178
		foreach ($array as $key => $value) {
179
			if ($condition($value)) {
180
				if (\is_array($value)) {
181
					$result[$key] = self::filterRecursive($value, $condition);
182
				} else {
183
					$result[$key] = $value;
184
				}
185
			}
186
		}
187
188
		return $result;
189
	}
190
191
	/**
192
	 * Inverse operation of self::filterRecursive, keeping only those items where
193
	 * the $condition evaluates to *false*.
194
	 * @param mixed[] $array
195
	 * @param callable(mixed):bool $condition
196
	 * @return mixed[]
197
	 */
198
	public static function rejectRecursive(array $array, callable $condition) : array {
199
		$invCond = fn($item) => !$condition($item);
200
		return self::filterRecursive($array, $invCond);
201
	}
202
203
	/**
204
	 * Convert the given array $arr so that keys of the potentially multi-dimensional array
205
	 * are converted using the mapping given in $dictionary. Keys not found from $dictionary
206
	 * are not altered.
207
	 * @param array<int|string, mixed> $arr
208
	 * @param array<int|string, int|string> $dictionary
209
	 * @return array<int|string, mixed>
210
	 */
211
	public static function convertKeys(array $arr, array $dictionary) : array {
212
		$newArr = [];
213
214
		foreach ($arr as $k => $v) {
215
			$key = $dictionary[$k] ?? $k;
216
			$newArr[$key] = \is_array($v) ? self::convertKeys($v, $dictionary) : $v;
217
		}
218
219
		return $newArr;
220
	}
221
222
	/**
223
	 * Walk through the given, potentially multi-dimensional, array and cast all leaf nodes
224
	 * to integer type. The array is modified in-place. Optionally, apply the conversion only
225
	 * on the leaf nodes matching the given predicate.
226
	 * @param mixed[] $arr Input/output array to operate on
227
	 * @param ?callable(mixed):bool $predicate
228
	 */
229
	public static function intCastValues(array &$arr, ?callable $predicate=null) : void {
230
		\array_walk_recursive($arr, function(&$value) use($predicate) {
231
			if ($predicate === null || $predicate($value)) {
232
				$value = (int)$value;
233
			}
234
		});
235
	}
236
237
	/**
238
	 * Given a two-dimensional array, sort the outer dimension according to values in the
239
	 * specified column of the inner dimension.
240
	 * @param array<array<string, mixed>> $arr Input/output array to operate on
241
	 */
242
	public static function sortByColumn(array &$arr, string $column) : void {
243
		\usort($arr, fn($a, $b) => StringUtil::caselessCompare($a[$column], $b[$column]));
244
	}
245
246
}