Why does array_diff_uassoc compares keys whose value do not match - php

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.

Related

Sorting an array of strings by arbitrary numbers of substrings

I'm attempting to modify the OrderedImportsFixer class in php-cs-fixer so I can clean up my files the way I want. What I want is to order my imports in a fashion similar to what you'd see in a filesystem listing, with "directories" listed before "files".
So, given this array:
$indexes = [
26 => ["namespace" => "X\\Y\\Zed"],
9 => ["namespace" => "A\\B\\See"],
3 => ["namespace" => "A\\B\\Bee"],
38 => ["namespace" => "A\\B\\C\\Dee"],
51 => ["namespace" => "X\\Wye"],
16 => ["namespace" => "A\\Sea"],
12 => ["namespace" => "A\\Bees"],
31 => ["namespace" => "M"],
];
I'd like this output:
$sorted = [
38 => ["namespace" => "A\\B\\C\\Dee"],
3 => ["namespace" => "A\\B\\Bee"],
9 => ["namespace" => "A\\B\\See"],
12 => ["namespace" => "A\\Bees"],
16 => ["namespace" => "A\\Sea"],
26 => ["namespace" => "X\\Y\\Zed"],
51 => ["namespace" => "X\\Wye"],
31 => ["namespace" => "M"],
];
As in a typical filesystem listing:
I've been going at uasort for a while (key association must be maintained) and have come close. Admittedly, this is due more to desperate flailing than any sort of rigorous methodology. Not really having a sense of how uasort works is kind of limiting me here.
// get the maximum number of namespace components in the list
$ns_counts = array_map(function($val){
return count(explode("\\", $val["namespace"]));
}, $indexes);
$limit = max($ns_counts);
for ($depth = 0; $depth <= $limit; $depth++) {
uasort($indexes, function($first, $second) use ($depth, $limit) {
$fexp = explode("\\", $first["namespace"]);
$sexp = explode("\\", $second["namespace"]);
if ($depth === $limit) {
// why does this help?
array_pop($fexp);
array_pop($sexp);
}
$fexp = array_slice($fexp, 0, $depth + 1, true);
$sexp = array_slice($sexp, 0, $depth + 1, true);
$fimp = implode(" ", $fexp);
$simp = implode(" ", $sexp);
//echo "$depth: $fimp <-> $simp\n";
return strnatcmp($fimp, $simp);
});
}
echo json_encode($indexes, JSON_PRETTY_PRINT);
This gives me properly sorted output, but with deeper namespaces on the bottom instead of the top:
{
"31": {
"namespace": "M"
},
"12": {
"namespace": "A\\Bees"
},
"16": {
"namespace": "A\\Sea"
},
"3": {
"namespace": "A\\B\\Bee"
},
"9": {
"namespace": "A\\B\\See"
},
"38": {
"namespace": "A\\B\\C\\Dee"
},
"51": {
"namespace": "X\\Wye"
},
"26": {
"namespace": "X\\Y\\Zed"
}
}
I'm thinking I may have to build a separate array for each level of namespace and sort it separately, but have drawn a blank on how I might do that. Any suggestions for getting the last step of this working, or something completely different that doesn't involve so many loops?
We divide this into 4 steps.
Step 1: Create hierarchical structure from the dataset.
function createHierarchicalStructure($indexes){
$data = [];
foreach($indexes as $d){
$temp = &$data;
foreach(explode("\\",$d['namespace']) as $namespace){
if(!isset($temp[$namespace])){
$temp[$namespace] = [];
}
$temp = &$temp[$namespace];
}
}
return $data;
}
Split the namespaces by \\ and maintain a $data variable. Use & address reference to keep editing the same copy of the array.
Step 2: Sort the hierarchy in first folders then files fashion.
function fileSystemSorting(&$indexes){
foreach($indexes as $key => &$value){
fileSystemSorting($value);
}
uksort($indexes,function($key1,$key2) use ($indexes){
if(count($indexes[$key1]) == 0 && count($indexes[$key2]) > 0) return 1;
if(count($indexes[$key2]) == 0 && count($indexes[$key1]) > 0) return -1;
return strnatcmp($key1,$key2);
});
}
Sort the subordinate folders and use uksort for the current level of folders. Vice-versa would also work. If both 2 folders in comparison have subfolders, compare them as strings, else if one is a folder and another is a file, make folders come above.
Step 3: Flatten the hierarchical structure now that they are in order.
function flattenFileSystemResults($hierarchical_data){
$result = [];
foreach($hierarchical_data as $key => $value){
if(count($value) > 0){
$sub_result = flattenFileSystemResults($value);
foreach($sub_result as $r){
$result[] = $key . "\\" . $r;
}
}else{
$result[] = $key;
}
}
return $result;
}
Step 4: Restore the initial data keys back and return the result.
function associateKeys($data,$indexes){
$map = array_combine(array_column($indexes,'namespace'),array_keys($indexes));
$result = [];
foreach($data as $val){
$result[ $map[$val] ] = ['namespace' => $val];
}
return $result;
}
Driver code:
function foldersBeforeFiles($indexes){
$hierarchical_data = createHierarchicalStructure($indexes);
fileSystemSorting($hierarchical_data);
return associateKeys(flattenFileSystemResults($hierarchical_data),$indexes);
}
print_r(foldersBeforeFiles($indexes));
Demo: https://3v4l.org/cvoB2
I believe the following should work:
uasort($indexes, static function (array $entry1, array $entry2): int {
$ns1Parts = explode('\\', $entry1['namespace']);
$ns2Parts = explode('\\', $entry2['namespace']);
$ns1Length = count($ns1Parts);
$ns2Length = count($ns2Parts);
for ($i = 0; $i < $ns1Length && isset($ns2Parts[$i]); $i++) {
$isLastPartForNs1 = $i === $ns1Length - 1;
$isLastPartForNs2 = $i === $ns2Length - 1;
if ($isLastPartForNs1 !== $isLastPartForNs2) {
return $isLastPartForNs1 <=> $isLastPartForNs2;
}
$nsComparison = $ns1Parts[$i] <=> $ns2Parts[$i];
if ($nsComparison !== 0) {
return $nsComparison;
}
}
return 0;
});
What it does is:
split namespaces into parts,
compare each part starting from the first one, and:
if we're at the last part for one and not the other, prioritize the one with the most parts,
otherwise, if the respective parts are different, prioritize the one that is before the other one alphabetically.
Demo
Here's another version that breaks the steps down further that, although it might not be the most optimal, definitely helps my brain think about it. See the comments for more details on what is going on:
uasort(
$indexes,
static function (array $a, array $b) {
$aPath = $a['namespace'];
$bPath = $b['namespace'];
// Just in case there are duplicates
if ($aPath === $bPath) {
return 0;
}
// Break into parts
$aParts = explode('\\', $aPath);
$bParts = explode('\\', $bPath);
// If we only have a single thing then it is a root-level, just compare the item
if (1 === count($aParts) && 1 === count($bParts)) {
return $aPath <=> $bPath;
}
// Get the class and namespace (file and folder) parts
$aClass = array_pop($aParts);
$bClass = array_pop($bParts);
$aNamespace = implode('\\', $aParts);
$bNamespace = implode('\\', $bParts);
// If the namespaces are the same, sort by class name
if ($aNamespace === $bNamespace) {
return $aClass <=> $bClass;
}
// If the first namespace _starts_ with the second namespace, sort it first
if (0 === mb_strpos($aNamespace, $bNamespace)) {
return -1;
}
// Same as above but the other way
if (0 === mb_strpos($bNamespace, $aNamespace)) {
return 1;
}
// Just only by namespace
return $aNamespace <=> $bNamespace;
}
);
Online demo
I find no fault with Jeto's algorithmic design, but I decided to implement it more concisely. My snippet avoids iterated function calls and arithmetic in the for() loop, uses a single spaceship operator, and a single return. My snippet is greater than 50% shorter and I generally find it easier to read, but then everybody thinks their own baby is cute, right?
Code: (Demo)
uasort($indexes, function($a, $b) {
$aParts = explode('\\', $a['namespace']);
$bParts = explode('\\', $b['namespace']);
$aLast = count($aParts) - 1;
$bLast = count($bParts) - 1;
for ($cmp = 0, $i = 0; $i <= $aLast && !$cmp; ++$i) {
$cmp = [$i === $aLast, $aParts[$i]] <=> [$i === $bLast, $bParts[$i]];
}
return $cmp;
});
Like Jeto's answer, it iterates each array simultaneously and
checks if either element is the last element of the array, if so it is bumped down the list (because we want longer paths to win tiebreakers);
if neither element is the last in its array, then compare the elements' current string alphabetically.
The process repeats until a non-zero evaluation is generated.
Since duplicate entries are not expected to occur, the return value should always be -1 or 1 (never 0)
Note, I am halting the for() loop with two conditions.
If $i is greater than the number of elements in $aParts (only) -- because if $bParts has fewer elements than $aParts, then $cmp will generate a non-zero value before a Notice is triggered.
If $cmp is a non-zero value.
Finally, to explain the array syntax on either side of the spaceship operator...The spaceship operator will compare the arrays from left to right, so it will behave like:
leftside[0] <=> rightside[0] then leftside[1] <=> rightside[1]
Making multiple comparisons in this way does not impact performance because there are no function calls on either side of the <=>. If there were function calls involved, it would be more performant to make individual comparisons in a fallback manner like:
fun($a1) <=> fun($b1) ?: fun($a2) <=> fun($b2)
This way subsequent function calls are only actually made if a tiebreak is necessary.

