Replace value in multidimensional php array - php

First, I have to say I already checked some answers but neither it wasn't exactly what I was looking for neither I couldn't fully understand the answer and how to use it.
I have this MultiDimensional array:
Array
(
[field_5abcb693a68bc] => Array
(
[0] => Array
(
[field_5abcbb1b51ddf] => mortgage
[field_5ae58a0b58b58] =>
[field_5abcbb1e51de0] => 10
[field_5abcbb2051de1] => הידגלה
[field_5abcbb2351de2] => 45,654,456
[field_5abcbb6251de3] =>
[field_5abcbb6651de4] => 04/2017
[field_5abcbb6851de5] => 4,454,656
[field_5abcbb6b51de6] => 24/07/2018
[field_5abcbbb351de7] => 657
[field_5abcbbb651de8] => 24/07/2018
[field_5abcbbb851de9] => 15
[field_5abcbbbb51dea] => yes
)
)
)
And I want to find values that much the pattern of mm/yyyy. Regex is a good option, I know how to write the condition:
if ( preg_match('/^\d[1-9]\/[1-9][0-9][0-9][0-9]$/',$v) ) {
//do stuff
}
I want to look in the inner part of the array for this pattern, and if it is a match, to change the value in the array to
$value = '01/' . $value;
means the value '04/2017' changes to '01/04/2017'.
note: there can be more than one value to change.
note: the name of the first array within the array [field_5abcb693a68bc] can vary and won't stay the same in other cases.
Thanks.

Use array_walk_recursive with reference to value as argument instead of regular value argument of callback.
It would work like that:
array_walk_recursive(
$array,
function (&$value) {
if (preg_match('/^\d[1-9]\/[1-9][0-9][0-9][0-9]$/',$value)) {
$value = '01/' . $value;
}
}
);
Check result on 3v4l.org

You want to perform a conditional replacement using a regex pattern? preg_replace() seems sensible. preg_replace() is happy to iterate a one-dimensional array, so the lowest level can be directly served to it.
Notice that I've changed the pattern delimiter to avoid escaping the forward slash. I've also made the pattern more brief by using \d and the {3} quantifier. $0 means the "fullstring match". You don't have to write the unset() call to purge those temporary variables, but some developers consider it to be best practice because it avoids potential variable conflict down-script.
Code: (Demo)
$array = [
'field_5abcb693a68bc' => [
0 => [
'field_5abcbb1b51ddf' => 'mortgage',
'field_5ae58a0b58b58' => '',
'field_5abcbb1e51de0' => '10',
'field_5abcbb2051de1' => 'הידגלה',
'field_5abcbb2351de2' => '45,654,456',
'field_5abcbb6251de3' => '',
'field_5abcbb6651de4' => '04/2017',
'field_5abcbb6851de5' => '4,454,656',
'field_5abcbb6b51de6' => '24/07/2018',
'field_5abcbbb351de7' => '657',
'field_5abcbbb651de8' => '24/07/2018',
'field_5abcbbb851de9' => '15',
'field_5abcbbbb51dea' => 'yes'
]
]
];
foreach ($array as &$set) {
foreach ($set as &$subset) {
$subset = preg_replace('~^\d[1-9]/[1-9]\d{3}$~', '01/$0', $subset);
}
}
unset($set, $subset); // avoid future variable interferences
var_export($array);
Output:
array (
'field_5abcb693a68bc' =>
array (
0 =>
array (
'field_5abcbb1b51ddf' => 'mortgage',
'field_5ae58a0b58b58' => '',
'field_5abcbb1e51de0' => '10',
'field_5abcbb2051de1' => 'הידגלה',
'field_5abcbb2351de2' => '45,654,456',
'field_5abcbb6251de3' => '',
'field_5abcbb6651de4' => '01/04/2017',
'field_5abcbb6851de5' => '4,454,656',
'field_5abcbb6b51de6' => '24/07/2018',
'field_5abcbbb351de7' => '657',
'field_5abcbbb651de8' => '24/07/2018',
'field_5abcbbb851de9' => '15',
'field_5abcbbbb51dea' => 'yes',
),
),
)

You can anonymously process down the levels of the array without actually knowing any of the names of the keys like this for example
foreach ($arr as $key => &$outer) {
foreach ($outer as &$inner) {
foreach ($inner as $k => $v) {
if ( preg_match('/^\d[1-9]\/[1-9][0-9][0-9][0-9]$/',$v) ) {
$inner[$k] = '01/' . $v;
}
}
}
}

