Multidimesional array pointers - php
I'm building an array piece by piece following a specific pattern.
For example, I have this string <val0=0, val1=<val2=2, val3=<val4=4>>, val5=5> and I need to translate it to an associative array. So every time I find < I have to create a new array and store the following elements until the next >.
The string above should result in something like this:
Array
(
[val0] => 0
[val1] => Array
(
[val2] => 2
[val3] => Array
(
[val4] => 4
)
)
[val5] => 5
)
Everything is working fine for non-multidimensional arrays using str_split to break the string in pieces and iterating over them in a for loop but I'm having difficulties to find a workaround every time there is a nesting array in the string.
What I need is a way to have a pointer to the last created array inside the main array.
Is there a way to store an array pointer reference in a variable so I could do this:
print_r($MULTIARRAY['val1']['val3']);
// prints: array()
$pointer = pointer($MULTIARRAY['val1']['val3']);
$pointer[] = 'AAA';
$pointer[] = 'BBB';
print_r($MULTIARRAY['val1']['val3']);
// prints: array(
// [0] => AAA
// [1] => BBB
//)
Here you go, it's called reference
$a[1][22] = array();
$pointer = &$a[1][22];
$pointer[] = 3;
$pointer[] = 4;
print_r($a);
Related
How to split array into all possible combinations
How can I loop through an array, split it into two arrays and run a function for each possible combination? Order does not matter. // Original array $array = array('a','b','c','d','e'); // Result 1 array('a'); array('b','c','d','e'); // Result 2 array('a', 'b'); array('c','d','e'); // Result 3 array('a', 'c'); array('b','d','e'); And so on...
Here's my take at this: <?php $ar = ['a','b','c','d','e']; function permuteThrough($ar, $callback, $allowMirroredResults = true, $mode = 'entry', $desiredLeftCount = null, $left = [], $right = []) { switch($mode) { case 'entry': // Logic: // Determine how many elements we're gonna put into left array $len = $allowMirroredResults ? count($ar) : floor(count($ar)/2); for($i=0; $i <= $len; $i++) { call_user_func(__FUNCTION__, $ar, $callback, $allowMirroredResults, 'permute',$i); } break; case 'permute': // We have nothing left to sort, let's tell our callback if( count($ar) === 0 ) { $callback($left,$right); break; } if( count($left) < $desiredLeftCount ) { // Note: PHP assigns arrays as clones (unlike objects) $ar1 = $ar; $left1 = $left; $left1[] = current(array_splice($ar1,0,1)); call_user_func(__FUNCTION__, $ar1, $callback, $allowMirroredResults, 'permute', $desiredLeftCount, $left1, $right); } // This check is needed so we don't generate permutations which don't fulfill the desired left count $originalLength = count($ar) + count($left)+count($right); if( count($right) < $originalLength - $desiredLeftCount ) { $ar2 = $ar; $right1 = $right; $right1[] = current(array_splice($ar2,0,1)); call_user_func(__FUNCTION__, $ar2, $callback, $allowMirroredResults, 'permute', $desiredLeftCount, $left, $right1); } break; } } function printArrays($a,$b) { echo '['.implode(',',$a).'],['.implode(',',$b)."]\n"; } permuteThrough($ar, 'printArrays', true); // allows mirrored results /* [],[a,b,c,d,e] [a],[b,c,d,e] [b],[a,c,d,e] [c],[a,b,d,e] [d],[a,b,c,e] [e],[a,b,c,d] [a,b],[c,d,e] [a,c],[b,d,e] [a,d],[b,c,e] [a,e],[b,c,d] [b,c],[a,d,e] [b,d],[a,c,e] [b,e],[a,c,d] [c,d],[a,b,e] [c,e],[a,b,d] [d,e],[a,b,c] [a,b,c],[d,e] [a,b,d],[c,e] [a,b,e],[c,d] [a,c,d],[b,e] [a,c,e],[b,d] [a,d,e],[b,c] [b,c,d],[a,e] [b,c,e],[a,d] [b,d,e],[a,c] [c,d,e],[a,b] [a,b,c,d],[e] [a,b,c,e],[d] [a,b,d,e],[c] [a,c,d,e],[b] [b,c,d,e],[a] [a,b,c,d,e],[] */ echo "==============\n"; // output separator permuteThrough($ar, 'printArrays', false); // doesn't allow mirrored results /* [],[a,b,c,d,e] [a],[b,c,d,e] [b],[a,c,d,e] [c],[a,b,d,e] [d],[a,b,c,e] [e],[a,b,c,d] [a,b],[c,d,e] [a,c],[b,d,e] [a,d],[b,c,e] [a,e],[b,c,d] [b,c],[a,d,e] [b,d],[a,c,e] [b,e],[a,c,d] [c,d],[a,b,e] [c,e],[a,b,d] [d,e],[a,b,c] */ My permuteThrough function takes three arguments. The array, the callback, and an optional boolean indicating whether or not you want to allow mirrored results. The logic is pretty straight forward: First decide how many elements we want to put into our left array. Then Recursively call the function like so: Our working array with the remaining elements to sort. Shift an element off and put it into the left array. The result gets sent to another layer of recursion. Shift an element off and put it into the right array. The result gets sent to another layer of recursion. If there's no elements left to shift off, call our callback with the resulting left and right arrays. Finally, make sure we're not going over the desired left array element size determined by the for loop at the beginning and make sure the right size doesn't become so big that it makes the desired left size impossible to satisfy. Normally this would be done by two separate functions. One to decide how many elements should go into the left array. And one to be used for recursion. But instead I tossed in another argument to the recursion function to eliminate the need for a separate function so it can all be handled by the same recursive function.
This is the best I can do: class Combos { /** * getPossible then getDivide * * #param array $input * #return array */ public function getPossibleAndDivided( array $input ) { return $this->getMultiShiftAndDivided( $this->getPossible( $input ) ); } /** * return all possible combinations of input * * #param array $input * #return array */ public function getPossible( array $inputs ) { $result = []; if ( count( $inputs ) <= 1 ) { $result = $inputs; } else { $result = array(); foreach($inputs as $input){ //make it an array $first = [ $input ]; //get all inputs not in first $remaining = array_diff( $inputs, $first ); //send the remaining stuff but to ourself $combos = $this->getPossible( $remaining );//recursive //iterate over the above results (from ourself) foreach( $combos as $combo ) { $last = $combo; //convert to array if it's not if( !is_array( $last ) ) $last = [ $last ]; //merge them $result[] = array_merge( $first, $last ); } } } return $result; } /** * shift and divide a multi level array * * #param array $array * #return array */ public function getMultiShiftAndDivided( array $mArray ) { $divided = []; foreach ( $mArray as $array ) { $divided = array_merge($divided, $this->getShiftAndDivided( $array )); } return $divided; } /** * shift and divide a single level array * * #param array $array * #return array */ public function getShiftAndDivided( array $array ) { $divided = []; $array1 = []; while( count( $array ) ){ $array1[] = array_shift( $array ); $divided[] = [ $array, $array1 ]; } return $divided; } } How it works Top level, I don't want to get into all the details. This is basically a 2 step process or at least it was easier to figure it out that way. I built it in a class to keep everything neat. It also allows better unit testing and re-usability. This requires 2 operations, or at least it was easier for me to do it in 2 instead of as 1. They are combined in this method public function getPossibleAndDivided( array $input ) { return $this->getMultiShiftAndDivided( $this->getPossible( $input ) ); } This is the main reason I made it a class, to keep everything packaged nicely together. I would call this a wrapper method. Step One $Combos->getPossible(array $input) if only one item remains, return $inputs. this is all the combinations it can ever have (its just a single element after all). else It iterates thought $inputs with foreach as $input Wraps $input as an array named $first this is a single element from $inputs Gets the remaining elements in $inputs in a non-destructive way as $remaining using array_diff we need both elements as arrays (see aabove) recursive call to $this->getPossible($remaining) (see #1) and returns as $combos Iterates over $combos foreach as $combo $combo is assigned to $last and turned into an array, if its not sometimes combo is an array with several elements sometimes is a single element. It depends on the recursive call We add to our result set the merger of $first and $last we need both as arrays so we can merge, without causing nesting End Anything in $result is returned. This basically rotates though all the combinations of the array and returns them in an array like this: Array ( [0] => Array ( [0] => a [1] => b [2] => c [3] => d [4] => e ) [1] => Array ( [0] => a [1] => b [2] => c [3] => e [4] => d ) [2] => Array ( [0] => a [1] => b [2] => d [3] => c [4] => e ) ... [117] => Array ( [0] => e [1] => d [2] => b [3] => c [4] => a ) [118] => Array ( [0] => e [1] => d [2] => c [3] => a [4] => b ) [119] => Array ( [0] => e [1] => d [2] => c [3] => b [4] => a ) ) Yes it returns 119 results, no I will not include them all. Step Two Don't forget the above output is a multi-dimensional array (this is important below). $Combos->getMultiShiftAndDivided(array $mArray) This method is intended to be used with multi-dimensional arrays (hence its name). We get this from "Step 1". Its basically a wrapper for $Combos->getShiftAndDivided($array) Iterates over $mArray foreach as $array It calls $this->getShiftAndDivided($array) returns and merges with $divided. there was no need to store the results, so I didn't waste a variable on it. Output example: $input = array(array('a','b','c','d','e')); print_r($combos->getMultiShiftAndDivided($input)); Array ( [0] => Array ( [0] => Array ( [0] => b [1] => c [2] => d [3] => e ) [1] => Array ( [0] => a ) ) .... [4] => Array ( [0] => Array ( ) [1] => Array ( [0] => a [1] => b [2] => c [3] => d [4] => e ) ) ) $Combos->getShiftAndDivided(array $array) This method is intended to be used single level arrays. Loops as long as the count of $array is more then 0, while loop $array1 gets the first element from $array added and that element is removed from $array (destructively) we store both $array and $array1 in our results $divided this records there current "state" at that moment when there are no more items in $array we return our results Output example: $input = array('a','b','c','d','e'); print_r($combos->getShiftAndDivided($input)); Array ( [0] => Array ( [0] => Array ( [0] => b [1] => c [2] => d [3] => e ) [1] => Array ( [0] => a ) ) .... [4] => Array ( [0] => Array ( ) [1] => Array ( [0] => a [1] => b [2] => c [3] => d [4] => e ) ) ) Basically this shifts the elements of a single array to two result arrays and records their state on each shift. I made it 2 functions so that it could be tested easier and be re-used easier. Another issue is its kind of hard to check for multi-dimensional arrays. I know how to do it, but I didn't feel like it because it's kind of ugly and there is a better way. I say that because its possible to use a one level array in what is getMultiShiftAndDivided and it wouldn't give you what you would expect. Probably you would get an error like this: //I say probably, but I actually test it ... lol Warning: array_shift() expects parameter 1 to be array Which could be confusing, one could think the code is buggered. So by having the second method call with a type set into it getShiftAndDivided( array $array ). When the wrapping method tries to call this with a string its going to blow up, but in a better way: Catchable fatal error: Argument 1 passed to Combos::getShiftAndDivided() must be of the type array, string given Hopefully that makes sense, it's something I always try to do in cases like this. It just makes life easier in the long run. Both function return the data in the same format, which is convenient (your welcome). Summary So the sum of what this does, is find all the combinations of our input, then it takes those and breaks each one into shifted and divided up array. There for it stands to reason that we will have all the possible combinations divided up into 2 arrays anyway possible. Because that is pretty much exactly what I said. Now I am not 100% it does that, you can check them if you want, it returns like 599 elements at the end. So good luck on that, I would suggest testing just the results of $combos->getPossible($input). If that has all the combinations like it should, then this will have all that it needs. I am not sure if it returns duplicates, I don't think that was specified. But I didn't really check it. You can call the main method like this: $input = array('a','b','c','d','e'); print_r((new Combos)->getPossibleAndDivided($input)); Test It! P.S. I spell breaks as brakes, but I can write code this, go figure...
pull dynamic element in single array
I am trying to pull dynamic element in single array but result is not showing properly Ex: $array =array(); $element="'abc1','abc2'"; $array=array('abc',$element); //I want result like that: array[ [0]=>abc, [1]=>abc1, [2]=>ab ]
If you need to parse a string of elements to an array you can use one of the csv functions. You may then merge your arrays. $array = array('abc'); $string_elements = "'abc1','abc2'"; $array_elements = str_getcsv($string_elements, ',', "'"); $array = array_merge($array, $array_elements); var_export($array); Output: array ( 0 => 'abc', 1 => 'abc1', 2 => 'abc2', ) Alternatively to add each array element to the end of another array you can push them like so using a splat: array_push($array, ...$array_elements);
According to my understanding, $element is storing string not an array so even if you try to get output it will display in following format Array ( [0] => abc [1] => 'abc1','abc2' ) Instead of this you can store array in $element variable and use array_merge() function to get required output e.g. $element = array('abc1','abc2'); $array = array('abc'); $result = array_merge($array,$element); // Output Array ( [0] => abc [1] => abc1 [2] => abc2 )
I honestly have no clue how to formulate this pass-by-reference conundrum
So here's what I see this code doing: An array is made A loop iterates 10 times A new array is created A reference to this new array is saved in the first array 10 arrays now reside in the original array with values 0, 1, 2, 3... What really happens: WTF? Code: <?php header('Content-type: text/plain'); $arrays = array(); foreach(range(0, 10) as $i) { $arr = array(); $arr[0] = $i; $arrays[] = &$arr; } print_r($arrays); Output: Array ( [0] => Array ( [0] => 10 ) [1] => Array ( [0] => 10 ) [2] => Array ( [0] => 10 ) [3] => Array ( [0] => 10 ) [4] => Array ( [0] => 10 ) [5] => Array ( [0] => 10 ) [6] => Array ( [0] => 10 ) [7] => Array ( [0] => 10 ) [8] => Array ( [0] => 10 ) [9] => Array ( [0] => 10 ) [10] => Array ( [0] => 10 ) ) I would like to know exactly why apparently only the 10th array is referred to ten times, instead of every instance of the arrays being referred to one each. Also if somebody who isn't just thinking WTF (like me) would like to edit the title, feel free to do so.
The line $arr = array(); does not create a new array but rather assigns an empty array to the already existing reference. If you want the variable name to "point" to a different array in memory, you have to unset() (or "disconnect") it first before assigning an empty array to it: foreach(range(0, 10) as $i) { unset($arr); $arr = array(); $arr[0] = $i; $arrays[] = &$arr; } This is because the only operations that can make a variable point to something else is the reference assignment (=&) and the unset().
What happens here is that by inserting a reference to $arr inside $arrays, you are effectively adding the exact same array 10 times -- and each reference to the array has the value last assigned to it (i.e. the one produced when $i is 10). It's not clear what you intend to achieve by inserting a reference in each iteration -- either removing the & or putting unset($arr) at the beginning of the loop would give you the expected behavior. What are you trying to accomplish?
Think about it this way. You do $arrays[] = &$arr; 10 times. This stores a reference to the local variable $arr 10 times. Since it's the same variable (the variable's scope is the entire function), it stores the same reference all 10 times. Thus, why should you expect the 10 elements to be different? The reference you are storing has nothing to do with the value of $arr; it just has to do with the variable $arr. When you print the reference it prints the value of $arr at that time.
It's because you're storing a reference to the array that $arr points to in the array. And you keep overwriting that array with the latest number. All references in $arr will point to the same array in the end. I don't know what you expect to get out of this in the end, but getting rid of & should fix this behavior.
PHP string to nested / multidimensional array
I have this example php string: $string = "#[item_1][door] #[mozart][grass] = yes #[mozart][green] = no #[mozart][human] #[blue][movie]=yes #[item_1][beat] = yes #[item_1][music] = no "; now $string idented just to easy view: #[item_1][door] #[mozart][grass] = yes #[mozart][green] = no #[mozart][human] #[blue][movie]=yes #[item_1][beat] = yes #[item_1][music] = no I want to know how can i get this string ( or other string following this style ) and transform in an array that looks like: Array ( [item_1] => Array ( [door] => Array ( [mozart] => Array ( [grass] => yes [green] => no [human] => Array ( [blue] => Array ( [movie] => yes ) ) ) ) [beat] => yes [music] => no ) ) What i tried I tried to use and recursive function to create an nested array but i can't have access to the array pointer ( in deep levels ) in recursive functions.. don't know why.. maybe is the wrong patch to the answer. thank you,
OK, I hope you still need this, because I wasted more time than I'd like to admin getting this right :) Basically, my approach was to first manipulate the string into the format [set][of][keys]=value, and then loop through the string of keys and comparing them with the last set of keys to create the correct key hierarchy. I used eval because it's easier, but you can write a replacement function if you can't stomach seeing that function in your code: //FIRST WE GET THE STRING INTO EASIER TO WORK WITH CHUNKS $original_string = "#[item_1][door] #[mozart][grass] = yes #[mozart][green] = no #[mozart][human] #[blue][movie]=yes #[item_1][beat] = yes #[item_1][music] = no "; $cleaned_string = str_replace('] #[','][',$original_string); /* This results in clusters of keys that equal a value: #[item_1][door][mozart][grass] = yes #[mozart][green] = no #[mozart][human][blue][movie]=yes #[item_1][beat] = yes #[item_1][music] = no OR (with line breaks for clarity): #[item_1][door][mozart][grass] = yes #[mozart][green] = no #[mozart][human][blue][movie]=yes #[item_1][beat] = yes #[item_1][music] = no */ //break it up into an array: $elements = explode('#',$cleaned_string); //create a variable to compare the last string to $last_keys = ""; //and another that will serve as our final array $array_of_arrays = array(); //now loop through each [item_1][door][mozart][grass] = yes,[mozart][green] = no, etc foreach($elements as $element){ if ($element==""){continue;} //skip the first empty item //break the string into [0] = group of keys and [1] the value that terminates the string //so [item_1][door][mozart][grass] = yes BECOMES [item_1][door][mozart][grass], AND yes $pieces = explode('=',str_replace(array('[',']'),array("['","']"),trim($element))); //now compare this set of keys to the last set of keys, and if they overlap merge them into a single key string $clean_keys = combine_key_strings($pieces[0],$last_keys); //set the new key string the value for the next comparison $last_keys = $clean_keys; //and (ugly, I know) we use an eval to convert "[item_1][door][mozart][grass]='yes'" into a properly keyed array eval("\$array_of_arrays".$clean_keys." = '".trim($pieces[1])."';"); } //now dump the contents print_r($array_of_arrays); //THIS FUNCTION COMPA function combine_key_strings($new,$old){ //get the key that starts the newer string $new_keys = explode('][',$new); $first_key = $new_keys[0].']'; //see if it appears in the last string $last_occurance = strrpos ($old,$first_key); //if so, merge the two strings to create the full array keystring if (is_int($last_occurance)){ return substr($old,0,$last_occurance).$new; } return $new; } This should spit out your correctly nested array: Array ( [item_1] => Array ( [door] => Array ( [mozart] => Array ( [grass] => yes [green] => no [human] => Array ( [blue] => Array ( [movie] => yes ) ) ) ) [beat] => yes [music] => no ) ) Good night!
Sorting a 3 dimensional array
I've got an array like this Array ( [0] => Array ( [0] => Array ( [szam] => 8 [index] => 0 ) ) [1] => Array ( [0] => Array ( [szam] => 1 [index] => 0 ) [1] => Array ( [szam] => 7 [index] => 1 ) ) I thought that my last cmp will work fine function maxSzerintCsokkeno($item1,$item2) { if ($item1['szam'] == $item2['szam']) return 0; return ($item1['szam'] < $item2['szam']) ? 1 : -1; } with foreach foreach ($tomb as $kulcs => $adat) usort($adat,"maxSzerintCsokkeno"); but it dosen't do anything, advise?
foreach ($tomb as $kulcs => $adat) usort($adat,"maxSzerintCsokkeno"); This only sorts the subarray array $adat. And this only exists temporarily until foreach loops over the next one. The lazy option here would be to use a reference: foreach ($tomb as & $adat) usort($adat,"maxSzerintCsokkeno"); Notice the &. This way the modification on $adat will be applied directly in the parent array.
You're sorting a temporary variable, meaning the changes are not applied. The following should work for you: for($i = 0, $length = count($tomb); $i < $length; $i++) { usort($tomb[$i], "maxSzerintCsokkeno"); }
When iterating through the foreach loop, the key and value variables ($kulcs and $adat in your code) are copies of the actual values in the array. Like Tim Cooper said, you are actually sorting a copy of the original value. You can also pass the value by reference in your foreach loop. This means that you will be modifying the original value: foreach ($tomb as $kulcs => &$adat) usort($adat,"maxSzerintCsokkeno");