Optimizing PHP algorithm - php

I have built a class that finds the smallest number divisible by a all numbers in a given range.
This is my code:
class SmallestDivisible
{
private $dividers = array();
public function findSmallestDivisible($counter)
{
$this->dividers = range(10, 20);
for($x=1; $x<$counter; $x++) {
if ($this->testIfDevisibleByAll($x, $this->dividers) == true) {
return $x;
}
}
}
private function testIfDevisibleByAll($x, $dividers)
{
foreach($dividers as $divider) {
if ($x % $divider !== 0) {
return false;
}
}
return true;
}
}
$n = new SmallestDivisible();
echo $n->findSmallestDivisible(1000000000);
This class finds a number that is divisible by all numbers in the range from 1 to 20 ($this->dividers).
I know it works well as I tested it with other, lower ranges, but, unfortunately, it is not able to find the solution for range(10, 20) within 30 seconds - and this is the time after which a PHP script is halted.
A parameter that is fed to the findSmallestDivisible method is the ceiling of the group of numbers the script is going to inspect (e.i. from 1 to $counter (1000000000 is this execution)).
I would be grateful for suggestions on how I can optimize this script so that it executes faster.

Your solution is brute-force and simply horrible.
Instead, how about handling it mathematically? You're looking for the lowest common multiple of numbers in your range, so...
function gcd($n, $m) {
$n=abs($n); $m=abs($m);
list($n,$m) = array(min($m,$n),max($m,$n));
while($r = $m % $n) {
list($m,$n) = array($n,$r);
}
return $n;
}
function lcm($n, $m) {
return $m * ($n/gcd($n,$m));
}
function lcm_array($arr) {
while(count($arr) > 1) {
array_push($arr, lcm(array_shift($arr),array_shift($arr)));
}
return array_shift($arr);
}
var_dump(lcm_array(range(10,20)));
// result int(232792560)
This means your original code would have had to do 232,792,560 iterations, no wonder it took so long!

Your goal is an easy mathematical calculation named the least common multiple but using brute force to compute it is totally wrong (as you already found out).
The Wikipedia page lists several reasonable algorithms that can be used to compute it faster.
The one explained in the section "A method using a table" is really fast and doesn't require much memory. You keep only the leftmost column of the table (the numbers you want to get the lcm for) and the rightmost column (the current step). If you implement it I suggest you hardcode a list of prime numbers into your program to avoid computing them.

Here is another solution I came up with.
In short, the algorithm will calculate LCM (lesast common multiple) for a group of numbers.
class Lcmx
{
public $currentLcm = 0;
private function gcd($a, $b)
{
if ($a == 0 || $b == 0)
return abs( max(abs($a), abs($b)) );
$r = $a % $b;
return ($r != 0) ?
$this->gcd($b, $r) :
abs($b);
}
private function lcm($a, $b)
{
return array_product(array($a, $b)) / $this->gcd($a, $b);
}
public function lcm_array($array = array())
{
$factors = $array;
while(count($factors) > 1) {
$this->currentLcm = $this->lcm(array_pop($factors), array_pop($factors));
array_push($factors, $this->currentLcm);
}
return $this;
}
}
$l = new Lcmx;
echo $l->lcm_array(range(1, 20))->currentLcm;
//232792560

Related

PHP create unique value compared to values from object

