array_walk with anonymous function - php

I am getting familiar with anonymous function and closures in php and I need to use a closure or anon function to pass to array_walk but with an additional parameter here is a simple code block:
$array = array(1, 2, 3, 4, 5, array(1, 2));
$callback = function(&$value, $key)
{
$value = $key*$value;
};
var_dump($array, array_walk_recursive($array, $callback), $array);
It is very simple as it is but say I want to change the function as follows:
$callback = function(&$value, $key, $multiplier)
{
$value = $key*$value*$multiplier;
};
How can I pass the multiplier to the anon function? Or if it should be a closure how can it be done.
Because as follows is giving me an error:
array_walk_recursive($array, $callback(5))
I know that array_walk has an extra param $user_data which can be passed but I need it with a closure or anon function.

PHP's closures can be used for this:
<?php
$array = array(1, 2, 3, 4, 5, array(1, 2));
$multiplier = 5;
$callback = function(&$value, $key) use ($multiplier) {
$value = $key*$value*$multiplier;
};
var_dump($array, array_walk_recursive($array, $callback), $array);
Obviously $multiplier can receive non-static values, like ta query argument or the result of a computation. Just make sure to validate and type cast to guarantee a numeric value.

You can use two options:
$mltpl = 10;
$callback = function(&$value, $key)
{
global $mltpl;
$value = $key*$value*$mltpl;
};
Or
$mltpl = 10;
$callback = function(&$value, $key) use ($mltpl)
{
$value = $key*$value*$mltpl;
};

Related

About php7 Uniform Variable Syntax, nested functions

I'm try to solve a task which uses new functions php7 uniform variable syntax nested () support foo()() (https://wiki.php.net/rfc/uniform_variable_syntax).
I need write function test for this code:
$sum = function($a, $b) { return $a + $b; };
test(6)(2)(3)($sum); // 11
test(3)(1)($sum); // 4
test(3)(3)('pow'); // 27
I don't found any explanation for this feature. Where can I find how to use it? I see that I must return function name in function test, but how to pass argument?
Thanks all for help. It's something like this:
<?php
function test($a) {
echo '<br/>';
$arr[] = $a;
return $mf = function($b) use(&$mf, &$a, &$arr) {
if(gettype($b) == 'object') {
echo(array_reduce($arr, $b));
} elseif (gettype($b) == 'string') {
if($b == 'pow') {
echo array_reduce($arr, function ($carry, $a) {
return !empty($carry) ? pow($carry, $a) : $a;
});
}
} elseif (gettype($b) == 'integer') {
$arr[] = $b;
}
return $mf;
};
}
$sum = function($a, $b) { return $a + $b; };
test(6)(2)(3)($sum); // 11
test(3)(1)($sum); // 4
test(3)(3)('pow'); // 27
This is more about nested recursive functions, or currying, than that rfc. That rfc just enabled the syntax that supported it.
This uses recursion until you pass a callable:
function test($var) {
$values = [$var];
$function = function($callback) use (&$values, &$function) {
if (is_callable($callback)) {
return array_reduce(array_slice($values, 1), $callback, $values[0]);
}
$values[] = $callback;
return $function;
};
return $function;
}
Because your functions expect two parameters but your nesting could have unlimited parameters, it's best to use an array and array reduce.
However, since multiplication functions like pow won't work with a null initial value, you can specify the initial value as the first passed parameter from the array.

Using a Static Class Method to *Quickly* Sort an Array By Key Value in PHP