Compare the keys and values of three arrays with "array_diff_ukey"

Can somebody explain, what is 1 and -1 in this code: ($a>$b)?1:-1; ?
I know the Array ( [c] => blue ) is returning because the key c is not exist in $a2 and key_compare_func need to return number smaller, equal or bigger then 0.
But I don't understand, how I get the Array ( [c] => blue ), when the key_compare_func returns 0, 1 and -1:
function myfunction($a,$b) {
if ($a === $b) {
return 0;
}
return ($a > $b) ? 1 : -1;
}
$a1=array("a"=>"red","b"=>"green","c"=>"blue");
$a2=array("a"=>"blue","b"=>"black","e"=>"blue");
$result=array_diff_ukey($a1,$a2,"myfunction");
As you can see in array-diff-ukey documentation the "key_compare_func" need to return number smaller, equal or bigger then 0. the numbers 1 and -1 are just example for this results.
In your case you can simply use strcmp as it return the same logic.
You have Array ( [c] => blue ) in the return because the key c is not exist in the $a2 array as it say:
Compares the keys from array1 against the keys from array2 and returns the difference. This function is like array_diff() except the comparison is done on the keys instead of the values.
Edited
Specifically in array-diff-ukey you only need the return 0 because that the way this function is decided the keys are the same so in your example you can define it as:
function myfunction($a,$b) {
if ($a === $b)
return 0;
return 1; // or -1 or 3 or -3 **just not 0**
}
Consider that as the logic behind array-diff-ukey:
array function array-diff-ukey($a1, $a2, $compareFunction) {
$arr = array(); // init empty array
foreach($a1 as $key1 => $value1) { // for each key in a1
$found = false;
foreach($a1 as $key2 => $value2) { //search for all keys in a2
if ($compareFunction($key1, $key2) == 0)
$found = true; // found a key with the same
}
if ($found === false) // add the element only if non is found
$arr[$key1] = $value1;
}
return $arr;
}
If ($a>$b) is true (right after the ?) - you return 1. else (right after the :) will return -1.
It's a short way of writing this:
if ($a>$b) {
return 1;
} else {
return -1;
}
It is the ternary operator in PHP. You can say it as shorthand If/Else. Here is an example:
/* most basic usage */
$var = 5;
$var_is_greater_than_two = ($var > 2 ? true : false); // if $var greater than 2
// return true
// else false
If it is being difficult for you to understand, you can change it with:
if ($a===$b)
{
return 0;
}
else if($a > $b)
{
return 1;
}
else
{
return -1;
}

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();

