Dynamic array_multisort() without calling deprecated function create_function() - php

I have this function in my old php5 code that will accept a variable number of parameters and perform sorting based on the parameters:
function array_alternate_multisort(){
$arguments = func_get_args();
$arrays = $arguments[0];
for ($c = (count($arguments)-1); $c > 0; $c--)
{
if (in_array($arguments[$c], array(SORT_ASC , SORT_DESC)))
{
continue;
}
$compare = create_function('$a,$b','return strcasecmp($a["'.$arguments[$c].'"], $b["'.$arguments[$c].'"]);');
usort($arrays, $compare);
if ($arguments[$c+1] == SORT_DESC)
{
$arrays = array_reverse($arrays);
}
}
return $arrays ;
}
I call it like this:
$alliances = array_alternate_multisort($alliances, "output", SORT_DESC, "score", SORT_DESC);
How can I replace this with a function without calling create_function()?

You can use an anonymous function instead:
$compare = function ($a, $b) use ($arguments, $c) {
return strcasecmp($a[$arguments[$c]], $b[$arguments[$c]]);
};
Untested but should be close enough
The use keyword allows you to inherit variables from the parent scope inside your function.

First of all, I'll argue that if you have the ability to hardcode this into your script:
$alliances = array_alternate_multisort($alliances, "output", SORT_DESC, "score", SORT_DESC);
then you can just as easily completely scrap your custom function and just write this:
Code: (Demo)
array_multisort(...[
array_column($alliances, 'output'),
SORT_DESC,
array_column($alliances, 'score'),
SORT_DESC,
&$alliances
]);
This will do EVERYTHING that your custom function will do and more WITHOUT introducing ANY custom function.
This is a very concise, totally native, and instantly readable technique. Using this means that:
you will not confine your script to only SORT_ASC and SORT_DESC; there are other useful sorting flags to enjoy for specific scenarios.
you can opt to omit the sorting direction parameters if you wish to use SORT_ASC (the default sorting flag).
you modify the input array by reference like other native sorting functions.
Now anything beyond the above is going to introduce unnecessary convolution. To keep this hypothetical (which, again, I don't endorse) demonstration simple I'll insist that the sorting direction flags are required like in your original snippet.
Code: (Demo)
function array_alternate_multisort($array, ...$args) {
foreach ($args as $i => $arg) {
$sortParams[] = $i & 1 ? $arg : array_column($array, $arg);
}
$sortParams[] = &$array;
array_multisort(...$sortParams);
return $array;
}
& 1 is a bitwise odd check. If the index is odd, push the constant into $sortParams otherwise push the column data into $sortParams.
This answer belongs to a family of similar answers that use the splat operator to unpack parameters into a array_multisort() call.
array_multisort and dynamic variable options
Sort array of associative arrays on multiple columns using specified sorting rules

Related

Recursive function to sort an array of objects

I've been trying to write a recursive function that would reorder an array of objects based on the order provided by another array (simple, numeric array).
I want to use this sorting function to sort an array of objects by a 'template' array that would hold only one property of each object present in the array to sort, e.g.
$template = ['A', 'B', 'C']
Array to sort:
$myArray = [
new Element('B'),
new Element('C'),
new Element('A'),
]
class Element
{
public $name;
public function __construct($name)
{
$this->name = $name;
}
}
I was not successful. Perhaps you might have an idea about how to apprach this task?
I don't see how recursion would help you with that task. This is how you can use the built in sort functions:
usort($myArray, function(Element $a, Element $b) use ($template) {
return array_search($a->name, $template) - array_search($b->name, $template);
});
usort sorts by given comparison callback
I added the Element type hint to the callback because the sort function will only work with arrays of Element objects
array_search returns the key for the given name property value within the $template array. If the value does not exist in the array, it will be placed at the beginning, because the result false is coerced to 0.
I also managed to do the sorting using recursion - here it is:
function orderRecursively($template, $myArray, &$ordered)
{
foreach($myArray as $k => $v) {
if ($myArray[$k]->name == $template[0]) {
$ordered[] = $myArray[$k];
array_splice($template, 0, 1);
}
}
if (!empty($template)) orderRecursively($template, $myArray, $ordered);
}
$ordered = [];
order($template, $myArray, $ordered);
$ordered would then hold the sorted array of objects.
Still, I find #fschmengler's answer more elegant.

How to pass in an empty generator parameter?

I have a method which takes a generator plus some additional parameters and returns a new generator:
function merge(\Generator $carry, array $additional)
{
foreach ( $carry as $item ) {
yield $item;
}
foreach ( $additional as $item ) {
yield $item;
}
}
The usual use case for this function is similar to this:
function source()
{
for ( $i = 0; $i < 3; $i++ ) {
yield $i;
}
}
foreach ( merge(source(), [4, 5]) as $item ) {
var_dump($item);
}
But the problem is that sometimes I need to pass empty source to the merge method. Ideally I would like to be able to do something like this:
merge(\Generator::getEmpty(), [4, 5]);
Which is exactly how I would do in C# (there is a IEnumerable<T>.Empty property). But I don't see any kind of empty generator in the manual.
I've managed to work around this (for now) by using this function:
function sourceEmpty()
{
if ( false ) {
yield;
}
}
And this works. The code:
foreach ( merge(sourceEmpty(), [4, 5]) as $item ) {
var_dump($item);
}
correctly outputs:
int(4)
int(5)
But this is obviously not an ideal solution. What would be the proper way of passing an empty generator to the merge method?
Bit late, but needed an empty generator myself, and realized creating one is actually quite easy...
function empty_generator(): Generator
{
yield from [];
}
Don't know if that's better than using the EmptyIterator, but this way you get exactly the same type as non-empty generators at least.
Just for completeness, perhaps the least verbose answer so far:
function generator() {
return; yield;
}
I just wondered about the same question and remembered an early description in the docs (which should be in at least semantically until today) that a generator function is any function with the yield keyword.
Now when the function returns before it yields, the generator should be empty.
And so it is.
Example on 3v4l.org: https://3v4l.org/iqaIY
I've found the solution:
Since \Generator extends \Iterator I can just change the method signature to this:
function merge(\Iterator $carry, array $additional)
{
// ...
This is input covariance thus it would break backward compatibility, but only if someone did extend the merge method. Any invocations will still work.
Now I can invoke the method with PHP's native EmptyIterator:
merge(new \EmptyIterator, [4, 5]);
And the usual generator also works:
merge(source(), [4, 5])
As explained in the official docs, you can create an in-line Generator instance, by using yield in an expression:
$empty = (yield);
That should work, but when I tried using that, I got a fatal error (yield expression can only be used in a function). Using null didn't help either:
$empty = (yield null); //error
So I guess you're stuck with the sourceEmpty function... it was the only thing I found that works... note that it will create a null value in the array you're iterating.
All the code was tested on PHP 5.5.9, BTW
The best fix I can come up with (seeing as compatibility is an issue) would be to make both arguments optional:
function merge(\Generator $carry = null, array $additional = array())
{
if ($carry)
foreach ($carry as $item)
yield $item;
foreach ($additional as $item)
yield $item;
}
foreach(merge(null, [1,2]) as $item)
var_dump($item);
This way, existing code won't brake, and instead of constructing an empty generator, passing null will work just fine, too.

Sorting an array using usort and a dynamic generated function

I am using php function usort to sort an array. The custom php function must be generated because its dynamic
$intCompareField = 2;
$functSort = function($a, $b) {
return ($a[$intCompareField] > $a[$intCompareField])?1:-1;
}
usort($arrayToSort, $functSort);
The $intCompareField in the compare function is null, my guessing is because the $intCompareField was declared outside of the function. Setting global $intCompareField does not seem to work.
Ps: I am using $intCompareField because the array to sort is multidimensional and i want to be able what key in the array to sort.
While Dor Shemer's answer would suffice, I find it often better to have a function which generates the required comparison function.
$functSort = function ($field) {
return function($a, $b) use ($field) {
// Do your comparison here
};
};
$intCompareField = 2;
usort($arrayToSort, $functSort($intCompareField));
You could make the function in $functSort be a named function (e.g. sort_by_field_factory() or some other appropriate name), there's no requirement for it to be an anonymous function.
Try adding use, which passes variables from the outer scope to anonymous functions
function($a, $b) use ($intCompareField) {
return ($a[$intCompareField] > $a[$intCompareField])?1:-1;
}

working of callback function

while reading abt array_filter() from php manual,came to face example to demostrate
the same function using callback function as given below
<?php
function odd($var)
{
// returns whether the input integer is odd
return($var & 1);
}
function even($var)
{
// returns whether the input integer is even
return(!($var & 1));
}
$array1 = array("a"=>1, "b"=>2, "c"=>3, "d"=>4, "e"=>5);
$array2 = array(6, 7, 8, 9, 10, 11, 12);
echo "Odd :\n";
print_r(array_filter($array1, "odd"));
echo "Even:\n";
print_r(array_filter($array2, "even"));
?>
can you please help me to know how actually calback function calling,actual parameter pass,working?
any link to demostrate about callback wld be great help.
In the two given examples, array_filter will go over the values in the passed array and send each value in it to the callback function (odd or even). The callback function will then inspect the value to see whether it is odd or even and return TRUE or FALSE. If it returns FALSE, the value is filtered from the array.
The easiest way to find out what your function is passing to your callback is to supply a callback that prints the passed arguments, e.g.
array_filter($anArray, function() { var_dump(func_get_args()) });
Callbacks are described in detail at
http://de.php.net/manual/en/language.pseudo-types.php#language.types.callback
Imagine you have a function like this:
function getNumbersDivisibleBy3($arr)
{
$threes = array();
foreach($arr as $val)
{
if($val % 3 == 0)
{
$threes[] = $val;
}
}
return $threes
}
This function filters out all the numbers divisible by three from an array and returns them as another array.
Now imagine another function:
function GetWordsStartingWithC($arr)
{
$cs = array();
foreach($arr as $word)
{
if($word[0] == 'C')
{
$cs[] = $word;
}
}
return $cs;
}
This function filters out all the words that start with C from an array of words and returns them another array.
If you look at the above functions, their meta function (as it were) can be explained as "This functions filters out all the items in an array that satisfies a condition and returns them as another array."
So instead of having to write the same boiler plate code to iterate through a list and filter out all elements that match, the PHP developers have written a function that takes an array and a string that is a function name.
In other languages, such as C#, instead of a string that is a function name, you actually pass in an object called a delegate, which is a pointer or reference to a function. But in PHP they have ways of figuring out which function you mean by the name you pass in. I don't know what those are.
So the array_filter function could look something like this (it won't as it's probably not written in PHP)
function array_filter($arr, $callbackname)
{
$result = array();
foreach($arr as $item)
{
if(call_user_func($callbackname, $item))
{
$result[] = $item;
}
}
return $result;
}
In that function you can see how similar it is to the previous two, but instead of a predefined condition, it calls back (using the call_user_func() function) the function to be used via the name you passed in and applies it to each item in the array by using each item as a parameter for the function.
So you can reduce the amount of code you write by using array_filter because you don't have to write the boiler plate iteration code, just the conditional function you need to filter on.
A callback function is a function that it "called back" by another one. In the case of array_filter() the callback is invoked for every element of the array, and that element is the argument passed to it. You don't control what argument are passed, that's up the the main function you're using.
So you basically say to array_filter(): please run through this array and apply this function named "odd" to every element; this function will return a boolean value so you know what to keep and what to discard.

Sorting an array of objects in PHP

I want to write a static method in a class to generically sort an array of objects.
I am thinking of somthing along the lines of:
class GenUtils {
const ASCENDING = 1;
const DESCENDING = 2;
protected static alphaSort($value1, $value2, $sort_type=self::DESCENDING){
$retval = strcasecmp($value1, $value2);
return ($sort_type == self::DESCENDING) ? $retval : (-1*$retval);
}
protected static numericSort($value1, $value2, $sort_type=self::DESCENDING){
return $value1 < $value2;
}
// Assumption: array is non-empty and homogeneous
public doSort(array& $object_array, $method_name, $sort_type=self::DESCENDING) {
if(!empty($object_array) && method_exists($object_array[0],$method_name)) {
$element = $object_array[0];
$value = $element->$method_name();
if(is_string($value)){
//do string sort (possibly using usort)
}
elseif(is_number($value)){
//do numeric sort (possibly using usort)
}
}
}
}
This is just a quick brain dump -perharps someone can fill in the missing pieces, or suggest a better way of doing this?
[Edit]
Just to clarify, the objects to be sorted (in the array), have methods which return either a string (e.g. getName()) or a numeric value (e.g. getId())
A typical usecase code snippet would therefore be somethimng like this:
GenUtils::doSort($objects,'getName'); // This will do an alphabetic DESC sort using the getName() method
GenUtils::doSort($objects, 'getId', GenUtils::ASCENDING); // This will do a numeric ASC sort using the getId() method
The use cases (numeric and string) in your example are already built in to PHP - Check out the sort function. I would use the built-in function unless I had more specific needs.
Use usort and define your own comparison function to work with your objects.

Categories