I need to create a unique value for $total, to be different from all other values from received object. It should compare total with order_amount from object, and then if it is the same, it should increase its value by 0.00000001, and then check again through that object to see if it matches again with another order_amount. The end result should be a unique value, with minimal increase compared to the starting $total value. All values are set to have 8 decmal places.
I have tried with the following but it won't get me the result i need. What am i doing wrong?
function unique_amount($amount, $rate) {
$total = round($amount / $rate, 8);
$other_amounts = some object...;
foreach($other_amounts as $amount) {
if ($amount->order_amount == $total) {
$total = $total + 0.00000001;
}
}
return $total;
}
<?php
define('EPSILON',0.00000001);
$total = 4.00000000;
$other_amounts = [4.00000001,4.00000000,4.00000002];
sort($other_amounts);
foreach($other_amounts as $each_amount){
if($total === $each_amount){ // $total === $each_amount->order_amount , incase of objects
$total += EPSILON;
}
}
var_dump($total);
OUTPUT
float(4.00000003)
You may add an additional break if $total < $each_amount to make it a bit more efficient.
UPDATE
To sort objects in $other_amounts based on amount, you can use usort.
usort($other_amounts,function($o1,$o2){
if($o1->order_amount < $o2->order_amount ) return -1;
else if($o1->order_amount > $o2->order_amount ) return 1;
return 0;
});
Ok, here's the solution I came up with. First I created a function to deliver random objects with random totals so I could work with, unnecessary for you but useful for the sake of this test:
function generate_objects()
{
$outputObjects = [];
for ($i=0; $i < 100; $i++) {
$object = new \stdClass();
$mainValue = random_int(1,9);
$decimalValue = random_int(1,9);
$object->order_amount = "{$mainValue}.0000000{$decimalValue}";
$outputObjects[] = $object;
}
return $outputObjects;
}
And now for the solution part, first the code, then the explanation:
function unique_amount($amount, $rate) {
$total = number_format(round($amount / $rate, 8), 4);
$searchTotal = $total;
if (strpos((string) $searchTotal, '.') !== false) {
$searchTotal = str_replace('.', '\.', $searchTotal);
}
$other_amounts = generate_objects();
$similarTotals = [];
foreach($other_amounts as $amount) {
if (preg_match("/^$searchTotal/", $amount->order_amount)) {
$similarTotals[] = $amount->order_amount;
}
}
if (!empty($similarTotals)) {
rsort($similarTotals);
$total = ($similarTotals[0] + 0.00000001);
}
// DEBUG
//echo '<pre>';
//$vars = get_defined_vars();
//unset($vars['other_amounts']);
//print_r($vars);
//die;
return $total;
}
$test = unique_amount(8,1);
echo $test;
I decided to use RegEx to find the amounts that starts with the ones I provided. Since in the exercise I provided only integers with 1-9 in the last decimal case, I tracked them and added them to one array $similarTotals.
Then I sorted this array, if not empty, descending by the values, got the first item and incremented by 0.00000001.
So, finally, returning either the $total (assuming nothing was found) or the incremented first item in the array.
PS. I did not expect this code to be this big but, well...
You can see the test here: https://3v4l.org/WGThI

Calculate the intersection of arrays with a threshold in PHP

Let's say I have following arrays:
$a = [1,2,3,4,5];
$b = [1,3,4,5,6];
$c = [1,7,8,9,10];
$d = [1,2,3,4];
The intersection of those would be $result = [1], which is easy enough. But what if I wanted the intersection of those with a minimum threshold of let's say 3? The threshold means I can skip one or more arrays from the intersection, as long as my resulting intersection has at least 3 elements, which in this case might result in:
$result = [1,3,4];
1, 3 and 4 are present in $a, $b and $d, but not in $c which is skipped because of the threshold. Is there an existing PHP class, algorithm or function with which I might accomplish this?
To do that we have to use combinations of an array. I have used combinations algorithm from this great article. Adjusting this algorithm we can write the following class:
class Intersections
{
protected $arrays;
private $arraysSize;
public function __construct($arrays)
{
$this->arrays = $arrays;
$this->arraysSize = count($arrays);
}
public function getByThreshold($threshold)
{
$intersections = $this->getAll();
foreach ($intersections as $intersection) {
if (count($intersection) >= $threshold) {
return $intersection;
}
}
return null;
}
protected $intersections;
public function getAll()
{
if (is_null($this->intersections)) {
$this->generateIntersections();
}
return $this->intersections;
}
private function generateIntersections()
{
$this->generateCombinationsMasks();
$this->generateCombinations();
$combinationSize = $this->arraysSize;
$intersectionSize = 0;
foreach ($this->combinations as $combination) {
$intersection = call_user_func_array('array_intersect', $combination);
if ($combinationSize > count($combination)) {
$combinationSize = count($combination);
$intersectionSize = 0;
}
if (count($intersection) > $intersectionSize) {
$this->intersections[$combinationSize] = $intersection;
$intersectionSize = count($intersection);
}
}
}
private $combinationsMasks;
private function generateCombinationsMasks()
{
$combinationsMasks = [];
$totalNumberOfCombinations = pow(2, $this->arraysSize);
for ($i = $totalNumberOfCombinations - 1; $i > 0; $i--) {
$combinationsMasks[] = str_pad(
decbin($i), $this->arraysSize, '0', STR_PAD_LEFT
);
}
usort($combinationsMasks, function ($a, $b) {
return strcmp(strtr($b, ['']), strtr($a, ['']));
});
$this->combinationsMasks = array_slice(
$combinationsMasks, 0, -$this->arraysSize
);
}
private $combinations;
private function generateCombinations()
{
$this->combinations = array_map(function ($combinationMask) {
return $this->generateCombination($combinationMask);
}, $this->combinationsMasks);
}
private function generateCombination($combinationMask)
{
$combination = [];
foreach (str_split($combinationMask) as $key => $indicator) {
if ($indicator) {
$combination[] = $this->arrays[$key];
}
}
return $combination;
}
}
I have tried to give self-explanatory names to methods. Some chunks of code can be optimized more (for example, I call count function multiple times on same arrays; this was done in order to reduce variables fiddling) for production use.
So basically the logic is pretty simple. We generate all combinations of arrays and sort them decreasingly by the number of used arrays. Then we find the longest intersection for each length of combinations. Actually, this is the hardest part. To get one particular intersection we return first one that matches threshold.
$intersections = new Intersections([$a, $b, $c, $d]);
var_dump($intersections->getAll());
var_dump($intersections->getByThreshold(3));
Here is working demo.
There are other ways to find all combinations, for example, one from "PHP Cookbook". You can choose whatever one you like most.
No build in feature for that. You need to write something short like:
$values = [];
foreach ([$a, $b, $c, $d] as $arr)
foreach ($arr as $value)
$values[$value] = ($values[$value] ?? 0) + 1;
// For threshold of 3
$values = array_keys(array_filter($values, function($a) { return $a >= 3; }));
Note: This requires PHP7 for ?? operator. Otherwise use something like:
$values[$value] = empty($values[$value]) ? 1 : $values[$value] + 1;

