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.
Related
Hi I have the following function:
foreach($array_1 as &$arr){
usort($arr,"cmp");
}
function cmp($a, $b) {
if ($a[2] == $b[2]) {
return 0;
}
return ($a[2] < $b[2]) ? -1 : 1;
}
If I do it like this it works, but if I put a variable instead of 2 which == 2 and a number the function doesn't work. Why is this and how can I work around it? I want to be able to change the number to sort an array based on variable input.
The code that doesn't work is:
$type = 2;
foreach($array_1 as &$arr){
usort($arr,"cmp");
}
function cmp($a, $b) {
if ($a[$type] == $b[$type]) {
return 0;
}
return ($a[$type] < $b[$type]) ? -1 : 1;
}
For PHP 5.3+, which hopefully you should be using, the solution is simple using a closure:
$index = 2;
foreach ($array_1 as &$arr) {
usort($arr, function ($a, $b) use ($index) {
return $a[$index] - $b[$index];
});
}
For PHP 5.2-, you could use global variables (YUCK!!) or a class:
class Sort {
protected $index;
public function __construct($index) {
$this->index = $index;
}
public function compare($a, $b) {
return $a[$this->index] - $b[$this->index];
}
}
$index = 2;
foreach ($array_1 as &$arr) {
usort($arr, array(new Sort($index), 'compare'));
}
I am using a uasort function like this:
uasort($entity_list, 'sortArray');
function sortArray($a, $b) {
if($a['fixed_column_name'] == $b['fixed_column_name']) {
return 0;
}
return ($a['fixed_column_name'] < $b['fixed_column_name']) ? -1 : 1;
}
I would like to pass a parameter to the sortArray function, like this:
uasort($entity_list, 'sortArray($arg)');
function sortArray($a, $b, $arg) {
$larg = $arg;
if($a[$larg] == $b[$larg]) {
return 0;
}
return ($a[$larg] < $b[$larg]) ? -1 : 1;
}
If you're using PHP 5.3+ (and you really should at this point in time), you can use closures:
uasort($entity_list, function ($a, $b) use ($arg) {
if ($a[$arg] == $b[$arg]) {
return 0;
}
return ($a[$arg] < $b[$arg]) ? -1 : 1;
});
Otherwise, you'll have to work around doing the same thing using global variables (oh noes!) or a class.
You could always use a class:
$sorter = new Sorter($arg);
usort($entity_list, array($sorter, "sort")); //will use $sorter->sort as callback
class Sorter {
function __construct($arg) {
$this->arg = $arg;
}
function sort($a, $b) {
// sort using $a, $b and $this->arg
}
}
Closures are nicer though ^^
Take a look at Example #4 using a closure on the usort() page of the PHP manual. The same technique can be used with all the basic sorts, including uasort()
I have a class that is basically a wrapper to an Array and implements IteratorAggregate.
When a new object of the class is created, it stores it's value in a protected variable called $value. The value can be a string, integer, etc.. or a traversable object (like an array).
This object is "recursive" since when a traversable object (like an array) is passed to the constructor, he creates another instance of the class. Here's the constructor to clarify:
class ListItem implements \IteratorAggregate
{
protected $readOnly = false;
protected $key;
protected $value;
public function __construct($key, $value, $readOnly = false)
{
if($readOnly) $this->readOnly = true;
if(is_numeric($key)) $key = 'index'.$key;
$this->key = $key;
if (is_array($value) || $value instanceof Traversable) {
$this->value = array();
foreach($value as $k => $v) {
if(is_numeric($k)) $k = 'index'.$k;
$this->value[$k] = new ListItem($k, $v, $readOnly);
}
} else {
$this->value = $value;
}
}
public function __toString()
{
if ( is_array($this->value) ) {
return 'ListItem OBJECT(' . count($this->value) . ')';
} else {
return $this->value;
}
}
Right now, I'm trying to write a simple sorting method for the class.
To sort by Key, this works like a charm:
$success = ksort($this->value, $comparison);
but to sort by value, asort does not work since the actual value I'm trying to sort is stored inside the value property.
So I tried using uasort, like this:
$success = uasort($this->value, function ($a, $b)
{
if ($a->value == $b->value) return 0;
else if($a->value < $b->value) return -1;
else return 1;
});
but for some unclear reason i get the following error:
Warning: uasort() [function.uasort]: Array was modified by the user
comparison function in /* /* /* /ListItem.php on line 129
Q. Why does this happen if I'm just accessing $value for comparison not actually changing anything?
It seems a closure (or anonymous function) is in the global scope which means uasort could not access private or protected members of the ListItem object (although other ListItem Objects can access their sibling's private/protected properties)
this solved the problem: (casting to string)
$success = uasort($this->value, function ($objA, $objB) use ($comparison)
{
$a = (string) $objA;
$b = (string) $objB;
if($comparison == ListItem::SORT_NUMERIC) {
if (is_numeric($a)) $a = (int) $a;
if (is_numeric($b)) $b = (int) $b;
}
if ($a == $b) return 0;
else if($a < $b) return -1;
else return 1;
});
Here is what I want to do:
$newArray = array();
foreach($student as $s){
$newArray[$s->id][$s->grade] = $s;
}
I want to sort the students by their grades (more of a group than a sort) but I just want the grades to be sorted not the id. I could have don't this:
$newArray[$s->id] = $s->grade
asort($newArray)
but I need the remaining data in $s. Also, there is huge chunk of data associated with each student which I want to maintain.
How can I achieve such a sorting?
Edit:
Sine you're working in a framework, best declare your sort callback as a member function (inside the same class as where you'll be needing it, of course):
private function sortCB(array $a, array $b)
{//the array type hinting in arguments is optional
$i = array_keys($a);//but highly recommended
$j = array_keys($b);
if (end($i) === end($j))
{
return 0;
}
//replace '>' with '<' if you want to sort descending
return (end($i) > end($j) ? 1 : -1);//this is ascending
}
Then, in the method where the actual sorting is needed:
uasort($theArray,array($this,'sortCB'));
For more examples, see the docs. I've added a full class example at the end of this (bulky) answer
I've tried this on writecodeonline, which isn't all too good at this kind of stuff, but this did work:
$foo = array_fill_keys(array('foo','bar','q','Bond'),array());
$i = '256';
foreach($foo as $k=>$v)
{
$foo[$k][$i] = $k;
$i = (string)((int)$i%2 === 0 ? ((int)$i/2)+1 : (int)$i*3);
}
function sortCB($a,$b)
{
$i = array_keys($a);
$j = array_keys($b);
if (end($i) === end($j))
{
return 0;
}
return (end($i) > end($j) ? 1 : -1);
}
uasort($foo,'sortCB');
var_dump($foo);
But since you're using a framework, you might do well declaring that function as a member function private function sortCB(array $a,array $b), and use it like so:
uasort($foo,array($this, 'sortCB'));
There might be some more info on how best to use this callback function in a class context here
Full example + usage (tested and working):
class test
{
public $foo = null;
public function __construct()
{
$this->foo = array_fill_keys(array('foo','bar','q','Bond'),array());
$i = '256';
foreach($this->foo as $k=>$v)
{
$this->foo[$k][$i] = $k;
$i = (string)((int)$i%2 === 0 ? ((int)$i/2)+1 : (int)$i*3);
}
}
private function sortCB($a,$b)
{
$i = array_keys($a);
$j = array_keys($b);
if (end($i) === end($j))
{
return 0;
}
return (end($i) > end($j) ? 1 : -1);
}
public function sortFoo()
{
uasort($this->foo,array($this,'sortCB'));
print_r($this->foo);
return $this->foo;
}
}
$bar = new test();
$arr = $bar->sortFoo();
You can do something like:
foreach($student as $s){
$newArray[$s->id] = $s;
}
usort($newArray, function ($a, $b) { return $a->grade - $b->grade; });
Edit
For later versions that don't support anonymous functions you can define comparison function first:
function sortByGrade($a, $b)
{
return $a->grade - $b->grade;
}
usort($newArray, 'sortByGrade');
But if you get this data from db it would be easier to order it in your sql query. If you use ORM you can use its associated method.
Is it possible, in any way, to pass comparison operators as variables to a function? I am looking at producing some convenience functions, for example (and I know this won't work):
function isAnd($var, $value, $operator = '==')
{
if(isset($var) && $var $operator $value)
return true;
}
if(isAnd(1, 1, '===')) echo 'worked';
Thanks in advance.
You can also use version_compare() function, as you can pass operator which will be used for comparison as third argument.
How about this one?
function num_cond ($var1, $op, $var2) {
switch ($op) {
case "=": return $var1 == $var2;
case "!=": return $var1 != $var2;
case ">=": return $var1 >= $var2;
case "<=": return $var1 <= $var2;
case ">": return $var1 > $var2;
case "<": return $var1 < $var2;
default: return true;
}
}
Test:
$ops = array( "=", "!=", ">=", "<=", ">", "<" );
$v1 = 1; $v2 = 5;
foreach ($ops as $op) {
if (num_cond($v1, $op, $v2)) echo "True ($v1 $op $v2)\n"; else echo "False ($v1 $op $v2)\n";
}
How about a small class:
class compare
{
function is($op1,$op2,$c)
{
$meth = array('===' => 'type_equal', '<' => 'less_than');
if($method = $meth[$c]) {
return $this->$method($op1,$op2);
}
return null; // or throw excp.
}
function type_equal($op1,$op2)
{
return $op1 === $op2;
}
function less_than($op1,$op2)
{
return $op1 < $op2;
}
}
The top answer recommends a small class, but I like a trait.
trait DynamicComparisons{
private $operatorToMethodTranslation = [
'==' => 'equal',
'===' => 'totallyEqual',
'!=' => 'notEqual',
'>' => 'greaterThan',
'<' => 'lessThan',
];
protected function is($value_a, $operation, $value_b){
if($method = $this->operatorToMethodTranslation[$operation]){
return $this->$method($value_a, $value_b);
}
throw new \Exception('Unknown Dynamic Operator.');
}
private function equal($value_a, $value_b){
return $value_a == $value_b;
}
private function totallyEqual($value_a, $value_b){
return $value_a === $value_b;
}
private function notEqual($value_a, $value_b){
return $value_a != $value_b;
}
private function greaterThan($value_a, $value_b){
return $value_a > $value_b;
}
private function lessThan($value_a, $value_b){
return $value_a < $value_b;
}
private function greaterThanOrEqual($value_a, $value_b){
return $value_a >= $value_b;
}
private function lessThanOrEqual($value_a, $value_b){
return $value_a <= $value_b;
}
}
The bigger problem is that this function is pretty pointless. Let's replace that with a real (hypothetically working) example:
function isAnd($var, $value, $operator = '==') {
return isset($var) && $var $operator $value;
}
isAnd($foo, 1, '===');
In this example $foo is not set. You'll get an error because you're trying to pass a non-existent variable ($foo) to a function (isAnd). So, you will need to test $foo for isset before calling isAnd:
isset($foo) && isAnd($foo, 1, '===');
So, any variable that ever enters the isAnd function is definitely set. You don't need to test for it inside the function. So the whole exercise is pretty pointless.
What may be confusing is that isset() and empty() don't have this limitation, i.e. you can pass a non-existent variable to them without error. The thing is though, these are not normal functions, they're special language constructs (that happen to look like functions; blame PHP). Unfortunately you can not make these kinds of constructs, parameters for your functions always need to exist.
You should just get used to writing isset($foo) && $foo === 1. With properly structured code, you can reduce this to a minimum by always declaring all variables you're going to use, which is good practice anyway.
For the dynamic operator... you'll need some form of if ... else somewhere to decide which operator to use anyway. Instead of setting the operator variable and then evaluating it, isn't it easier to do the evaluation right there?
If you absolutely insist you can use eval.
if(isset($var) && eval("return \$var $operator \$value"))
return true;
But I wouldn't recommend it.
Here is a simple solution which should work for almost all the operators
Eg.
$b = 10;
$c = '+';
$p = $a . $c. $b; // Forming a String equation
$p = eval('return '.$p.';'); // Evaluating the Equation
echo $p;
Output:
15
Another example with comparison operator:
$b = 10;
$c = '==';
$p = $a . $c. $b;
$p = eval('return '.$p.';');
echo $p;
Output:
false
Hope this helps.
As Michael Krelin suggests you could use eval - but that potentially enables a lot of code injection attacks.
You can't substitute a variable for an operator - but you can substitute a variable for a function:
function is_equal($a, $b) {
return $a==$b;
}
function is_same($a, $b) {
return $a===$b;
}
function is_greater_than($a, $b)
....
$compare='is_equal';
if ($compare($a, $b)) {
....
C.
As far as I know it is not possible and since there is no reference about callback on operators in PHP documentation,
http://www.php.net/manual/en/language.operators.php
instead of using eval, I would redefine each operators in global functions and use php callbacks
How do I implement a callback in PHP?
$a = 4;
eval('$condition=($a == 4)?true:false;');
if($condition){ echo "Yes"; }else{ echo "No"; }
No, it's impossible.
You can use conditional operators instead, but it will be much,much better if you redesign your application to make such a dynamic comparison unnecessary.