PHP Array & Pointer Confusion

Let's say I have a function like this:
function z($zzz){
for($c=0;$c<5;$c++){
$zzz[$c]= 10;
//more and more codes
}
}
I want to write a loop so that
the 1st time the function is executed, argument $array is passed
the 2nd time : argument $array[0] is passed
while the 3rd time : argument $array[1] is passed
.....
and the 12th time : argument $array[0][0] is passed
This is what comes to my mind:
$a = -1;
$b = -1;
$array = array();
while($a<10){
while($b<10){
z($array);
$b++;
$array= &$array[$b];
}
$a++;
$array= &$array[$a];
}
I've tried it but it didn't work..
I would appreciate if someone can provide a solution..
If z() is supposed to change the passed array, your function definition should be:
function z(&$zzz)
$a = 0;
while ($a < 99) // <-- edit as applicable
{
$b = 0
while ($b < 12)
{
if ($b == 0)
{
Z($array[$a]);
} else
{
Z($array[$a][$b]);
}
$b++;
}
$a++;
}
And as stated by Jack you need to pass the $array variable by reference for update. Not too sure what that function is trying to achieve though. If you need to fill an array with a predermined dimension, maybe array_fill could be more useful.
http://www.php.net/manual/en/function.array-fill.php
function z(&$zzz){
for($c=0;$c<5;$c++){
$zzz[$c]= 10;
//more and more codes
}
}

How to loop through arrays of different lengths and run a function inside the loop

I'm trying to create a function that will loop through an array of various lengths. During the loop, a function is run to see if the immediately prior item (the item at current key minus 1) matches what is in the array.
Here are two examples of arrays:
$terms1 = array(
0 => 'MEL',
1 => 'Appliances',
2 => 'Clothes Dryers',
3 => 'Clothes dryers - electric'
);
$terms2 = array(
0 => 'Clothes Dryers',
1 => 'Clothes dryers - electric'
);
And here is the function to be run within the loop... this function will return a value and then I will compare that to what is in the array in the immediately prior location (current key minus 1). This pulls from a db.
getParent($terms1[3]); //Would output the value I want to compare to $terms1[2]
I've tried something like this:
$fail = null;
foreach(array_reverse($terms1, true) as $key => $value){
if($key > 0){
$priorkey = $key - 1;
if(getParent($terms1[$key]) != $terms1[$priorkey]){
$fail = true;
}
}
}
return $fail;
I think I need a recursive function... any help or nudges in the right direction would be appreciated.
$prev = null;
foreach ($terms1 as $v) {
if ($prev == getParent($v)) {
$fail = true;
break;
}
$prev = $v;
}
I don't understand why your code doesn't work, but if you add a break; after $fail = true; it will run faster and return the same result. There's no need to check the rest after the first failure.

Categories