Is it possible for an array to know its parent? - php

Consider this array:
$super = [
"first" => ["first1","first2"],
"second" => ["second1","second2"]
];
Now let's grab a reference to the subarray:
$sub = $super["second"];
How can I reference the super array from the sub array?
I'm looking for a way to do something like this:
var_dump($sub[../"first"]);
array (size=2)
0 => string 'first1' (length=6)
1 => string 'first2' (length=6)

NO, PHP Arrays are implemented as ordered hashmaps. Which means that every key in the array gets hashed and maps directly to a value. That value could be another array, sure, but the map does not go in reverse. So a value cannot map back to a key in PHP or in the implementation of any typical hashmap. The hash only goes one way.
So in $arr = ["foo" => ["bar"]] the key "foo" in the array $arr will map to the value ["bar"], which happens to be another array. But there is no way to go in reverse order (i.e. map the value ["bar"] back to the key "foo" in $arr).
If you want this kind of co-recursive relationship, like a tree or graph, it's actually easier to accomplish with objects, because objects don't have the same by copy-by-value semantics that arrays do.
$obj = new stdClass;
$obj->first = new stdClass;
$obj->second = new stdClass;
$obj->first->parent = $obj;
$obj->second->parent = $obj;
$obj->first->value = ["first1", "first2"];
$obj->second->value= ["second1","second2"];
$first = $obj->first; // now I can ask it for its parent
$second = $first->parent->second->value;
var_dump($second);
You get
array(2) {
[0]=>
string(7) "second1"
[1]=>
string(7) "second2"
}
Now it doesn't actually matter if you change the value from $first or $obj. The object remains intact, because PHP objects aren't stored directly in the variable. They are stored in a container that's abstracted away from userland and the variable merely holds a reference to this container. So $first and $obj still both point to the same object.
So continuing from the example above, if we tried something like this ...
$second = $first->parent->second;
$first->parent->second->value = ["I changed you!", "I changed you too!"];
// This magically gets the right value
var_dump($second->value, $obj->second->value);
array(2) {
[0]=>
string(14) "I changed you!"
[1]=>
string(18) "I changed you too!"
}
array(2) {
[0]=>
string(14) "I changed you!"
[1]=>
string(18) "I changed you too!"
}

You can only know what's the parent index of an index by creating custom function like this :
function get_parent($key, $arr){
$index = 0;
foreach($arr as $keyCur => $val){
if($keyCur === $key){
$ret = [];
$ret['indexOfKey'] = $index;
$ret['valueOfKey'] = $val;
return $ret;
}
$index++;
}
return false;
}

Related

Dynamically inserting values into indexed arrays inside nested JSON object?

I have a nested object that was created using json_decode and that I want to dynamically loop through in PHP and alter specific values within it. The path to the key I want to alter is stored in an indexed array that I pass to my function, where the last item in the array contains the key.
The array containing the path to the key looks like this:
array(3) {
[0]=>
string(6) "level1"
[1]=>
string(6) "level2"
[2]=>
string(6) "level3"
}
My object $obj looks like this:
{
"level1": {
"level2": {
"level3": "value"
}
}
}
When it's all objects, inside my for loop I can do this:
for($i = 0; $i < count($array); $i++) {
$obj = $obj->{$array[$i]};
if($i == count($array)-1) $obj = $value;
}
=> thus, altering the original object.
However, I'm stuck at inserting a new value into an indexed array that is part of my JSON object, using the method above. Consider that 'level3' in the following example represents an indexed array inside my object.
{
"level1": {
"level2": {
"level3": []
}
}
}
If I do:
for($i = 0; $i < count($array); $i++) {
$obj = $obj->{$array[$i]};
if($i == count($array)-1) {
if(gettype($obj) == 'object') $obj = $value;
else if(gettype($obj) == 'array') array_push($obj, $value);
}
}
When I var_dump the altered array inside the for loop, I get the expected results. However this way, the original array inside my object remains untouched. How can I achieve the array being altered in my object instead of just in local scope?
It seems that I am only able to achieve that behavior only if I access it staticly, like this: array_push($obj->level1->level2->level3, $value);
But I can't think of a dynamic way of achieving this.
I hope I made it all clear and anyone has a possible solution to this problem.

Merge arrays with strings and delete repetitive values in PHP