Finding all possible sequences

Hi I'm trying to build a function which will walk through all possible number sequences and pass the sequence through a function and if it return true stops.
Here is the markup:
function sequences($smallest, $biggest, $long, $func) {
$seq = array(); // Will have $long values
/*
* generates the sequence
*/
if (call_user_func($functions[$func])) {
return $seq;
} else {
//generate next sequence.
}
}
The generated sequence will have $long unique values between $smallest integer to $biggest integer and must be sorted example:
/* $long = 4; $smallest = 5, $biggest = 10;
*
* 5,6,7,8
* 5,6,7,9
* 5,6,7,10
* 5,6,8,9
* 5,6,8,10
* ...
* 7,8,9,10
*
*
* $long = 4; $smallest = 15, $biggest = 60;
*
* ...
* 15,41,49,56
* ...
* 37,39,53,60
* ...
*/
I haven’t been able to wrap my head around it, so far the only way I achieve that was to generate numbers randomly and than sorting the array each time.
That's clearly not the best way.
Other programming languages will be great too (c++, C#, js, java).
NOTES
Its not an odometer duplicate numbers in the sequence are not allowed and the values of each index can be bigger than 9.
The function that tests the sequence against a some conditions and will return True or False it doesn't matter what it does the actual problem is generating the sequences one by one without duplicates.
This was a fun challenge to generate the sequences as you specified.
This code below should do what you want (I think). Or at least you should be able to modify it to suit your needs. I wasn't exactly sure if you wanted the sequences() function to return only the first sequence for which the test function $functions[$func] returns true, or all the sequences so far. In this example only the first "match" is returned (or null if no match was found).
This code requires PHP 5.5+ as it uses a generator function (and also short array syntax available in PHP 5.4+). I tested this on PHP 5.5.12 and it seems to work as intended. The code can probably be modified to work on older PHP versions if needed (just avoid using generators/yields). Actually this is the first time I've written a PHP generator function.
sequenceGenerator() is a recursive generator function that you can iterate over using foreach.
I also wrote an echoSequences() function for testing the sequence generation which just outputs all of the generated sequences in order using echo.
function sequenceGenerator(array $items, $long = null, $level = 1, $path = null) {
$itemCount = count($items);
if (empty($long)) $long = $itemCount;
if ($path == null) $path = [];
if ($itemCount > 1) {
foreach ($items as $item) {
$subPath = $path;
$subPath[] = $item;
if ($level == $long) {
yield $subPath;
continue;
}
if (count($subPath) + count($items) > $long) {
$items = array_values(array_diff($items, [$item]));
$iteration = sequenceGenerator($items, $long, $level + 1, $subPath);
foreach ($iteration as $value) yield $value;
}
}
} elseif ($itemCount == 1) {
$path[] = $items[0];
yield $path;
}
}
// Function for testing sequence generation
function echoSequences($smallest, $biggest, $long) {
$items = range($smallest, $biggest);
foreach (sequenceGenerator($items, $long) as $sequence) {
echo implode(',', $sequence)."<br>\n";
}
}
function sequences($smallest, $biggest, $long, $func) {
global $functions;
$items = range($smallest, $biggest);
foreach (sequenceGenerator($items, $long) as $sequence) {
if (call_user_func($functions[$func], $sequence)) {
return $sequence;
}
}
return null; // Return null when $func didn't return true for any sequence
}
//echoSequences(5, 10, 4); // Test sequence generation
$functions = array(
// This test function returns true only for the sequence [5,6,8,10]
'testfunc' => function($sequence) { return ($sequence == [5,6,8,10]); }
);
$sequence = sequences(5, 10, 4, 'testfunc'); // Find the first sequence that 'testfunc' will return true for (or null)
if (!empty($sequence)) {
echo 'Found match: '.implode(',', $sequence);
} else {
echo 'Match not found';
}

Why does array_diff_uassoc compares keys whose value do not match

I just read that question about strange php behaviour, and even though I could research a bit more, I'm nowhere near understanding it.
I assume the reader has read the original question and is aware of OP's code block and sample, but in short, OP is trying to compare those two arrays, and while the result is good, the compare function seems to be called erratically:
$chomik = new chomik('a');
$a = array(5, $chomik, $chomik, $chomik);
$b = array($chomik, 'b', 'c', 'd');
array_diff_uassoc($a, $b, 'compare');
The documentation is a bit obscure... but it does state that:
The comparison function must return an integer less than, equal to, or greater than zero if the first argument is considered to be respectively less than, equal to, or greater than the second.
As I understand it, that means that the compare() function should be more like this:
function compare($a, $b) {
echo("$a : $b<br/>");
if($a === $b) return 0;
else if ($a > $b) return 1;
else return -1;
}
however this still gives very strange results, with even more "duplicates"
1 : 0
1 : 2
3 : 1
2 : 1
3 : 2
1 : 0
1 : 2
3 : 1
2 : 1
3 : 2
0 : 0
1 : 0
1 : 1
2 : 0
2 : 1
2 : 2
3 : 0
3 : 1
3 : 2
3 : 3
faced with many doubts, I read the compat php function, and the part where the check actually happens is interesting:
foreach ($args[0] as $k => $v) {
for ($i = 1; $i < $array_count; $i++) {
foreach ($args[$i] as $kk => $vv) {
if ($v == $vv) { // compare keys only if value are the same
$compare = call_user_func_array($compare_func, array($k, $kk));
if ($compare == 0) {
continue 3; // value should not be added to the result
}
}
}
}
$result[$k] = $v;
}
here's the actual source (per comment)
The way this code executes the compare function should not be outputting the result we see. Foreach is not able to move back and forth in the keys (AFAIK???), as seems to be the case in the order of the first key here:
1 : 2
3 : 1
2 : 1
moreover, it shouldn't check the keys if the value do not match, so why do all these are checked:
1 : 2
3 : 1
2 : 1
3 : 2
etc...
How can the topmost foreach() in the source code loop back and forth through the keys?!
Why are keys whose values do not match still compared?
Do foreach loops actually continue executing even when they've been continued?
Is this an example of concurrency? can call_user_func_array somehow be launched and actually execute the echo("$a : $b<br/>"); of the compare function not in the same order they were "launched"??
I believe you've pinpointed a bug, my friend. I just ran the code in the question you referenced, and sure enough, it compared keys for values that weren't the same. However, I wanted to test if the source code itself contained the mistake, so I added the official source for array_diff_uassoc this to the top his code, inside my own namespace:
<?php
namespace mine;
// Code obtained from https://pear.php.net/reference/PHP_Compat-latest/__filesource/fsource_PHP_Compat__PHP_Compat-1.6.0a3CompatFunctionarray_diff_uassoc.php.html
function array_diff_uassoc()
{
// Sanity check
$args = func_get_args();
if (count($args) < 3) {
user_error('Wrong parameter count for array_diff_uassoc()', E_USER_WARNING);
return;
}
// Get compare function
$compare_func = array_pop($args);
if (!is_callable($compare_func)) {
if (is_array($compare_func)) {
$compare_func = $compare_func[0] . '::' . $compare_func[1];
}
user_error('array_diff_uassoc() Not a valid callback ' .
$compare_func, E_USER_WARNING);
return;
}
// Check arrays
$array_count = count($args);
for ($i = 0; $i !== $array_count; $i++) {
if (!is_array($args[$i])) {
user_error('array_diff_uassoc() Argument #' .
($i + 1) . ' is not an array', E_USER_WARNING);
return;
}
}
// Compare entries
$result = array();
foreach ($args[0] as $k => $v) {
for ($i = 1; $i < $array_count; $i++) {
foreach ($args[$i] as $kk => $vv) {
if ($v == $vv) {
// echo ("$v\n");
// echo ("$vv\n");
// echo ("$k\n");
// echo ("$kk\n");
// die();
$compare = call_user_func_array($compare_func, array($k, $kk));
if ($compare == 0) {
continue 3;
}
}
}
}
$result[$k] = $v;
}
return $result;
}
class chomik {
public $state = 'normal';
public $name = 'no name';
public function __construct($name) {
$this->name = $name;
}
public function __toString() {
return $this->name . " - " . $this->state;
}
}
function compare($a, $b) {
echo("$a : $b\n");
if($a != $b) {
return 0;
}
else return 1;
}
$chomik = new chomik('a');
$a = array(5, $chomik, $chomik, $chomik);
$b = array($chomik, 'b', 'c', 'd');
array_diff_uassoc($a, $b, 'mine\compare');
This time, it only compared keys for values that were equal:
1 : 0
2 : 0
3 : 0
Strange, huh?
From this comment by powerlord:
Judging from the fact that the custom compare function asks you to return -1; 0; or 1, it seems like its doing a sort either before or at the same time as a comparison between the two arrays.
I was encouraged to go and read the actual php_array_diff() source, the registered function for array_diff_uassoc(), and discovered it uses the compare function a lot of times, which I wrongly interpreted as foreach going back and forth through the keys.
How can the topmost foreach() in the source code loop back and forth through the keys?!
it doesn't. It's just that the user-supplied function
diff_data_compare_func = php_array_user_compare;
is used multiple times to sort and evaluate the data.
[...]
zend_sort((void *) lists[i], hash->nNumOfElements,
sizeof(Bucket), diff_data_compare_func, (swap_func_t)zend_hash_bucket_swap);
[...]
while (Z_TYPE(ptrs[i]->val) != IS_UNDEF && (0 < (c = diff_data_compare_func(ptrs[0], ptrs[i])))) {
[...]
if (diff_data_compare_func(ptrs[0], ptr) != 0) {
[...]
Why are keys whose values do not match still compared?
It is true the compat pear code posted in the question hints that if the valus do not match, the compare function should not even be run, but php_array_diff() acts differently.
Do foreach loops actually continue executing even when they've been continued?
Is this an example of concurrency?
Nonsense. If I were you, I'd edit that out.

Recursive generators in PHP

Introduction
Since version 5.5 in PHP there's such great thing as generators. I will not repeat official manual page, but they are great thing for short definition of iterators. The most-known sample is:
function xrange($from, $till, $step)
{
if ($from>$till || $step<=0)
{
throw new InvalidArgumentException('Invalid range initializers');
}
for ($i = $from; $i < $till; $i += $step)
{
yield $i;
}
}
//...
foreach (xrange(2, 13, 3) as $i)
{
echo($i.PHP_EOL); // 2,5,8,11
}
and generator is actually not a function, but an instance of a concrete class:
get_class(xrange(1, 10, 1)); // Generator
The problem
Done with RTM stuff, now moving on to my question. Imagine that we want to create generator of Fibonacci numbers. Normally, to get those, we can use simple function:
function fibonacci($n)
{
if(!is_int($n) || $n<0)
{
throw new InvalidArgumentException('Invalid sequence limit');
}
return $n < 2 ? $n : fibonacci($n-1) + fibonacci($n-2);
}
var_dump(fibonacci(6)); // 8
Let's transform this into something, that holds sequence and not only it's last member:
function fibonacci($n)
{
if (!is_int($n) || $n<0)
{
throw new InvalidArgumentException('Invalid sequence limit');
}
if ($n<2)
{
return range(0, $n);
}
$n1 = fibonacci($n-1);
$n2 = fibonacci($n-2);
return array_merge($n1, [array_pop($n1)+array_pop($n2)]);
}
//...
foreach (fibonacci(6) as $i)
{
echo($i.PHP_EOL); // 0,1,1,2,3,5,8
}
We have now a function that returns array with full sequence
The question
Finally, the question part: how can I transform my latest fibonacci function so it will yield my values, not holding them in an array? My $n can be big, so I want to use benefits of generators, like in xrange sample. Pseudo-code will be:
function fibonacci($n)
{
if (!is_int($n) || $n<0)
{
throw new InvalidArgumentException('Invalid sequence limit');
}
if ($n<2)
{
yield $n;
}
yield fibonacci($n-2) + fibonacci($n-1);
}
But this, obviously, is crap since we can't handle with it like this way because recursion will cause object of class Generator and not int value.
Bonus: getting fibonacci sequence is just a sample for more general question: how to use generators with recursion in common case? Of course, I can use standard Iterator for that or re-write my function to avoid recursion. But I want to achieve that with generators. Is this possible? Does this worth efforts to use this such way?
So the issue I ran into when attempting to create a recursive generator function, is that once you go past your first depth level each subsequent yield is yielding to its parent call rather than the iteration implementation (the loop).
As of php 7 a new feature has been added that allows you to yield from a subsequent generator function. This is the new Generator Delegation feature: https://wiki.php.net/rfc/generator-delegation
This allows us to yield from subsequent recursive calls, which means we can now efficiently write recursive functions with the use of generators.
$items = ['what', 'this', 'is', ['is', 'a', ['nested', 'array', ['with', 'a', 'bunch', ['of', ['values']]]]]];
function processItems($items)
{
foreach ($items as $value)
{
if (is_array($value))
{
yield from processItems($value);
continue;
}
yield $value;
}
}
foreach (processItems($items) as $item)
{
echo $item . "\n";
}
This gives the following output..
what
this
is
is
a
nested
array
with
a
bunch
of
values
I've finally identified a real-world use for recursive generators.
I've been exploring QuadTree datastructures recently. For those not familiar with QuadTrees, they're a tree-based datastructure use for geospatial indexing, and allowing a fast search lookup of all points/locations within a defined bounding box.
Each node in the QuadTree represents a segment of the mapped region, and acts as a bucket in which locations are stored... but a bucket of restricted size. When a bucket overflows, the QuadTree node splits off 4 child nodes, representing the North-west, North-east, South-west and South-east areas of the parent node, and starts to fill those.
When searching for locations falling within a specified bounding box, the search routine starts at the top-level node, testing all the locations in that bucket; then recurses into the child nodes, testing whether they intersect with the bounding box, or are encompassed by the bounding box, testing each QuadTree node within that set, then recursing again down through the tree. Each node may return none, one or many locations.
I implemented a basic QuadTree in PHP, designed to return an array of results; then realised that it might be a valid use case for a recursive generator, so I implemented a GeneratorQuadTree that can be accessed in a foreach() loop yielding a single result each iteration.
It seems a much more valid use-case for recursive generators because it is a truly recursive search function, and because each generator may return none, one or many results rather than a single result. Effectively, each nested generator is handling a part of the search, feeding its results back up through the tree through its parent.
The code is rather too much to post here; but you can take a look at the implementation on github.
It's fractionally slower than the non-generator version (but not significantly): the main benefit is reduction in memory because it isn't simply returning an array of variable size (which can be a significant benefit depending on the number of results returned). The biggest drawback is the fact that the results can't easily be sorted (my non-generator version does a usort() on the results array after it's returned).
function fibonacci($n)
{
if($n < 2) {
yield $n;
}
$x = fibonacci($n-1);
$y = fibonacci($n-2);
yield $x->current() + $y->current();
}
for($i = 0; $i <= 10; $i++) {
$x = fibonacci($i);
$value = $x->current();
echo $i , ' -> ' , $value, PHP_EOL;
}
If you first want to make a generator you might as well use the iterative version of fibonacci:
function fibonacci ($from, $to)
{
$a = 0;
$b = 1;
$tmp;
while( $to > 0 ) {
if( $from > 0 )
$from--;
else
yield $a;
$tmp = $a + $b;
$a=$b;
$b=$tmp;
$to--;
}
}
foreach( fibonacci(10,20) as $fib ) {
print "$fib "; // prints "55 89 144 233 377 610 987 1597 2584 4181 "
}
Here's a recursive generator for combinations (order unimportant, without replacement):
<?php
function comb($set = [], $size = 0) {
if ($size == 0) {
// end of recursion
yield [];
}
// since nothing to yield for an empty set...
elseif ($set) {
$prefix = [array_shift($set)];
foreach (comb($set, $size-1) as $suffix) {
yield array_merge($prefix, $suffix);
}
// same as `yield from comb($set, $size);`
foreach (comb($set, $size) as $next) {
yield $next;
}
}
}
// let's verify correctness
assert(iterator_to_array(comb([0, 1, 2, 3, 4], 3)) == [
[0, 1, 2], [0, 1, 3], [0, 1, 4], [0, 2, 3], [0, 2, 4],
[0, 3, 4], [1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]
]);
foreach (comb([0, 1, 2, 3], 3) as $combination) {
echo implode(", ", $combination), "\n";
}
Outputs:
0, 1, 2
0, 1, 3
0, 2, 3
1, 2, 3
Same thing non-yielding.
Recently ran into a problem that needed 'recursive' generators or generator delegation. I ended up writing a little function that converts delegated generators calls into a single generator.
I turned it into a package so you could just require it with composer, or checkout the source here: hedronium/generator-nest.
Code:
function nested(Iterator $generator)
{
$cur = 0;
$gens = [$generator];
while ($cur > -1) {
if ($gens[$cur]->valid()) {
$key = $gens[$cur]->key();
$val = $gens[$cur]->current();
$gens[$cur]->next();
if ($val instanceof Generator) {
$gens[] = $val;
$cur++;
} else {
yield $key => $val;
}
} else {
array_pop($gens);
$cur--;
}
}
}
You use it like:
foreach (nested(recursive_generator()) as $combination) {
// your code
}
Checkout that link above. It has examples.
Short answer: recursive generators are simple. Example for walking through tree:
class Node {
public function getChildren() {
return [ /* array of children */ ];
}
public function walk() {
yield $this;
foreach ($this->getChildren() as $child) {
foreach ($child->walk() as $return) {
yield $return;
};
}
}
}
It's all.
Long answer about fibonacci:
Generator is something that is used with foreach (generator() as $item) { ... }. But OP wants fib() function to return int, but at the same time he wants it to return generator to be used in foreach. It is very confusing.
It is possible to implement recursive generator solution for fibonacci. We just need to put somewere inside fib() function a loop that will indeed yield each member of the sequence. As generator is supposed to be used with foreach, it looks really wierd, and I do not think it is effective, but here it is:
function fibGenerator($n) {
if ($n < 2) {
yield $n;
return;
}
// calculating current number
$x1 = fibGenerator($n - 1);
$x2 = fibGenerator($n - 2);
$result = $x1->current() + $x2->current();
// yielding the sequence
yield $result;
yield $x1->current();
yield $x2->current();
for ($n = $n - 3; $n >= 0; $n--) {
$res = fibGenerator($n);
yield $res->current();
}
}
foreach (fibGenerator(15) as $x) {
echo $x . " ";
}
I am offering two solution for Fibonacci number, with and without recursion:
function fib($n)
{
return ($n < 3) ? ($n == 0) ? 0 : 1 : fib($n - 1) + fib($n - 2);
}
function fib2()
{
$a = 0;
$b = 1;
for ($i = 1; $i <= 10; $i++)
{
echo $a . "\n";
$a = $a + $b;
$b = $a - $b;
}
}
for ($i = 0; $i <= 10; $i++)
{
echo fib($i) . "\n";
}
echo fib2();

Categories