Did O'reilly make a mistake: array_reduce - php

I am learning PHP from O'reilly media book 'Programing PHP' and I stumbled upon this:
function add_up ($running_total, $current_value) {
$running_total += $current_value * $current_value;
return $running_total;
}
$numbers = array(2, 3, 5, 7);
$total = array_reduce($numbers, 'add_up');
echo $total;
The array_reduce( ) line makes these function calls:
add_up(2,3)
add_up(11,5)
add_up(36,7)
// $total is now 87
But when I calculate I get 85. I think it should write like this:
The array_reduce( ) line makes these function calls:
add_up (0,2);
add_up (4,3);
add_up (13,5);
add_up (38,7);
Because optional value $initial is by default set to NULL.
mixed array_reduce ( array $input , callable $function [, mixed $initial = NULL ] )
Can somebody with more knowledge explain to me, who is wrong and why?

It has been reported in the errata (though not confirmed). But since you're not the only one to notice, you are most likely correct.
{128} Section "Reducing an Array";
Reducing An Array - Example of function calls created by array_reduce();
The array reduce() line makes these function calls:
add_up(2,3)
add_up(13,5)
add_up(38,7)
The correct list of calls should be:
add_up(0,2) // This line is missing in the book
add_up(4,3) // This line is incorrect in the book
add_up(13,5)
add_up(38,7)
[129] first example;
the resulting calls of the second example of array_reduce() should be:
add_up(11, 2)
add_up(15, 3)
add_up(24, 5)
add_up(49, 7)

Related

Collection Framework - need to cache result from the PHP method

Need help/clarification with caching results from the PHP method that takes a string and returns the number of unique characters in the string (method below ⬇️ done and works)
function uniqueCharacters($str): int
{
$counter = [];
$splitted = str_split($str);
foreach($splitted as $s) {
if(!isset($counter[$s]))
$counter[$s] = 0;
$counter[$s]++;
}
$unique = array_keys(array_filter($counter, function($c) {
return $c == 1;
}));
return count($unique);
}
It is expected that a string with the same character sequence may be passed several times to the method. Since the counting operation can be time-consuming, the method should cache the results, so that when the method is given a string previously encountered, it will simply retrieve the stored result. Use collections and maps where appropriate.
Tried a solution from https://stackoverflow.com/questions/3540403/caching-function-results-in-php but that one is not what was asked from the task...
Just keep a static cache in your function and only do the work if encountering the string for the first time. The static keyword will ensure that the variable's contents are not lost between subsequent function calls.
Because you only have a single string as an incoming parameter, you don't need to serialize the data to form a string. When declaring string keys in an array, those values will not be corrupted. However if your values -- to become keys -- are floats, booleans, null, arrays, objects, resources, (and probably a few other types that I'm not thinking of) then you'd better convert it to some form of string to be safe. In other words, you can safely use my snippet if your incoming parameter is string or int typed (not a mix of int and strings).
Code: (Demo)
function countUniqueCharacters(string $str): int
{
static $cache = [];
if (!key_exists($str, $cache)) {
echo PHP_EOL . "*** doing the work on $str ***";
$cache[$str] = count(
array_filter(
count_chars($str, 1),
fn($count) => $count === 1
)
);
}
return $cache[$str];
}
$tests = ['seven', 'eight', 'nine', 'seven', 'eight'];
foreach ($tests as $test) {
echo PHP_EOL . "$test : " . countUniqueCharacters($test);
}
Output:
*** doing the work on seven ***
seven : 3
*** doing the work on eight ***
eight : 5
*** doing the work on nine ***
nine : 2
seven : 3
eight : 5
Because your return value is an int (cannot be null), you can simplify the above using the null coalescing operator like this: (Demo)
function countUniqueCharacters(string $str): int
{
static $cache = [];
return $cache[$str] ??= count(
array_filter(
count_chars($str, 1),
fn($count) => $count === 1
)
);
}
Relevant links:
PHP function that caches its response by re-writing itself after first call
How to cache results of a function in laravel
Is there a better way to cache a property in PHP?

What is the meaning of three dots (...) in PHP?