Related

How to remove duplicate arrays from a multi dimensional array?

I have a large multidimensional array that looks like the below.
I want to remove duplicate arrays based on the ID, however, I am struggling to achieve this.
I want the duplicates to work over the entire array, so you can see that ID 1229873 is a duplicate, in the array 2021-07-07 and 2021-07-09, it should therefore be removed from 2021-07-09
How would I achieve this? array_unique has not worked for me.
$data = array (
'2021-07-07' =>
array (
0 =>
array (
'id' => 5435435,
'homeID' => 8754,
'match_url' => '/usa/reading-united-ac-vs-ocean-city-noreasters-fc-h2h-stats#1229873',
'competition_id' => 5808,
'matches_completed_minimum' => 12,
),
1 =>
array (
'id' => 1229873,
'homeID' => 8754,
'match_url' => '/usa/reading-united-ac-vs-ocean-city-noreasters-fc-h2h-stats#1229873',
'competition_id' => 5808,
'matches_completed_minimum' => 12,
),
),
'2021-07-09' =>
array (
0 =>
array (
'id' => 3243234,
'homeID' => 8754,
'match_url' => '/usa/reading-united-ac-vs-ocean-city-noreasters-fc-h2h-stats#1229873',
'competition_id' => 5808,
'matches_completed_minimum' => 12,
),
1 =>
array (
'id' => 1229873,
'homeID' => 8754,
'match_url' => '/usa/reading-united-ac-vs-ocean-city-noreasters-fc-h2h-stats#1229873',
'competition_id' => 5808,
'matches_completed_minimum' => 12,
),
),
);
This is a perfect case for array_uunique()! No wait, scratch that. The PHP devs refused to implement it for the perfectly valid reason of... [shuffles notes] "the function name looks like a typo".
[sets notes on fire]
Anyhow, you just need to iterate over that data, keep track of the IDs you've seen, and remove entries that you've already seen.
$seen = [];
foreach(array_keys($data) as $i) {
foreach(array_keys($data[$i]) as $j) {
$id = $data[$i][$j]['id'];
if( in_array($id, $seen) ) {
unset($data[$i][$j]);
} else {
$seen[] = $id;
}
}
}
I've opted for the foreach(array_keys(...) as $x) approach as avoiding PHP references is always the sane choice.
Run it.
I am Sure That is the way which you want to get the unique array.
$unique = array_map("unserialize", array_unique(array_map("serialize", $data)));
echo "<pre>";
print_r($unique);
echo "</pre>";

Remove similar from foreach

