Passed
Push — master ( ffd534...2be3f6 )
by Pauli
03:35
created

ArrayUtil::unique()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 6
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
		foreach ($dictionary as $k => $v) {
111
			if (StringUtil::caselessEqual((string)$k, $key)) {
112
				return $v;
113
			}
114
		}
115
		return null;
116
	}
117
118
	/**
119
	 * Get multiple items from @a $array, as indicated by a second array @a $keys.
120
	 * If @a $preserveKeys is given as true, the result will have the original keys, otherwise
121
	 * the result is re-indexed with keys 0, 1, 2, ...
122
	 * @param array<int|string, mixed> $array
123
	 * @param array<int|string> $keys
124
	 * @return array<int|string, mixed>
125
	 */
126
	public static function multiGet(array $array, array $keys, bool $preserveKeys=false) : array {
127
		$result = [];
128
		foreach ($keys as $key) {
129
			if ($preserveKeys) {
130
				$result[$key] = $array[$key];
131
			} else {
132
				$result[] = $array[$key];
133
			}
134
		}
135
		return $result;
136
	}
137
138
	/**
139
	 * Get multiple columns from the multidimensional @a $array. This is similar to the built-in
140
	 * function \array_column except that this can return multiple columns and not just one.
141
	 * @param array<array<int|string, mixed>> $array
142
	 * @param array<int|string> $columns
143
	 * @param int|string|null $indexColumn
144
	 * @return array<array<int|string, mixed>>
145
	 */
146
	public static function columns(array $array, array $columns, $indexColumn=null) : array {
147
		if ($indexColumn !== null) {
148
			$array = \array_column($array, null, $indexColumn);
149
		}
150
151
		return \array_map(fn($row) => self::multiGet($row, $columns, true), $array);
152
	}
153
154
	/**
155
	 * Like the built-in function \array_filter but this one works recursively on nested arrays.
156
	 * Another difference is that this function always requires an explicit callback condition.
157
	 * Both inner nodes and leafs nodes are passed to the $condition.
158
	 * @param mixed[] $array
159
	 * @return mixed[]
160
	 */
161
	public static function filterRecursive(array $array, callable $condition) : array {
162
		$result = [];
163
164
		foreach ($array as $key => $value) {
165
			if ($condition($value)) {
166
				if (\is_array($value)) {
167
					$result[$key] = self::filterRecursive($value, $condition);
168
				} else {
169
					$result[$key] = $value;
170
				}
171
			}
172
		}
173
174
		return $result;
175
	}
176
177
	/**
178
	 * Inverse operation of self::filterRecursive, keeping only those items where
179
	 * the $condition evaluates to *false*.
180
	 * @param mixed[] $array
181
	 * @return mixed[]
182
	 */
183
	public static function rejectRecursive(array $array, callable $condition) : array {
184
		$invCond = fn($item) => !$condition($item);
185
		return self::filterRecursive($array, $invCond);
186
	}
187
188
	/**
189
	 * Convert the given array $arr so that keys of the potentially multi-dimensional array
190
	 * are converted using the mapping given in $dictionary. Keys not found from $dictionary
191
	 * are not altered.
192
	 * @param array<int|string, mixed> $arr
193
	 * @param array<int|string, int|string> $dictionary
194
	 * @return array<int|string, mixed>
195
	 */
196
	public static function convertKeys(array $arr, array $dictionary) : array {
197
		$newArr = [];
198
199
		foreach ($arr as $k => $v) {
200
			$key = $dictionary[$k] ?? $k;
201
			$newArr[$key] = \is_array($v) ? self::convertKeys($v, $dictionary) : $v;
202
		}
203
204
		return $newArr;
205
	}
206
207
	/**
208
	 * Walk through the given, potentially multi-dimensional, array and cast all leaf nodes
209
	 * to integer type. The array is modified in-place. Optionally, apply the conversion only
210
	 * on the leaf nodes matching the given predicate.
211
	 * @param mixed[] $arr Input/output array to operate on
212
	 */
213
	public static function intCastValues(array &$arr, ?callable $predicate=null) : void {
214
		\array_walk_recursive($arr, function(&$value) use($predicate) {
215
			if ($predicate === null || $predicate($value)) {
216
				$value = (int)$value;
217
			}
218
		});
219
	}
220
221
	/**
222
	 * Given a two-dimensional array, sort the outer dimension according to values in the
223
	 * specified column of the inner dimension.
224
	 * @param array<array<string, mixed>> $arr Input/output array to operate on
225
	 */
226
	public static function sortByColumn(array &$arr, string $column) : void {
227
		\usort($arr, fn($a, $b) => StringUtil::caselessCompare($a[$column], $b[$column]));
228
	}
229
230
}