While I am installing Magento 2 on my Server, I got an error. After investigating the code and found that there is are three dots (...), which is producing the error. I included the code I found below:
return new $type(...array_values($args));
What is this operator called, and what is its purpose?
This is literally called the ... operator in PHP, but is known as the splat operator from other languages. From a 2014 LornaJane blog post on the feature:
This feature allows you to capture a variable number of arguments to a function, combined with "normal" arguments passed in if you like. It's easiest to see with an example:
function concatenate($transform, ...$strings) {
$string = '';
foreach($strings as $piece) {
$string .= $piece;
}
return($transform($string));
}
echo concatenate("strtoupper", "I'd ", "like ", 4 + 2, " apples");
(This would print I'D LIKE 6 APPLES)
The parameters list in the function declaration has the ... operator in it, and it basically means " ... and everything else should go into $strings". You can pass 2 or more arguments into this function and the second and subsequent ones will be added to the $strings array, ready to be used.
There are TWO uses for the ellipsis (...) PHP token—think of them as packing an array and unpacking an array. Both purposes apply to function arguments.
Pack
When defining a function, if you need a dynamic number of variables provided to the function (i.e., you don't know how many arguments will be provided to that function when called in your code), prefix the ellipsis (...) token to a variable name—e.g., ...$numbers—to capture all (remaining) arguments provided to that function into an array assigned to the named variable—in this case $numbers—that is accessible inside the function block. The number of arguments captured by prefixing ellipsis (...) can be zero or more.
For example:
// function definition
function sum (...$numbers) { // use ellipsis token when defining function
$acc = 0;
foreach ($numbers as $nn) {
$acc += $nn;
}
return $acc;
}
// call the function
echo sum(1, 2, 3, 4); // provide any number of arguments
> 10
// and again...
echo sum(1, 2, 3, 4, 5);
> 15
// and again...
echo sum();
> 0
When packing is used in function instantiation, prefixing ellipsis (...) to a variable name captures all remaining arguments, i.e., you can still have any number—zero or more—of initial, fixed (positional) arguments:
function sum ($first, $second, ...$remaining_numbers) {
$acc = $first + $second;
foreach ($remaining_numbers as $nn) {
$acc += $nn;
}
return $acc;
}
// call the function
echo sum(1, 2); // provide at least two arguments
> 3
// and again...
echo sum(1, 2, 3, 4); // first two are assigned to fixed arguments, the rest get "packed"
> 10
...the prefixed ellipsis variable captures all the rest. For this reason it must be the final function argument.
Unpack
Alternatively, when calling a function, if the arguments you provide to that function are previously combined into an array use a ellipsis (...) token prefixed variable "inline" to convert that array variable into individual arguments provided to the function. When any number of function arguments are replaced with an ellipsis prefixed variable, each array element is assigned to the respective function argument variable named in the function definition.
For example:
function add ($aa, $bb, $cc) {
return $aa + $bb + $cc;
}
$arr = [1, 2, 3];
echo add(...$arr); // use ellipsis token when calling function
> 6
$first = 1;
$arr = [2, 3];
echo add($first, ...$arr); // used with positional arguments
> 6
$first = 1;
$arr = [2, 3, 4, 5]; // array can be "oversized"
echo add($first, ...$arr); // remaining elements are ignored
> 6
Unpacking is particularly useful when using array functions to manipulate arrays or variables.
For example, unpacking the result of array_slice:
function echoTwo ($one, $two) {
echo "$one\n$two";
}
$steaks = array('ribeye', 'kc strip', 't-bone', 'sirloin', 'chuck');
// array_slice returns an array, but ellipsis unpacks it into function arguments
echoTwo(...array_slice($steaks, -2)); // return last two elements in array
> sirloin
> chuck
Every answer refers to the same blog post, besides them, here is the official documentation about variable-length argument lists:
http://php.net/manual/en/functions.arguments.php#functions.variable-arg-list
In PHP 5.6 and later, argument lists may include the ... token to denote that the function accepts a variable number of arguments. The arguments will be passed into the given variable as an array
It seems "splat" operator is not an official name, still it's cute!
In PHP 7.4 the ellipsis is also the Spread operator:
$parts = ['apple', 'pear'];
$fruits = ['banana', 'orange', ...$parts, 'watermelon'];
// ['banana', 'orange', 'apple', 'pear', 'watermelon'];
Source: https://wiki.php.net/rfc/spread_operator_for_array
Meaning is that it decomposes an associative array to a list. So you do not need to type N parameters to call a method, just one. If method allows a decomposed parameter and if parameters are of the same type.
For me, the most important thing about splat operator is that it can help to typehint array parameters:
$items = [
new Item(),
new Item()
];
$collection = new ItemCollection();
$collection->add(...$items); // !
// what works as well:
// $collection->add(new Item());
// $collection->add(new Item(), new Item(), new Item()); // :(
class Item {};
class ItemCollection {
/**
* #var Item[]
*/
protected $items = [];
public function add(Item ...$items)
{
foreach ($items as &$item) {
$this->items[] = $item;
}
}
}
it saves some effort on type control, especially while working with huge collections or very object-oriented.
Important to notice is that ...$array do decompose an array despite the type of its items, so you can go the ugly way also:
function test(string $a, int $i) {
echo sprintf('%s way as well', $a);
if ($i === 1) {
echo('!');
}
}
$params = [
(string) 'Ugly',
(int) 1
];
test(...$params);
// Output:
// Ugly way as well!
But please don't.
PHP 8 update - named arguments
Since PHP 8 you can now decompose associative arrays, it is, arrays that have keys for them values. If you use it in function that has arguments named the same, PHP will transfer values to proper variables:
$arr = [
'pi' => 3.14,
'r' => 4,
];
function circ($r, $pi) {
return 2*$pi*$r;
}
// so that call
circ(...$arr);
// will be effectively a call of
circ(pi: 3.14, r: 4);
and you can slightly less bother about order of parameters.
PHP 8.1 update - new syntax for creating callable
Despite usage with arrays, ... earned a whole new, very useful functionality that help creating callable from any context:
$f = strtoupper(...); // creating a callable
echo $f('fo');
class test {
public function func($a) {
echo $a . PHP_EOL;
}
}
$f = (new test)
->func(...); // creating a callable
$f('x');
In PHP 8.1, this syntax "(...)" is used as a new way to create callables.
Before PHP 8.1:
$callable = [$this, 'myMethod'];
After PHP 8.1:
$callable = $this->myMethod(...);
Source: https://wiki.php.net/rfc/first_class_callable_syntax
To use this feature, just warn PHP that it needs to unpack the array into variables using the ... operator. See here for more details, a simple example could look like this:
$email[] = "Hi there";
$email[] = "Thanks for registering, hope you like it";
mail("someone#example.com", ...$email);
This is the so called "splat" operator. Basically that thing translates to "any number of arguments"; introduced with PHP 5.6
See here for further details.
It seems no one has mentioned it, so here to stay[It will also help Google (& Other SEs) guide devs who asking for Rest Parameters in PHP]:
As indicated here its called Rest Parameters on JS & I prefer this meaningful naming over that splat thing!
In PHP, The functionality provided by ...args is called Variadic functions which's introduced on PHP5.6. Same functionality was used to be implemented using func_get_args().
In order to use it properly, you should use rest parameters syntax, anywhere it helps reducing boilerplate code.

PHP programming challenge - code generator wont accept

You are given an array in PHP, which contains positive integers and/or recursively nested arrays of positive integers. It may, for example, be initialized as:
$arr = array(array(141,151,161), 2, 3, array(101, 202, array(303,404)));
Write a function "function MaxArray($arr)" which returns the maximum value contained in $arr or some array nested within $arr. In the example, the returned value should be 404.
function MaxArray($arr){
static $x = 0;
foreach($arr as $array){
if(is_array($array)) $x = MaxArray($array);
else if($array > $x) $x = $array;
}
return $x;
}
//additional output just for testing
$arr = array(array(141,5651,161), 2, 45446, array(101, 202, array(303,4)));
//additional output just for testing
print MaxArray($arr);
It works every time I run it but the online code generator would not accept. Is this an error on there part or is it me...?
Also on a personal note I would like to say thank you to everyone on stackoverflow, such an amazing site. Four years ago I knew nothing about programming and now I have built this from scratch audio visual reviews and I am writing android apps in java!
Update:
This question was taken from an online test that I sat. No error information was displayed, however the code generator just gave the response of wrong answer. Thus, I was not marked for the question.
I wanted to check my solution before appealing the decision.
# so... this works well enough
$arr = array(array(141,5651,161), 2, 45446, array(101, 202, array(303,4)));
print MaxArray($arr); // outputs 45446
print "\n";
# but now...let's try it again. In the same script. *gasp!*
$arr2 = array(42);
print MaxArray($arr2); // outputs 45446 (!!!)
The problem is that static variable; it isn't getting cleared between calls to the function. So previous results affect future ones. This is almost certainly incorrect, and would certainly cause a knowledgeable human judge to reject your code.
Get rid of the static variable. You can pass it as an argument, you can simply compare with subresults like the other answer here was suggesting... or you could show off with a closure and array_walk_recursive. :)
function MaxArray($arr) {
$max = -1;
array_walk_recursive($arr, function($x) use (&$max) {
if ($x > $max) $max = $x;
});
return $max;
}
<?php
$arr = [[141, 151, 161], 2, 3, [101, 202, [303, 404]]]
/**
* #param $input
* #return int
*/
function getActMax($input)
{
if ( ! is_array($input))
{
return $input;
} else
{
return findMax($input);
}
}
/**
* #param array $inputs
* #return int
*/
function findMax(array $inputs)
{
//Maybe the first item in the list is the biggest, this is our current max value
$max = getActMax(current($inputs));
foreach ($inputs as $v)
{
//If we found a bigger, change our max value
if (getActMax($v) > $max)
{
$max = getActMax($v);
}
}
return (int)$max;
}
var_dump(findMax($arr));

Best way to emulate Ruby "splat" operator in PHP function signatures [Method overloading]

In Ruby
def my_func(foo,bar,*zim)
[foo, bar, zim].collect(&:inspect)
end
puts my_func(1,2,3,4,5)
# 1
# 2
# [3, 4, 5]
In PHP (5.3)
function my_func($foo, $bar, ... ){
#...
}
What's the best way to to do this in PHP?
Copying my answer from another question related to this:
This is now possible with PHP 5.6.x, using the ... operator (also
known as splat operator in some languages):
Example:
function addDateIntervalsToDateTime( DateTime $dt, DateInterval ...$intervals )
{
foreach ( $intervals as $interval ) {
$dt->add( $interval );
}
return $dt;
}
Try
func_get_args — Returns an array comprising a function's argument list
PHP Version of your Ruby Snippet
function my_func($foo, $bar)
{
$arguments = func_get_args();
return array(
array_shift($arguments),
array_shift($arguments),
$arguments
);
}
print_r( my_func(1,2,3,4,5,6) );
or just
function my_func($foo, $bar)
{
return array($foo , $bar , array_slice(func_get_args(), 2));
}
gives
Array
(
[0] => 1
[1] => 2
[2] => Array
(
[0] => 3
[1] => 4
[2] => 5
[3] => 6
)
)
Note that func_get_args() will return all arguments passed to a function, not just those not in the signature. Also note that any arguments you define in the signature are considered required and PHP will raise a Warning if they are not present.
If you only want to get the remaining arguments and determine that at runtime, you could use the ReflectionFunction API to read the number of arguments in the signature and array_slice the full list of arguments to contain only the additional ones, e.g.
function my_func($foo, $bar)
{
$rf = new ReflectionFunction(__FUNCTION__);
$splat = array_slice(func_get_args(), $rf->getNumberOfParameters());
return array($foo, $bar, $splat);
}
Why anyone would want that over just using func_get_args() is beyond me, but it would work. More straightforward is accessing the arguments in any of these ways:
echo $foo;
echo func_get_arg(0); // same as $foo
$arguments = func_get_args();
echo $arguments[0]; // same as $foo too
If you need to document variable function arguments, PHPDoc suggest to use
/**
* #param Mixed $foo Required
* #param Mixed $bar Required
* #param Mixed, ... Optional Unlimited variable number of arguments
* #return Array
*/
Hope that helps.

How to implement a function that can take arbitary parameters in PHP?

Does PHP has such a feature?
You can use these functions:
func_get_arg » http://php.net/manual/en/function.func-get-arg.php
func_get_args » http://php.net/manual/en/function.func-get-args.php
fung_num_args » http://php.net/manual/en/function.func-num-args.php
Example:
function my_sum() { // <--- No parameters! :D
$total = func_num_args();
$numbers = func_get_args();
if ($total < 1) {
trigger_error('Less than one number :(');
return 0;
} else {
// Calculate the sum
$sum = array_sum($numbers);
return ($sum / $total);
}
}
// Usage:
echo my_sum(1, 2, 5, 109, 10231);
?>
Use the function func_get_args(). It will give you an array of all the parameters passed to that function.
Sure, make a function with no parameters, call it with whatever you want, and inside the function use func_get_args() to get the actual arguments.
Prehaps a better way is to pass one argument, that is actually an array of arguments.
eg:
function my_sum($data){
$sum = 0;
foreach($sum as $value){
$sum+=$value;
}
return $sum;
}
echo my_sum(array(1, 2, 5, 109, 10231));
I often use an associative array to pass arguments to a function. This is very usefull if the arguments being submitted are optional, or change in nature.
eg:
$data = array(
'date' => '10/4/06',
'name' => 'biggles',
'occupation' => 'pilot'
);
add_person($data);
This will also allow for better error detection. if you have many many arguments, it is quite possible to get them in the wrong order. plus if you extend or modify teh function it is more likely to keep working with existing code.

Categories