Trying use array_unique but this did not help, since not all fields are repeated for me.
My array:
'Rows' =>
array (
0 =>
array (
'HotelId' => 94852,
'OfferId' => 858080496,
'OfferIdStr' => '858080496',
'Price' => 2762,
),
1 =>
array (
'HotelId' => 94852,
'OfferId' => 858080497,
'OfferIdStr' => '858080497',
'Price' => 3000,
),
And my try:
$allHotels['Rows'] = array_unique($allHotels['Rows'], SORT_REGULAR);
Changed flags, didn't help either (SORT_NORMAL, etc...).
It deletes absolutely everything, i need to ignore the HotelId replay, only first need.
If you want to delete duplicated entries based solely on the HotelId value, it's probably easiest just to iterate over the array, storing the seen HotelId values and deleting the current entry if it's HotelId value has already been seen.
$seenHotels = array();
foreach ($allHotels['Rows'] as $key => $hotel) {
if (in_array($hotel['HotelId'], $seenHotels)) {
unset($allHotels['Rows'][$key]);
}
else {
$seenHotels[] = $hotel['HotelId'];
}
}
print_r($allHotels);
Demo on 3v4l.org

Remove deeply nested element from multi-dimensional array?

I need to remove an element form a deeply nested array of unknown structure (i.e. I do not know what the key sequence would be to address the element in order to unset it). The element I am removing however does have a consistent structure (stdObject), so I can search the entire multidimensional array to find it, but then it must be removed. Thoughts on how to accomplish this?
EDIT: This is the function I have right now trying to achieve this.
function _subqueue_filter_reference(&$where)
{
foreach ($where as $key => $value) {
if (is_array($value))
{
foreach ($value as $filter_key => $filter)
{
if (isset($filter['field']) && is_string($filter['field']) && $filter['field'] == 'nodequeue_nodes_node__nodequeue_subqueue.reference')
{
unset($value[$filter_key]);
return TRUE;
}
}
return _subqueue_filter_reference($value);
}
}
return FALSE;
}
EDIT #2: Snipped of array structure from var_dump.
array (size=1)
1 =>
array (size=3)
'conditions' =>
array (size=5)
0 =>
array (size=3)
...
1 =>
array (size=3)
...
2 =>
array (size=3)
...
3 =>
array (size=3)
...
4 =>
array (size=3)
...
'args' =>
array (size=0)
empty
'type' => string 'AND' (length=3)
...so assuming that this entire structure is assigned to $array, the element I need to remove is $array[1]['conditions'][4] where that target is an array with three fields:
field
value
operator
...all of which are string values.
This is just a cursor problem.
function recursive_unset(&$array)
{
foreach ($array as $key => &$value) # See the added & here.
{
if(is_array($value))
{
if(isset($value['field']) && $value['field'] == 'nodequeue_nodes_node__nodequeue_subqueue.reference')
{
unset($array[$key]);
}
recursive_unset($value);
}
}
}
Notes : you don't need to use is_string here, you can just make the comparison as you're comparing to a string and the value exists.
Don't use return unless you're sure there is only one occurrence of your value.
Edit :
Here is a complete example with an array similar to what you showed :
$test = array (
1 => array (
'conditions' =>
array (
0 => array ('field' => 'dont_care1', 'value' => 'test', 'operator' => 'whatever'),
1 => array ('field' => 'dont_care2', 'value' => 'test', 'operator' => 'whatever'),
2 => array ('field' => 'nodequeue_nodes_node__nodequeue_subqueue.reference', 'value' => 'test', 'operator' => 'whatever'),
3 => array ('field' => 'dont_care3', 'value' => 'test', 'operator' => 'whatever')
),
'args' => array (),
'type' => 'AND'
));
var_dump($test);
function recursive_unset(&$array)
{
foreach ($array as $key => &$value)
{
if(is_array($value))
{
if(isset($value['field']) && $value['field'] == 'nodequeue_nodes_node__nodequeue_subqueue.reference')
{
unset($array[$key]);
}
recursive_unset($value);
}
}
}
recursive_unset($test);
var_dump($test);
One way to solve this was to extend your recursive function with a second parameter:
function _subqueue_filter_reference(&$where, $keyPath = array())
You'd still do the initial call the same way, but the internal call to itself would be this:
return _subqueue_filter_reference($value, array_merge($keyPath, array($key)));
This would provide you with the full path of keys to reach the current part of the array in the $keyPath variable. You can then use this in your unset. If you're feeling really dirty, you might even use eval for this as a valid shortcut, since the source of the input you'd give it would be fully within your control.
Edit: On another note, it may not be a good idea to delete items from the array while you're looping over it. I'm not sure how a foreach compiles but if you get weird errors you may want to separate your finding logic from the deleting logic.
I have arrived at a solution that is a spin-off of the function found at http://www.php.net/manual/en/function.array-search.php#79535 (array_search documentation).
Code:
function _subqueue_filter_reference($haystack,&$tree=array(),$index="")
{
// dpm($haystack);
if (is_array($haystack))
{
$result = array();
if (count($tree)==0)
{
$tree = array() + $haystack;
}
foreach($haystack as $k=>$current)
{
if (is_array($current))
{
if (isset($current['field']) && is_string($current['field']) && $current['field'] == 'nodequeue_nodes_node__nodequeue_subqueue.reference')
{
eval("unset(\$tree{$index}[{$k}]);"); // unset all elements = empty array
}
_subqueue_filter_reference($current,$tree,$index."[$k]");
}
}
}
return $tree;
}
I hate having to use eval as it SCREAMS of a giant, gaping security hole, but it's pretty secure and the values being called in eval are generated explicitly by Drupal core and Views. I'm okay with using it for now.
Anyway, when I return the tree I simply replace the old array with the newly returned tree array. Works like a charm.

Performing an action when finding same values in two PHP arrays

I have two associative arrays in PHP that are defined as following:
$this_week[] = array(
"top_song_id" => $row["view_song_id"],
"top_place" => $i,
"top_move" => "0",
"top_last" => $i,
"top_peak" => $i,
"top_rating" => get_song_rating_by_id($row["view_song_id"]),
"top_views" => $row["view_sum"],
"top_start" => $monday,
"top_end" => $sunday
);
and
$last_week[] = array(
"top_song_id" => $row["view_song_id"],
"top_place" => get_song_place_by_id($row["view_song_id"]),
"top_move" => "0",
"top_last" => get_song_last_by_id($row["view_song_id"]),
"top_peak" => get_song_peak_by_id($row["view_song_id"]),
"top_rating" => get_song_rating_by_id($row["view_song_id"]),
"top_views" => $row["view_sum"],
"top_start" => $prev_monday,
"top_end" => $prev_sunday
);
Now here is my question: how can I traverse this two arrays and perform an action if there is any song id in one array that can be found in the other one?
A for() loop doesn't work because there can be common songs for both arrays but not on the same array index.
Any help is appreciated.
An efficient way to do this is to just change the first line of the last snippet this way:
$last_week[$row["view_song_id"]] = array( // Added the song id as the array index
"top_song_id" => $row["view_song_id"],
...
After that you can use a simple for loop this way:
for ($this_week as $item) {
if ( isset ($last_week[ $item["top_song_id"] ]) ) {
// HERE YOU HAVE FOUND A DUPLICATE
}
}
if( in_array( $this_week["top_song_id"], $last_week ) ) {
//do something
}
Why not just hardcode the 5(?) comparisons you need in one if statement? No need to overcomplicate things.

Retrieve first key in multi-dimensional array using PHP

I would like to retrieve the first key from this multi-dimensional array.
Array
(
[User] => Array
(
[id] => 2
[firstname] => first
[lastname] => last
[phone] => 123-1456
[email] =>
[website] =>
[group_id] => 1
[company_id] => 1
)
)
This array is stored in $this->data.
Right now I am using key($this->data) which retrieves 'User' as it should but this doesn't feel like the correct way to reach the result.
Are there any other ways to retrieve this result?
Thanks
There are other ways of doing it but nothing as quick and as short as using key(). Every other usage is for getting all keys. For example, all of these will return the first key in an array:
$keys=array_keys($this->data);
echo $keys[0]; //prints first key
foreach ($this->data as $key => $value)
{
echo $key;
break;
}
As you can see both are sloppy.
If you want a oneliner, but you want to protect yourself from accidentally getting the wrong key if the iterator is not on the first element, try this:
reset($this->data);
reset():
reset() rewinds array 's internal
pointer to the first element and
returns the value of the first array
element.
But what you're doing looks fine to me. There is a function that does exactly what you want in one line; what else could you want?
Use this (PHP 5.5+):
echo reset(array_column($this->data, 'id'));
I had a similar problem to solve and was pleased to find this post. However, the solutions provided only works for 2 levels and do not work for a multi-dimensional array with any number of levels. I needed a solution that could work for an array with any dimension and could find the first keys of each level.
After a bit of work I found a solution that may be useful to someone else and therefore I included my solution as part of this post.
Here is a sample start array:
$myArray = array(
'referrer' => array(
'week' => array(
'201901' => array(
'Internal' => array(
'page' => array(
'number' => 201,
'visits' => 5
)
),
'External' => array(
'page' => array(
'number' => 121,
'visits' => 1
)
),
),
'201902' => array(
'Social' => array(
'page' => array(
'number' => 921,
'visits' => 100
)
),
'External' => array(
'page' => array(
'number' => 88,
'visits' => 4
)
),
)
)
)
);
As this function needs to display all the fist keys whatever the dimension of the array, this suggested a recursive function and my function looks like this:
function getFirstKeys($arr){
$keys = '';
reset($arr);
$key = key($arr);
$arr1 = $arr[$key];
if (is_array($arr1)){
$keys .= $key . '|'. getFirstKeys($arr1);
} else {
$keys = $key;
}
return $keys;
}
When the function is called using the code:
$xx = getFirstKeys($myArray);
echo '<h4>Get First Keys</h4>';
echo '<li>The keys are: '.$xx.'</li>';
the output is:
Get First Keys
The keys are: referrer|week|201901|Internal|page|number
I hope this saves someone a bit of time should they encounter a similar problem.

Categories