This question is different than others, as it's focus is on sorting an array with a static class method rather than the typical procedural approach.
I am look for a very performant way to implement the function sortByKeyValue below. Other somewhat related answers are focused on getting the job done, this question is more about getting the job done and getting it done very quickly (as a static method).
Anyone want to take a crack at it? I'll probably throw some bounty on this question to squeeze out the performance junkies. :)
<?php
$data = array(
array('name' => 'B', 'cheesy' => 'bacon'),
array('name' => 'C', 'delicious' => 'tacos'),
array('name' => 'A', 'lovely' => 'viddles'),
);
class MyArray {
public static function sortByKeyValue($array, $key, $direction = 'ASC') {
// Do the thing
// Help me!
return $array;
}
}
$data = MyArray::sortByKeyValue($data, 'name');
// Should output the name key in order
// I am not including the garbage keys I threw in, but they should be there too)
// [{"name": "A"},{"name": "B"},{"name": "C"}]
?>
You can use usort with a closure (Available php 5.3+)
usort($array, function($a, $b){ return strcmp($a["name"], $b["name"]);});
Pre 5.3 you would have to create a sorter function and pass to usort
function arraySorter($a, $b){
return strcmp($a["name"], $b["name"]);
}
usort($array, "arraySorter");
Or you can place the arraySorter as a static method on your class
public static function _arraySorter($a, $b){
return strcmp($a["name"], $b["name"]);
}
// then call
usort($array, array("MyArray", "_arraySorter")); // for static
Note. This will perform an in place sort.
As Suggested by #Kirk in comments you can use a function to return an anonymous sorter function so the implementation isn't bound to the name property
function getSorter($property){
return function($a, $b) use ($property){
return strcmp($a[$property], $b[$property]);
};
}
Then you can call
usort($array, getSorter("name"));
I ran the following to compare the speed of multisort, a pre-PHP 5.3 method, with a more modern method that uses usort with a closure function:
$alpha = 'abcdefghijklmnopqrstuvwxyz';
$cnt = 1000;
$key = 'name';
$direction = 'ASC';
$array = array();
for ($i=0; $i<$cnt; $i++){
$array[$i]['name'] = substr(str_shuffle($alpha), 0, 8);
$array[$i]['job'] = substr(str_shuffle($alpha), 0, 8);
}
$pre = $array;//the test dummies
//PRE-PHP 5.3
$t[0] = -microtime();
$sub = array();
foreach ($pre as $item) {
$sub[] = $item[$key];
}
if ($direction == 'ASC') $ord = SORT_ASC;
else $ord = SORD_DESC;
array_multisort($sub, $ord, $pre);
$t[0] += microtime();
//USORT WITH CLOSURE
$t[1] = -microtime();
usort($array, function ($a, $b) use($key, $direction){
if ($direction == 'ASC'){
return strcmp($a[$key], $b[$key]);
}
return strcmp($b[$key], $a[$key]);
});
$t[1] += microtime();
var_dump($t);
As you can see, the old method was more than twice as fast:
Array
(
[0] => 0.005
[1] => 0.014001
)
So here's how I would do the class:
class MyArray {
public static function sortByKeyValue($array, $key, $direction = SORT_ASC) {
$sub = array();
foreach ($array as $item) {
$sub[] = $item[$key];
}
array_multisort($sub, $direction, $array);
return $array;
}
}
I prefer array_multisort():
<?php
$data = array(
array('name' => 'B', 'cheesy' => 'bacon'),
array('name' => 'C', 'delicious' => 'tacos'),
array('name' => 'A', 'lovely' => 'viddles'),
);
class MyArray {
public static function sortByKeyValue($array, $key, $direction = 'ASC') {
$tmp = array();
foreach($array as $k=>$r){
$tmp[] = $r[$key];
}
array_multisort($tmp,($direction == 'ASC' ? SORT_ASC : SORT_DESC),$array);
return $array;
}
}
$data = MyArray::sortByKeyValue($data, 'name');
echo '<pre>',print_r($data),'</pre>';

Elegant way to search an PHP array using a user-defined function

