Reputation: 14532
I need a function, that takes an $array
and returns it with only the elements, where the key is prefixed by a a given $prefix
. The keys of the result array should not contain the prefix. Here is the unit test, that the method has to pass:
class ArrayProcessorTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider provideDataForExtractElementsWithKeyPrefixedByString
*/
public function testExtractElementsWithKeyPrefixedByString($testArray, $prefix, $expectedResult)
{
$this->assertEquals($expectedResult, $this->arrayProcessor->extractElementsWithKeyPrefixedByString($testArray, $prefix));
}
}
public function provideDataForExtractElementsWithKeyPrefixedByString()
{
$data = [];
$testArray = [
'foo__qwe' => '123', 'bar__asd' => '234', 'baz__yxc' => '345', 'buz__lmn' => '456',
'foo__qsc' => '567', 'bar__wsx' => '678', 'baz__edc' => '789', 'buz__rfv' => '890',
];
$prefixes = [
'singlePrefixFoo' => 'foo__',
'arrayPrefixBarBuz' => ['bar__', 'buz__',]
];
$expectedResults = [
'singlePrefixFoo' => ['qwe' => '123', 'qsc' => '567',],
'arrayPrefixBarBuz' => ['asd' => '234', 'wsx' => '678', 'lmn' => '456', 'rfv' => '890',]
];
$data = [
[$testArray, $prefixes['singlePrefixFoo'], $expectedResults['singlePrefixFoo']],
[$testArray, $prefixes['arrayPrefixBarBuz'], $expectedResults['arrayPrefixBarBuz']]
];
return $data;
}
Here my variant of the method:
class ArrayProcessor
{
public function extractElementsWithKeyPrefixedByString(array $array, $prefix)
{
$filteredArray = [];
if (is_string($prefix)) {
array_walk($array, function($value, $keyName) use($prefix, &$filteredArray) {
if (strpos($keyName, $prefix) === 0) {
$filteredArray[str_replace($prefix, '', $keyName)] = $value;
}
});
} elseif (is_array($prefix)) {
foreach ($prefix as $currentPrefix) {
$filteredArray = array_merge(
$filteredArray, $this->extractElementsWithKeyPrefixedByString($array, $currentPrefix)
);
}
}
return $filteredArray;
}
}
How to make this method more efficient?
Upvotes: 0
Views: 236
Reputation: 316979
How about
$array = ['foo__x' => 1, 'foo__y' => 2, 'bar__z' => 3, 'baz' => 42];
// For other test arrays and prefixes (of type `string` and `array`) s. the unit test in the question.
public function extractElementsWithKeyPrefixedByString(array $array, $prefix)
{
if (is_array($prefix)) {
$prefix = implode('|', $prefix);
}
$iterator = new \RegexIterator(
new \ArrayIterator($array),
'~^(' . $prefix . ')([a-zA-Z0-9_-]+)$~',
\RegexIterator::REPLACE,
\RegexIterator::USE_KEY
);
$iterator->replacement = '$2';
return iterator_to_array($iterator);
}
Output:
Array
(
[x] => 1
[y] => 2
[z] => 3
)
It performs a about 10%
better than the original code (for a case with string
and array
prefixes):
xDebug callgraph before (for the original code):
xDebug callgraph after:
Note that removing the prefixes is not necessarily a good idea. If you got foo__x
and bar__x
and remove both, "foo__" and "bar__", only the last x will make it to the resulting array, e.g. the value of foo__x
will be overwritten by bar__x
.
Upvotes: 2
Reputation: 14532
I could now significantly increase the performance by using the preg_filter(...)
ans some native array manipulating functions:
public function extractElementsWithKeyPrefixedByString(array $array, $prefix)
{
$filteredArray = [];
if (is_array($prefix)) {
$prefix = implode('|', $prefix);
}
$filteredKeys = preg_filter(['/(' . $prefix . ')([a-zA-Z0-9_-]+)/'], ['$2'], array_keys($array));
$arrayValues = array_values($array);
$filteredValues = array_intersect_key($arrayValues, $filteredKeys);
$filteredArray = array_combine($filteredKeys, $filteredValues);
return $filteredArray;
}
xDebug callgraph before:
xDebug callgraph after:
A disadvantage of this solution is, that I have to explicitly set the regex for the part of array key after the $prefix
(here [a-zA-Z0-9_-]+
-- it's not complete!). But it's at least faster (much, much faster!) than the original variant.
Upvotes: 0