From WP_Query I am dumping strings stored into separate arrays:
array(1) {
[0]=>
string(8) "Portugal"
}
array(1) {
[0]=>
string(5) "Spain"
}
array(1) {
[0]=>
string(5) "Italy"
}
array(1) {
[0]=>
string(6) "Monaco"
}
array(1) {
[0]=>
string(5) "Spain"
}
array(1) {
[0]=>
string(9) "Lithuania"
}
I am trying to merge those arrays into one array, delete repetitive strings like "Spain" and get the number of unique values.
I was trying to use array_merge():
$tester = array();
foreach($array_string as $value) {
array_merge($tester, $value);
}
$result = array_unique($tester);
print_r($result);
But without any decent results, error telling that <b>Warning</b>: array_merge(): Argument #2 is not an array Could someone tell where I am missing the point? Many thanks for all possible help, will be looking forward.
The code posted in the question is almost good. The intention is correct but you missed out a simple thing: you initialize $tester with an empty array and then never add something to it. In the end, it is still empty and array_unique() has nothing to do but return an empty array too.
The error is in the line:
array_merge($tester, $value);
array_merge() does not change the arrays passed to it as argument. It returns a new array that your code ignores (instead of saving it into $tester).
This is how your code should look like:
$tester = array();
foreach($array_string as $value) {
$tester = array_merge($tester, $value);
}
$result = array_unique($tester);
print_r($result);
Solution #2
You can use call_user_func_array() to invoke array_merge() and pass the values of $array_string as arguments. The returned array contains duplicates; passing it to array_unique() removes them:
$result = array_unique(call_user_func_array('array_merge', $array_string));
Solution #3
A simpler (and possibly faster) way to accomplish the same thing is to use array_column() to get the values into an array and then pass it to array_unique(), of course:
$result = array_unique(array_column($array_string, 0));
This solution works only with PHP 5.5 or newer (the array_column() function doesn't exist in older versions.)
To get the number of unique strings in the merged array you will have to set empty array before WP_Query
$tester = array();
Than inside while loop of WP_Query every string is put into separate array and pushed to $tester
foreach((array)$array_string as $key=>$value[0]) {
array_push($tester,$value);
}
Unique values in array $tester is found using array_unique() functions that should be placed after WP_Query while loop.
$unique_array_string = array_unique($tester, SORT_REGULAR);
$unique_string_number = sizeof($unique_array_string);
You will create an array and will take key of the array for cities names like spain..etc And it will give different cities name always...
$data= array();
$data1 = array("Portugal");
$data2 = array("Spain");
$data3 = array("Italy");
$data4 = array("Monaco");
$data5 = array("Spain");
$data6 = array("Lithuania");
$merge = array_merge($data1, $data2,$data3,$data4,$data5,$data6);
$data = array();
foreach($merge as $value) {
$data[$value] = $value;
}
echo '<pre>',print_r($data),'</pre>';

Using array_count_values and getting "Can only count STRING and INTEGER values!" error

I'm trying to add 4 arrays into one array ($all_prices) and then check the values of each key in each individual array against $all_prices to make sure that they are unique. If they are not I want to add a 0 to the end of if to make it unique (so 0.50 becomes 0.500).
For some reason I'm getting the following error, despite the fact that I already changed the data type from decimal to varchar:
array_count_values(): Can only count STRING and INTEGER values!
Edit
Here is a snippet from dd($all_prices)
array(4) { [0]=> array(9) { ["14.45"]=> string(8) "sample 1" ["12.40"]=>
string(8) "sample 2" ["14.13"]=> string(8) "sample 3" ["15.11"]=>
string(8) "sample 4"
Code:
$all_prices = [$list_a_prices, $list_b_prices, $list_c_prices, $list_d_prices];
$price_count = array_count_values($all_prices);
foreach($list_b_prices as $key => $value){
if($price_count[$key] >= 2){
$key . "0";
}
}
Where am I going wrong? Is there is a way to leave the data type as Decimal?
I think you should not index by the prices, after all from a math point of view 5.0 and 5.00 does not make difference at all.
If you are obtaining values from a database you will get strigs everywhere. So you will have to cast (int)$key for the keys.
And in your foreach you are changing a temporary variable. $key exists only for the current iteration of the loop you will want to declare it as:
foreach($list_b_prices as &(int)$key => $value){
if($price_count[$key] >= 2){
$key . "0";
}
}
Note the ampersand and the casting to integer. Although i'm not sure which will come first. But again: I think indexing by some different value shall give you a better result.
What about a nested loop ?
$all_prices = [$list_a_prices, $list_c_prices, $list_d_prices];
foreach($list_b_prices as $key => $value){
foreach($all_prices as $array){
if(isset($array[$key])){
$list_b_prices[$key] .= '0';
}
}
}
Not elegant but it does the trick.

php - push array into an array -(pushing both key and the array)

I am trying to add an array to an existing array. I am able to add the array using the array_push . The only problem is that when trying to add array that contains an array keys, it adds an extra array within the existing array.
It might be best if I show to you
foreach ($fields as $f)
{
if ($f == 'Thumbnail')
{
$thumnail = array('Thumbnail' => Assets::getProductThumbnail($row['id'] );
array_push($newrow, $thumnail);
}
else
{
$newrow[$f] = $row[$f];
}
}
The fields array above is part of an array that has been dynamically fed from an SQl query it is then fed into a new array called $newrow. However, to this $newrow array, I need to add the thumbnail array fields .
Below is the output ( using var_dump) from the above code. The only problem with the code is that I don't want to create a seperate array within the arrays. I just need it to be added to the array.
array(4) { ["Product ID"]=> string(7) "1007520"
["SKU"]=> string(5) "G1505"
["Name"]=> string(22) "150mm Oval Scale Ruler"
array(1) { ["Thumbnail"]=> string(77) "thumbnails/products/5036228.jpg" } }
I would really appreciate any advice.
All you really want is:
$newrow['Thumbnail'] = Assets::getProductThumbnail($row['id']);
You can use array_merge function
$newrow = array_merge($newrow, $thumnail);
Alternatively, you can also assign it directly to $newrow:
if ($f == 'Thumbnail')
$newrow[$f] = Assets::getProductThumbnail($row['id']);
else
...
Or if you want your code to be shorter:
foreach($fields as $f)
$newrow[$f] = ($f == 'Thumbnail')? Assets::getProductThumbnail($row['id']) : $row[$f];
But if you're getting paid by number of lines in your code, don't do this, stay on your code :) j/k

How to convert an array of arrays or objects to an associative array?

I'm used to perl's map() function where the callback can assign both the key and the value, thus creating an associative array where the input was a flat array. I'm aware of array_fill_keys() which can be useful if all you want to do is create a dictionary-style hash, but what if you don't necessarily want all values to be the same? Obviously all things can be done with foreach iteration, but what other (possibly more elegant) methods exist?
Edit: adding an example to clarify the transformation. Please don't get hung up on the transformation, the question is about transforming a flat list to a hash where we can't assume that all the values will be the same.
$original_array: ('a', 'b', 'c', 'd')
$new_hash: ('a'=>'yes', 'b'=>'no', 'c'=>'yes', 'd'=>'no')
*note: the values in this example are arbitrary, governed by some business logic that is not really relevant to this question. For example, perhaps it's based on the even-oddness of the ordinal value of the key
Real-world Example
So, using an answer that was provided here, here is how you could parse through the $_POST to get a list of only those input fields that match a given criteria. This could be useful, for example, if you have a lot of input fields in your form, but a certain group of them must be processed together.
In this case I have a number of input fields that represent mappings to a database. Each of the input fields looks like this:
<input name="field-user_email" value="2" /> where each of this type of field is prefixed with "field-".
what we want to do is, first, get a list of only those input fields who actually start with "field-", then we want to create an associative array called $mapped_fields that has the extracted field name as the key and the actual input field's value as the value.
$mapped_fields = array_reduce( preg_grep( '/field-.+/', array_keys( $_POST ) ), function( $hash, $field ){ $hash[substr( $field, 6 )] = $_POST[$field]; return $hash; } );
Which outputs:
Array ( [date_of_birth] => 1 [user_email] => 2 [last_name] => 3 [first_name] => 4 [current_position] => 6 )
(So, just to forestall the naysayers, let me agree that this bit of compact code is arguably a lot less readable that a simple loop that iterates through $_POST and, for each key, checks to see if it has the prefix, and if so, pops it and its value onto an array)
I had the exact same problem some days ago. It is not possible using array_map, but array_reduce does the trick.
$arr = array('a','b','c','d');
$assoc_arr = array_reduce($arr, function ($result, $item) {
$result[$item] = (($item == 'a') || ($item == 'c')) ? 'yes' : 'no';
return $result;
}, array());
var_dump($assoc_arr);
result:
array(4) { ["a"]=> string(3) "yes" ["b"]=> string(2) "no" ["c"]=> string(3) "yes" ["d"]=> string(2) "no" }
As far as I know, it is completely impossible in one expression, so you may as well use a foreach loop, à la
$new_hash = array();
foreach($original_array as $item) {
$new_hash[$item] = 'something';
}
If you need it in one expression, go ahead and make a function:
function array_map_keys($callback, $array) {
$result = array();
foreach($array as $item) {
$r = $callback($item);
$result[$r[0]] = $r[1];
}
return $result;
}
This is a clarification on my comment in the accepted method. Hopefully easier to read. This is from a WordPress class, thus the $wpdb reference to write data:
class SLPlus_Locations {
private $dbFields = array('name','address','city');
public function MakePersistent() {
global $wpdb;
$dataArray = array_reduce($this->dbFields,array($this,'mapPropertyToField'));
$wpdb->insert('wp_store_locator',$dataArray);
}
private function mapPropertyToField($result,$property) {
$result[$property] = $this->$property;
return $result;
}
}
Obviously there is a bit more to the complete solution, but the parts relevant to array_reduce() are present. Easier to read and more elegant than a foreach or forcing the issue through array_map() plus a custom insert statement.
Nice!
A good use case of yield operator!
$arr = array('a','b','c','d');
$fct = function(array $items) {
foreach($items as $letter)
{
yield sprintf("key-%s",
$letter
) => "yes";
}
};
$newArr = iterator_to_array($fct($arr));
which gives:
array(4) {
'key-a' =>
string(3) "yes"
'key-b' =>
string(3) "yes"
'key-c' =>
string(3) "yes"
'key-d' =>
string(3) "yes"
}

Categories