Basically, I want to be able to get the functionality of C++'s find_if(), Smalltalk's detect: etc.:
// would return the element or null
check_in_array($myArray, function($element) { return $elemnt->foo() > 10; });
But I don't know of any PHP function which does this. One "approximation" I came up with:
$check = array_filter($myArray, function($element) { ... });
if ($check)
//...
The downside of this is that the code's purpose is not immediately clear. Also, it won't stop iterating over the array even if the element was found, although this is more of a nitpick (if the data set is large enough to cause problems, linear search won't be an answer anyway)
To pull the first one from the array, or return false:
current(array_filter($myArray, function($element) { ... }))
More info on current() here.
Here's a basic solution
function array_find($xs, $f) {
foreach ($xs as $x) {
if (call_user_func($f, $x) === true)
return $x;
}
return null;
}
array_find([1,2,3,4,5,6], function($x) { return $x > 4; }); // 5
array_find([1,2,3,4,5,6], function($x) { return $x > 10; }); // null
In the event $f($x) returns true, the loop short circuits and $x is immediately returned. Compared to array_filter, this is better for our use case because array_find does not have to continue iterating after the first positive match has been found.
In the event the callback never returns true, a value of null is returned.
Note, I used call_user_func($f, $x) instead of just calling $f($x). This is appropriate here because it allows you to use any compatible callable
Class Foo {
static private $data = 'z';
static public function match($x) {
return $x === self::$data;
}
}
array_find(['x', 'y', 'z', 1, 2, 3], ['Foo', 'match']); // 'z'
Of course it works for more complex data structures too
$data = [
(object) ['id' => 1, 'value' => 'x'],
(object) ['id' => 2, 'value' => 'y'],
(object) ['id' => 3, 'value' => 'z']
];
array_find($data, function($x) { return $x->id === 3; });
// stdClass Object (
// [id] => 3
// [value] => z
// )
If you're using PHP 7, add some type hints
function array_find(array $xs, callable $f) { ...
The original array_search returns the key of the matched value, and not the value itself (this might be useful if you're will to change the original array later).
try this function (it also works will associatives arrays)
function array_search_func(array $arr, $func)
{
foreach ($arr as $key => $v)
if ($func($v))
return $key;
return false;
}
Use \iter\search() from nikic's iter library of primitive iteration functions. It has the added benefit that it operates on both arrays and Traversable collections.
$foundItem = \iter\search(function ($item) {
return $item > 10;
}, range(1, 20));
if ($foundItem !== null) {
echo $foundItem; // 11
}
Pulled from Laravel's Illuminate\Collections\Arr::first method:
if (!function_exists('array_first')) {
/**
* Return the first element in an array passing a given truth test.
*
* #param iterable $array
* #param callable|null $callback
* #param mixed $default
* #return mixed
*/
function array_first($array, callable $callback = null, $default = null)
{
if (is_null($callback)) {
if (empty($array)) {
return $default;
}
foreach ($array as $item) {
return $item;
}
}
foreach ($array as $key => $value) {
if ($callback($value, $key)) {
return $value;
}
}
return $default;
}
}
I think it's pretty good. There is also the Illuminate\Collections\Arr::last method, but it's probably not as optimized since it reverses the array and just calls the first method. It does get the job done, though.
if (!function_exists('array_last')) {
/**
* Return the last element in an array passing a given truth test.
*
* #param array $array
* #param callable|null $callback
* #param mixed $default
* #return mixed
*/
function array_last($array, callable $callback = null, $default = null)
{
if (is_null($callback)) {
return empty($array) ? $default : end($array);
}
return array_first(array_reverse($array, true), $callback, $default);
}
}
Pro-tip: If you have an array of objects, then you can specify the type of the callback's argument for that sweet IDE auto-completion.
$john = array_first($users, function(User $user) {
return $user->name === 'John';
});
// Works with pretty much anything.
$activeUsers = array_filter($users, function(User $user) {
return $user->isActive;
});
// Class example:
class User {
public string $name;
public bool $isActive;
//...
}
If you want to use some variable from the outer scope, you can use the use(&$var) syntax like this
foreach($values as $key => $value) {
array_find($conditionsRecords, function($row) use(&$key) {
$keyToFind = $key;
return $keyToFind;
})
}
You can write such a function yourself, although it is little more than a loop.
For instance, this function allows you to pass a callback function. The callback can either return 0 or a value. The callback I specify returns the integer if it is > 10. The function stops when the callback returns a non null value.
function check_in_array(array $array, $callback)
{
foreach($array as $item)
{
$value = call_user_func($callback, $item);
if ($value !== null)
return $value;
}
}
$a = array(1, 2, 3, 6, 9, 11, 15);
echo check_in_array($a, function($i){ return ($i > 10?$i:null); });
You can write your own function ;)
function callback_search ($array, $callback) { // name may vary
return array_filter($array, $callback);
}
This maybe seems useless, but it increases semantics and can increase readability

Can you implode an array into function arguments?

Is it possible to have an array and pass it into a function as separate arguments?
$name = array('test', 'dog', 'cat');
$name = implode(',' $name);
randomThing($name);
function randomThing($args) {
$args = func_get_args();
// Would be 'test', 'dog', 'cat'
print_r($args);
}
No. That's what call_user_func_array() is for.
As of PHP 5.6 you can use ... to pass an array as function arguments. See this example from the PHP documentation:
function add($a, $b) {
return $a + $b;
}
echo add(...[1, 2])."\n";
$a = [1, 2];
echo add(...$a);

Is an 'invoke'-style callback possible in PHP5?

PHP functions such as 'array_map' take a callback, which can be a simple function or a class or object method:
$array2 = array_map('myFunc', $array);
or
$array2 = array_map(array($object, 'myMethod'), $array);
But is there a syntax to pass a method which is bound within the iteration to the current object (like 'invoke' in Prototype.js)? So that the following could be used:
$array2 = array_map('myMethod', $array);
with the effect of
foreach($array as $obj) $array2[] = $obj->myMethod();
Obviously I can use this form, or I can write a wrapper function to make the call, and even do that inline. But since 'myMethod' is already a method it seems to be going round the houses to have to do one of these.
Not currently. When php 5.3 comes out, you could use the following syntax:
$array2 = array_map(function($obj) { return $obj->myMethod(); }, $array);
function obj_array_map($method, $arr_of_objects) {
$out = array();
$args = array_slice(func_get_args(), 2);
foreach ($arr_of_objects as $key => $obj) {
$out[$key] = call_user_func_array(Array($obj, $method), $args);
}
return $out;
}
// this code
$a = Array($obj1, $obj2);
obj_array_map('method', $a, 1, 2, 3);
// results in the calls:
$obj1->method(1, 2, 3);
$obj2->method(1, 2, 3);
Basically, no. There is no special syntax to make this any easier.
I can think of a fancier way of doing this in PHP 5.3, seeing as there's always more than one way to do things in PHP, but I'd say it wouldn't necessarily be better than your foreach example:
$x = array_reduce(
$array_of_objects,
function($val, $obj) { $val = array_merge($val, $obj->myMethod()); return $val; },
array()
);
Just go with your foreach :)
<?php
// $obj->$method(); works, where $method is a string containing the name of the
// real method
function array_map_obj($method, $array) {
$out = array();
foreach ($array as $key => $obj)
$out[$key] = $obj->$method();
return $out;
}
// seems to work ...
class Foo {
private $y = 0;
public function __construct($x) {
$this->y = $x;
}
public function bar() {
return $this->y*2;
}
}
$objs = array();
for ($i=0; $i<20; $i++)
$objs[] = new Foo($i);
$res = array_map_obj('bar', $objs);
var_dump($res);
?>
voila!
This is a bit of a silly answer, but you could subclass ArrayObject and use that instead of a normal array:
<?php
class ArrayTest extends ArrayObject {
public function invokeMethod() {
$result = array();
$args = func_get_args();
$method = array_shift($args);
foreach ($this as $obj) {
$result[] = call_user_func_array(array($obj, $method), $args);
}
return $result;
}
}
//example class to use
class a {
private $a;
public function __construct($a) {
$this->a = $a;
}
public function multiply($n) {
return $this->a * $n;
}
}
//use ArrayTest instance instead of array
$array = new ArrayTest();
$array[] = new a(1);
$array[] = new a(2);
$array[] = new a(3);
print_r($array->invokeMethod('multiply', 2));
Outputs this:
Array
(
[0] => 2
[1] => 4
[2] => 6
)
I would use create_function() to ... well ... create a temporary function for array_map while waiting for PHP 5.3
$func = create_function('$o', '$o->myMethod();');
array_map($func, $objects);

Categories