I have custom code where I get nearest user from array by array key:
$users = [
"4" => "John",
"7" => "Alex",
"13" => "Smith",
"95" => "Taylor"
];
$id = 9;
$nearestUserByIdInReverseOrder = false;
foreach($users as $userId => $name) {
if($id >= $userId) {
$nearestUserByIdInReverseOrder = $name;
}
}
echo $nearestUserByIdInReverseOrder;
When I change var $id to 3 or smaller number then don't get result. How to get first element of array when $id smaller then it. And can be shorted or optimized code if I've incorrect logic operation in my code? Maybe this possible without looping.
Here is demo
If you want to get the first value in your array if $id is less than any of the array keys, you can initialise $nearestUserByIdInReverseOrder to the first element in the array using reset:
$users = [
"4" => "John",
"7" => "Alex",
"13" => "Smith",
"95" => "Taylor"
];
$id = 3;
$nearestUserByIdInReverseOrder = reset($users);
foreach($users as $userId => $name) {
if($id >= $userId) {
$nearestUserByIdInReverseOrder = $name;
}
}
echo $nearestUserByIdInReverseOrder;
Output:
John
Demo on 3v4l.org
Note that for this (or your original code) to work, the keys must be in increasing numerical order. If they might not be, ksort the array first:
ksort($users);
Based on your explanation you want to reverse the comparison logic. Also, you want to break out of the loop when you have found the key that is greater, or else you will always get the greatest number:
foreach($users as $userId => $name) {
if($userId > $id) {
$nearestUserByIdInReverseOrder = $name;
break;
}
}
I have removed the = as that would return 4 for $id = 4 instead of 7. If that is not the desired behavior then add it back.
If the array is not guaranteed to be in ascending order of keys then you need to sort first:
ksort($users);
function findClosest($array, $index) {
ksort($array);
$idx = array_keys($array)[0];
foreach ($array as $key => $value) {
if ($key > $index) return $array[$idx];
$idx = $key;
}
}
$users = [
"13" => "Smith",
"4" => "John",
"7" => "Alex",
"95" => "Taylor"
];
$id = 3;
echo findClosest($users, $id);
You could use array_keys() (props to Jeto) on the array, search for the closest number in that (this for reference) and then grab the name based on that.
This would work and the logic is pretty simple, although there may be better solutions. I'm sure someone can use it at some point for something :)
Related
I need to group values in my flat array so that each non-integer value starts a new group/row in my result array and every integer value encountered until the next occurring non-integer should be pushed into a subarray in the respective group/row.
Input:
$unitsList = [
"Aulas Gratuitas",
"149",
"151",
"153",
"Módulo 0",
"964",
"989",
"Módulo 1",
"985",
"1079",
"1001",
"1003"
"Módulo 2",
"1009"
];
Current code:
$newArr = array();
foreach( $unitsList as $item ) {
if( !is_numeric($item) ) {
$title = array( "title" => $item, "units" => "" );
array_push( $newArr, $title);
} elseif ( is_numeric($item) ) {
$number = $item;
array_push( $newArr, $number);
}
}
Current result:
[
[
"title" => "Aulas Gratuitas",
"units" => ""
]
"149",
"151",
"153",
[
"title" => "Módulo 0",
"units" => ""
],
"964",
"989",
[
"title" => 'Módulo 1',
"units" => ""
],
"985",
"1079",
"1001",
"1003"
[
"title" => 'Módulo 2',
"units" => ''
],
"1009"
]
As you can see, I am having a hard time adding the numbers into the "units" key.
Desired result:
[
[
"title" => "Aulas Gratuitas",
"units" => ["149", "151", "153"]
],
[
"title" => 'Módulo 0',
"units" => ["964", "989"]
],
[
"title" => 'Módulo 1',
"units" => ["985", "1079", "1001", "1003"]
],
[
"title" => "Módulo 2",
"units" => ["1009"]
]
]
So that you don't need to keep track of first level indexes, use a reference variable and push related data into that AFTER pushing the new row into the result array.
Code: (Demo)
$result = [];
foreach ($array as $value) {
if (!ctype_digit($value)) {
unset($units);
$units = [];
$result[] = ['title' => $value, 'units' => &$units];
} else {
$units[] = $value;
}
}
var_export($result);
unset() is used to destroy the reference to the previous group, otherwise newly pushed data would be sent to two (or more) places in the result array.
Reference variables have a default value of null. If your title values are guaranteed to be followed by an integer/unit value, then $units = []; can be removed. If you have a title and then another title there will be no data for the former title group. With the explicit array declaration, the group will have an empty units array instead of null.
Related questions answered with the same general technique:
Group array of objects into deeper parent-child structure
Split flat array into grouped subarrays containing values from consecutive key in the input array
How to split a string by repeated characters in PHP?
If you are running PHP7.3 or higher (and by this point you definitely should be) AND you are intimidated/mystified by using a reference variable, then you can leverage array_key_last() to locate the latest created array row.
Code: (Demo)
$result = [];
foreach ($array as $value) {
if (!ctype_digit($value)) {
$result[] = ['title' => $value, 'units' => []];
} else {
$result[array_key_last($result)]['units'][] = $value;
}
}
var_export($result);
Items in the given list aren't regular. The first item has three units, and the second has two units. We cannot convert them into the expected structure without controlling the type of each item. My solution is below. I added explanations as a comment line.
$values = array(
"string",
11111,
22222,
33333,
"string_2",
44444,
55555
);
$formattedArray = [];
$index = -1;
foreach ($values as $value) {
// If the value is a string, create the new array structure item and assign the title
if (is_string($value)) {
$index++;
$formattedArray[$index]['title'] = $value;
$formattedArray[$index]['units'] = [];
// The rest of the code in "foreach scope" is for integer values, so skip the remaining part
continue;
}
// If the following line is executing, the value is an integer
// Push the value to the current item's units
$formattedArray[$index]['units'][] = $value;
}
var_dump($formattedArray);
$originalArray = ['a', 1, 2, 3, 'b', 4, 5, 6];
function formatArray($input) {
$output = [];
foreach($input as $inputRow) {
if (is_string($inputRow)) {
$output[] = ['title' => $inputRow, 'units' => []];
} elseif (count($output)) {
$output[count($output)-1]['units'][] = $inputRow;
}
}
return $output;
}
var_dump(formatArray($originalArray));
Note that your numbers after the title CANNOT be strings, otherwise this function will recognize it as new titles.
This code will output:
array(2) {
[0]=>
array(2) {
["title"]=>
string(1) "a"
["units"]=>
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}
}
[1]=>
array(2) {
["title"]=>
string(1) "b"
["units"]=>
array(3) {
[0]=>
int(4)
[1]=>
int(5)
[2]=>
int(6)
}
}
}
Approach: loop over values in array, check for value if its converted to int is equal to 0, So group by that key, otherwise accumelate they next elements.
<?php
$unitsList = [ "Aulas Gratuitas", "149", "151", "153", "Módulo 0", "964", "989", "Módulo 1", "985", "1079", "1001", "1003", "Módulo 2", "1009" ];
$result = [];
foreach ($unitsList as $key => $value) {
if(intval($value)==0){
$result[] = ['title' => $value, 'units' => []];
}else{
$result[count($result)-1]['units'][] = $value;
}
}
print_r(array_values($result));
?>
How can I convert the string test[1][2][3][4][5] to a multidimensional PHP array like:
array(1 => array(2 => array( 3 => array( 4 => array(5 => array()))));
If I understood your question correctly, you're asking to convert the string "test[1][2][3][4][5]" to array(1 => array(2 => array( 3 => array( 4 => array(5 => array()))));
First of all, people usually use the short array() notation, which is just [].
Second, why use strings, when you can just type
$test[1][2][3][4][5] = [];
to get what you want.
If you really want strings, you can do it in several ways, one of which is:
function makeArrayFromString($string, &$name)
{
$namePosEnd = strpos($string, '['); // name ends when first [ starts
if (!$namePosEnd) return false; // didn't find [ or doesn't start with name
$name = substr($string, 0, $namePosEnd);
$dimensionKeys = [];
$result = preg_match_all('/\[([0-9]+)\]/', $string, $dimensionKeys); // get keys
if (!$result) return false; // no matches for dimension keys
$dimensionKeys = array_reverse($dimensionKeys[1]);
$multiArray = [];
foreach ($dimensionKeys as $key)
{
$key = (int)$key; // we have string keys, turn them to integers
$temp = [];
$temp[$key] = $multiArray;
$multiArray = $temp;
}
return $multiArray;
}
$string = 'test[1][2][3][4][5]';
$name = '';
$multiArray = makeArrayFromString($string, $name);
if ($multiArray === false)
exit('Error creating the multidimensional array from string.');
$$name = $multiArray; // assign the array to the variable name stored in $name
var_dump($test); // let's check if it worked!
Outputs:
array(1) {
[1]=>
array(1) {
[2]=>
array(1) {
[3]=>
array(1) {
[4]=>
array(1) {
[5]=>
array(0) {
}
}
}
}
}
}
Keep in mind that I didn't add any checks if the $name string satisfies the PHP variable naming rules. So you might get an error if you do something like 111onetest[1][2][3][4][5], as variable names in PHP can't start with a number.
I have a string, I need to know at what index is that string exist in the array. My array is as follows:
array(3)
{
[0]=>object(stdClass)#47170 (3)
{
["countries"]=>string(2) "HK"
["last_seen_date"]=>string(10) "2016-09-17"
["ad_uid"]=>string(14) "157d5908a1ca83"
}
[1]=>object(stdClass)#47171 (3)
{
["countries"]=>string(2) "HK"
["last_seen_date"]=>string(10) "2016-09-27"
["ad_uid"]=>string(14) "157d7978513bc3"
}
[2]=>object(stdClass)#47230 (3)
{
["countries"]=>string(2) "HK"
["last_seen_date"]=>string(10) "2016-09-27"
["ad_uid"]=>string(14) "157ea7239824e9"
}
}
The last seen date is:2016-09-27.
I would like to know at what index does 2016-09-27 exist in the array. So I know what is ad_uid related to that date. I have a method which does this.
public function getAd_uid($last_seen_date,$values){
$key = array_keys($values,$last_seen_date);
print_r($key);
}
The result gets an empty array. I have tried array_serach() has same empty results. Any other alternative solutions to achieve results?
To find all $ad_uids last_seen at particular date you can use array_filter which will return you all elements you are looking for. If you need ad_uids only, you can apply array_map to that array as following:
<?php
// $array is the array in question.
$filtered = array_filter($array, function($item) {
return $item->last_seen_date == "2016-09-27";
});
$ad_uids = array_map(function($item){return $item->ad_uid;}, $filtered);
Example
As each each entry of your array is an object and you know the attributs' names of theses objects (I assume they never change), I would do it like this :
/**
* #param string $last_seen_date
* #param array $values
* #return mixed null|int
*/
function getAdUid($last_seen_date, array $values) {
// Just in case no entry match
$matching_index = null;
// Loop through each entry: $entry is an object
foreach($values as $index => $entry) {
if($entry->last_seen_date == $last_seen_date) {
$matching_index = $index;
break; // end loop: we found that we are looking for
}
}
return $matching_index;
}
to do that just loop your array
foreach($values as $key => $row) {
// do something
}
then check if $last_seen_date is equal to the loop index last_seen_date $row->last_seen_date
if ($row->last_seen_date == $last_seen_date) {
return $key;
}
if it is just return it
return $key;
so your php code would be like this
$arr = array(
0 =>
(object)array(
"countries" => "HK",
"last_seen_date" => "2016-09-17",
"ad_uid"=> "157d5908a1ca83"
),
1 =>
(object)array(
"countries" => "HK",
"last_seen_date" => "2016-09-20",
"ad_uid" => "157d7978513bc3"
),
2 =>
(object)array(
"countries" => "HK",
"last_seen_date" => "2016-09-26",
"ad_uid" => "157ea7239824e9"
)
);
function getAd_uid($last_seen_date, $values){
foreach($values as $key => $row) {
if ($row->last_seen_date == $last_seen_date) {
return $key;
}
}
}
echo '2016-09-17 is on index => '.getAd_uid('2016-09-17', $arr).'<br>';
echo '2016-09-20 is on index => '.getAd_uid('2016-09-20', $arr).'<br>';
echo '2016-09-26 is on index => '.getAd_uid('2016-09-26', $arr).'<br>';
RESULT
Working Demo
I need to sort the notification array by the value 'start_date' DESC order from the getNotifications function:
$posts_id_arr = getPoststIds($conn);
foreach ($posts_id_arr as $key => $val) {
$total_arr[$key] = [
'notification' => getNotifications($val['post_id'], $user_id, $conn)
];
}
$response_array = array('data' => $total_arr, 'more things' => $more_array);
echo json_encode($response_array);
right now the order is by post id due to the foreach loop.
data {
notification:
[
{
post_id: “1",
start_date: "2016-10-10 08:00:00",
},
{
post_id: “1",
start_date: "2016-10-10 12:00:00",
}
],
notification:
[
post_id: “2",
start_date: "2016-10-10 09:00:00",
},
{
post_id: “2",
start_date: "2016-10-10 13:00:00",
}
]
}
And i need it to be 1: 08:00, 2: 09:00, 1: 12:00, 2: 13:00
You can use a custom function to sort the values in the array using uasort. Your date format is sortable using strcmp - a date in the past is lower than a date in the future, so you can use this in your comparator.
function sort_by_date($a, $b) {
return strcmp($a->start_date, $b->start_date);
}
$sorted_posts = uasort($total_arr->notification, 'sort_by_date');
$response_array = array('data' => $sorted_posts, 'more things' => $more_array);
However, you don't need to perform the sort inside the foreach.
You can try the below code. Change the variable name accordingly.
foreach ($points as $key => $val) {
$time[$key] = $val[0];
}
array_multisort($time, SORT_ASC, $points);
This is because of the way array_multisort works. It sorts multiple arrays, and when the $time array is sorted, the $points array is re-ordered according to the array indices in $time. The array_multisort should come after the foreach, though
Hope this would be helpful.
If you want to sort with the inner array you can better prefer for the usort() method.
usort — Sort an array by values using a user-defined comparison function
This function will sort an array by its values using a user-supplied comparison function. If the array you wish to sort needs to be sorted by some non-trivial criteria, you should use this function.
<?php
function cmp($a, $b)
{
return strcmp($a["fruit"], $b["fruit"]);
}
$fruits[0]["fruit"] = "lemons";
$fruits[1]["fruit"] = "apples";
$fruits[2]["fruit"] = "grapes";
usort($fruits, "cmp");
while (list($key, $value) = each($fruits)) {
echo "\$fruits[$key]: " . $value["fruit"] . "\n";
}
?>
When sorting a multi-dimensional array, $a and $b contain references to the first index of the array.
The above example will output:
$fruits[0]: apples
$fruits[1]: grapes
$fruits[2]: lemons
Alternative Solution:
You can try array_multisort() since it will sort the array based on the order that you need.
$arr = your array;
$sort = array();
foreach($arr as $k=>$v) {
$sort['field'][$k] = $v['field'];
}
array_multisort($sort['field'], SORT_DESC, $arr);
echo "<pre>";
print_r($arr);
You can follow these steps:
1) Define a temporary array to store the dates:
foreach ($total_arr['notification'] as $vals) {
$temp_dates[] = $vals['start_date'];
}
2) Sort the newly created temp array using arsort():
arsort($temp_dates);
3) Use array_filter() and loop to find corresponding post ids and store to resulting array:
$i = 0;
foreach ($total_arr['notification'] as $vals) {
$res['notification'][] = array_filter($vals, function($val) {
return $val['start_date'] == $temp_dates[$i];
});
$i++;
}
$response_array = array('data' => $res, 'more things' => $more_array);
Note: Not sure if it works with duplicate start_dates.
As iblamefish mentioned, using uasort() is the way to go - it is simple an both memory and processing efficient, compared to all the other answers. But while strcmp() does produce good results for the SQL-style dates that you have, it is not the "correct" way to handle time fields - you should parse the times and compare them as time values:
$a = [
[ "name" => "foo", "date" => "2016-08-09 10:30:00" ],
[ "name" => "bar", "date" => "2016-01-09 02:00:00" ],
[ "name" => "baz", "date" => "2016-11-02 18:21:34" ]
];
uasort($a, function($a,$b) {
return (new DateTime($a->date))->getTimestamp() - (new DateTime($b->date))->getTimestamp();
});
var_dump($a);
produces:
array(3) {
[0] =>
array(2) {
'name' =>
string(3) "foo"
'date' =>
string(19) "2016-08-09 10:30:00"
}
[1] =>
array(2) {
'name' =>
string(3) "bar"
'date' =>
string(19) "2016-01-09 02:00:00"
}
[2] =>
array(2) {
'name' =>
string(3) "baz"
'date' =>
string(19) "2016-11-02 18:21:34"
}
}
Also, closures (anonymous functions) are more fun than using old-style "text callables").
I'm trying to count the number of times a certain value appears in my multidimensional array based on a condition. Here's an example array;
$fruit = array (
"oranges" => array(
"name" => "Orange",
"color" => "orange",
"taste" => "sweet",
"healthy" => "yes"
),
"apples" => array(
"name" => "Apple",
"color" => "green",
"taste" => "sweet",
"healthy" => "yes"
),
"bananas" => array(
"name" => "Banana",
"color" => "yellow",
"taste" => "sweet",
"healthy" => "yes"
),
"grapes" => array(
"name" => "Grape",
"color" => "green",
"taste" => "sweet",
"healthy" => "yes"
)
);
If I want to DISPLAY all green coloured fruit, I can do the following (let me know if this is the best way of doing it);
for ($row = 0; $row < 3; $row++) {
if($fruit[$row]["color"]=="green") {
echo $fruit[$row]["name"] . '<br />';
}
}
This will output;
Apple
Grape
That's great and I can see their are 2 values there, but how can I actually get PHP to count the number of fruit where the colour is green and put it in a variable for me to use further down the script to work stuff out? E.g. I want to do something like;
if($number_of_green_fruit > 1) { echo "You have more than 1 piece of green fruit"; }
I've taken a look at count(); but I don't see any way to add a 'WHERE/conditional' clause (a la SQL).
Any help would be really appreciated.
PHP has no support for a SQL where sort of thing, especially not with an array of arrays. But you can do the counting your own while you iterate over the data:
$count = array();
foreach($fruit as $one)
{
#$count[$one['color']]++;
}
printf("You have %d green fruit(s).\n", $count['green']);
The alternative is to write yourself some little helper function:
/**
* array_column
*
* #param array $array rows - multidimensional
* #param int|string $key column
* #return array;
*/
function array_column($array, $key) {
$column = array();
foreach($array as $origKey => $value) {
if (isset($value[$key])) {
$column[$origKey] = $value[$key];
}
}
return $column;
}
You then can get all colors:
$colors = array_column($fruit, 'color');
And then count values:
$count = array_count_values($colors);
printf("You have %d green fruit(s).\n", $count['green']);
That kind of helper function often is useful for multidimensional arrays. It is also suggested as a new PHP function for PHP 5.5.
$number_of_green_fruit = 0;
for ($row = 0; $row < 3; $row++) {
if($fruit[$row]["color"]=="green") {
$number_of_green_fruit++;
echo $fruit[$row]["name"] . '<br />';
}
}
All you need is an extra counter:
for ($row = $number_of_green_fruit = 0; $row < 3; $row++) {
if($fruit[$row]["color"]=="green") {
echo $fruit[$row]["name"] . '<br />';
$number_of_green_fruit++;
}
}
if($number_of_green_fruit > 1) {
echo "You have more than 1 piece of green fruit";
}
With PHP 5.4+ you can have this short snippet to count specific values (don't even need to declare the $count variable previously)
array_walk_recursive($fruit, function ($value) use (&$count) {
$count += (int) ($value === 'green');
});
var_dump($count); // Outputs: int(2)