PHP multidimensional array sort by value - php

Regarding this multidimensional array:
[
(int) 7 => [
(int) 0 => [
(int) 0 => '12:45',
(int) 1 => 'E1',
(int) 2 => 'B EXTREME 30'
],
(int) 1 => [
(int) 0 => '10:15',
(int) 1 => 'E1',
(int) 2 => 'B SHAPE 30'
],
],
(int) 1 => [
(int) 0 => [
(int) 0 => '09:30',
(int) 1 => 'E2',
(int) 2 => 'CYCLING VIRTUAL 50'
],
(int) 1 => [
(int) 0 => '10:30',
(int) 1 => 'E1',
(int) 2 => 'BODY PUMP VIRTUAL 60'
],
(int) 2 => [
(int) 0 => '11:45',
(int) 1 => 'E1',
(int) 2 => 'BODY BALANCE VIRTUAL 60'
],
],
(int) 2 => [
(int) 0 => [
(int) 0 => '14:45',
(int) 1 => 'E2',
(int) 2 => 'CYCLING VIRTUAL 50'
],
(int) 1 => [
(int) 0 => '17:00',
(int) 1 => 'E1',
(int) 2 => 'POSTURA ALONGAMENTO 60'
],
(int) 2 => [
(int) 0 => '09:15',
(int) 1 => 'E1',
(int) 2 => 'BODY PUMP 50'
],
]
]
The first key, of each first level array, are days of the week (day 7, day 1 and day 2).
The arrays inside each first level array contain hour (09:45), rooms (E1) and description (B EXTREME 30).
I tried to sort this multidimensional array by the second levels array hour value.
I used usort(), ksort(), array_multisort(), and some custom made functions for sorting the array as i need without luck.
The inside arrays must be sorted by ascending order, like this (example with day 2):
09:15 -> 14:45 -> 17:00
Does anyone knows how can i achieve this?

Assuming your data is called $data. Iterate the outer array, and apply a sort on each mid-level array, based on the time-part (in the innermost arrays). As your times are always formatted as "hh:ss", a string comparison in the usort callback does the job:
foreach ($data as &$events) {
usort($events, function($a, $b) {
return strcmp($a[0], $b[0]);
});
}
Note the & in the foreach: this makes sure you sort the original data, and not a copy of it.
If you want to create a new array, let's say $result, then do this (no & here!):
foreach ($data as $day => $events) {
usort($events, function($a, $b) {
return strcmp($a[0], $b[0]);
});
$result[$day] = $events;
}

usort — Sort an array by values using a user-defined comparison function.
Lets create a function where we compare time of two events.
This is part of your array
$array = [
2 => [
0 => [
0 => '14:45',
1 => 'E2',
2 => 'CYCLING VIRTUAL 50'
],
1 => [
0 => '17:00',
1 => 'E1',
2 => 'BODY PUMP VIRTUAL 60'
],
2 => [
0 => '09:15',
1 => 'E1',
2 => 'BODY BALANCE VIRTUAL 60'
],
],
];
This is an example how to sort items for one day (day 2)
usort($array[2], function ($a, $b) {
$time_a = strtotime($a[0]); // convert string to a unix timestamp to compare int
$time_b = strtotime($b[0]);
return $time_a - $time_b;
});
Output using print_r($array);
Array
(
[2] => Array
(
[0] => Array
(
[0] => 09:15
[1] => E1
[2] => BODY BALANCE VIRTUAL 60
)
[1] => Array
(
[0] => 14:45
[1] => E2
[2] => CYCLING VIRTUAL 50
)
[2] => Array
(
[0] => 17:00
[1] => E1
[2] => BODY PUMP VIRTUAL 60
)
)
)
To sort all days we do in a loop, passing each day array as a reference using &:
foreach ($array as &$day) {
usort($day, function ($a, $b) {
$time_a = strtotime($a[0]);
$time_b = strtotime($b[0]);
return $time_a - $time_b;
});
}

Related

How to remove duplicate in multidimensional array by comparing two values in each set. using php/laravel

I have an array with vehicle_code and year and vehicle_code repeating for the same year in array. I want to remove if vehicle code and year values repeat in an array. I tried many array function like in_array array_column array_search so on. but all example show how to remove duplicate in 1 column only but I need to compare values for two columns in each set.
0 => array:2 [
0 => "AUA3H147"
1 => 2015
2 => Audi
3 => 12457
]
1 => array:2 [
0 => "AUA3H147"
1 => 2015
2 => tata
3 => 16832545
]
2 => array:2 [
0 => "AUA3H148"
1 => 2016
2 => tata
3 => 55555
]
3 => array:2 [
0 => "AUA3H148"
1 => 2017
2 => Audi
3 => 55555
]
I need output like the below:
0 => array:2 [
0 => "AUA3H147"
1 => 2015
2 => Audi
3 => 12457
]
1 => array:2 [
0 => "AUA3H148"
1 => 2016
2 => tata
3 => 16832545
]
2 => array:2 [
0 => "AUA3H148"
1 => 2017
2 => tata
3 => 55555
]
I tried like
$newArray = array();
$addArrayValues = array();
$priceVehicleYearCounter = 0
foreach ( $excelPriceArr as $key => $line ) {
if ( (!in_array($line[0], $addArrayValues)) && (!in_array($line[1], $addArrayValues)) ) {
$addArrayValues[$priceVehicleYearCounter] = array(
'id' => $priceVehicleYearCounter,
'vehicle_code' => $line[0],
'year' => $line[1],
);
$priceVehicleYearCounter++;
$newArray[$priceVehicleYearCounter] = $line;
}
}
something like this I saw same example with single column search i modified it to search two values but it didn't work
if(array_search($line, array_column($addArrayValues, 'vehicle_code','year')) == false) {
$addArrayValues[$priceVehicleYearCounter] = array(
'id' => $priceVehicleYearCounter,
'vehicle_code' => $line[0],
'year' => $line[1],
);
$priceVehicleYearCounter++;
$newArray[$priceVehicleYearCounter] = $line;
}
also like this:
$result = array_unique($addArrayValues, SORT_REGULAR);
$result = array_map("unserialize", array_unique(array_map("serialize", $addArrayValues)));
sample set in array:
1 => array:12 [
0 => "AUA3H142"
1 => 2013
2 => "AUDI"
3 => "A3 SPORTBACK"
4 => "1.8 TFSI AMBITION (01/2011-04/2013)"
5 => "1.8 TFSI AMBITION"
6 => 2209
7 => 500
8 => 12023
9 => 125648
10 => 118835
11 => "Private"
]
Use a composite first-level key when populating the result array. The key must be the combination of the values from the two identifying columns.
When finished iterating, re-index the array with array_values().
This is more generally demonstrated here: Filter/Remove rows where column value is found more than once in a multidimensional array
The null coalescing assignment operator will only store the first occurring data for each composite key.
Code: (Demo)
foreach ($array as $row) {
$compositeKey = $row[0] . '_' . $row[1];
$result[$compositeKey] ??= $row; // only store if first occurrence of compositeKey
}
var_export(array_values($result)); // re-index and print

PHP sorting array of non-associative arrays but having string values at the beginning

$unresponsives = [
[
"Customer",
"172.52.46.75",
"2022-04-01 16:20:45",
"1817",
"nxlog",
"2327.02 Hours"
],
[
"Customer",
"172.25.89.45",
"2022-04-01 16:20:45",
"1817",
"nxlog",
"2327.02 Hours"
],
[
"Customer",
"172.19.10.94",
"2022-04-01 16:20:45",
"1817",
"nxlog",
"2327.02 Hours"
]]
This is an example from my array of arrays. I want to sort the arrays inside by their fifth element (hours) in descending order. I am able to achieve this with usort but in the array there are also arrays with the string "Undefined" as their fifth value. Example below:
[
"PreProd",
"178.18.15.12",
"\/",
"1502",
"iis",
"Undefined"
]
Currently they are listed at the bottom of the array after the sorting is done. I instead want them to be listed in the beginning of the array. So first arrays with undefined ones and then the rest in descending order. How can I achieve this?
Below is the usort function that I use:
usort($unresponsives, function ($unresponsive1, $unresponsive2) {
return floatval($unresponsive2[5]) <=> floatval($unresponsive1[5]);
});
array_multisort Approach - Example https://3v4l.org/SuJQX
For a slightly more complex approach that allows fine-grained control of the sorting as desired. Use array_column to retrieve the hours. Use array_keys on the hours to determine the filtered value positions and iterate over the positions to assign them as a numeric value. Then array_multisort can be used with the desired flags to sort the resulting arrays.
$hours = array_column($unresponsives, 5);
if ($undefined = array_keys($hours, 'Undefined')) {
$hours = array_replace($hours, array_fill_keys($undefined, PHP_INT_MAX));
}
array_multisort($hours, SORT_DESC, SORT_NUMERIC, $unresponsives);
Result
var_export($unresponsives);
array (
0 =>
array (
0 => 'Customer',
1 => '172.19.10.94',
2 => '2022-04-01 16:20:45',
3 => '1817',
4 => 'nxlog',
5 => 'Undefined',
),
1 =>
array (
0 => 'Customer',
1 => '172.25.89.45',
2 => '2022-04-01 16:20:45',
3 => '1817',
4 => 'nxlog',
5 => 'Undefined',
),
2 =>
array (
0 => 'Customer',
1 => '172.52.46.75',
2 => '2022-04-01 16:20:45',
3 => '1817',
4 => 'nxlog',
5 => '2328.02 Hours',
),
3 =>
array (
0 => 'Customer',
1 => '172.19.10.94',
2 => '2022-04-01 16:20:45',
3 => '1817',
4 => 'nxlog',
5 => '2324.02 Hours',
),
4 =>
array (
0 => 'Customer',
1 => '172.19.10.94',
2 => '2022-04-01 16:20:45',
3 => '1817',
4 => 'nxlog',
5 => '2322.02 Hours',
),
)
Ascending Numeric Sorting
To change the sort order to SORT_ASC swap out PHP_INT_MAX for PHP_INT_MIN, depending on where in the array you want the filtered value to reside.
$hours = array_column($unresponsives, 5);
if ($undefined = array_keys($hours, 'Undefined')) {
$hours = array_replace($hours, array_fill_keys($undefined, PHP_INT_MIN));
}
array_multisort($hours, SORT_ASC, SORT_NUMERIC, $unresponsives);
Result
var_export($unresponsives);
array (
0 =>
array (
0 => 'Customer',
1 => '172.19.10.94',
2 => '2022-04-01 16:20:45',
3 => '1817',
4 => 'nxlog',
5 => 'Undefined',
),
1 =>
array (
0 => 'Customer',
1 => '172.25.89.45',
2 => '2022-04-01 16:20:45',
3 => '1817',
4 => 'nxlog',
5 => 'Undefined',
),
2 =>
array (
0 => 'Customer',
1 => '172.19.10.94',
2 => '2022-04-01 16:20:45',
3 => '1817',
4 => 'nxlog',
5 => '2322.02 Hours',
),
3 =>
array (
0 => 'Customer',
1 => '172.19.10.94',
2 => '2022-04-01 16:20:45',
3 => '1817',
4 => 'nxlog',
5 => '2324.02 Hours',
),
4 =>
array (
0 => 'Customer',
1 => '172.52.46.75',
2 => '2022-04-01 16:20:45',
3 => '1817',
4 => 'nxlog',
5 => '2328.02 Hours',
),
)
usort Approach - Example https://3v4l.org/Uoml6
The same methodology of assigning the filtered value as a numeric value can be applied to usort by manipulating the compared values of the desired filtered value(s) in a conditional, which will only be used to sort the array.
Descending Order
function compd($a, $b)
{
/*
# Condensed Syntax
return ('Undefined' === $b[5] ? PHP_INT_MAX : floatval($b[5])) <=> ('Undefined' === $a[5] ? PHP_INT_MAX : floatval($a[5]));
*/
$v1 = $a[5];
$v2 = $b[5];
if ('Undefined' === $v1) {
$v1 = PHP_INT_MAX;
}
if ('Undefined' === $v2) {
$v2 = PHP_INT_MAX;
}
return floatval($v2) <=> floatval($v1);
}
usort($unresponsives, 'compd');
Ascending Order
As with the array_multisort approach change the sort order by swapping PHP_INT_MAX with PHP_INT_MIN but also swap the $b[5] <=> $a[5] comparison with $a[5] <=> $b[5] to sort the values in ascending order.
function compa($a, $b)
{
/*
# Condensed Syntax
return ('Undefined' === $a[5] ? PHP_INT_MIN : floatval($a[5])) <=> ('Undefined' === $b[5] ? PHP_INT_MIN : floatval($b[5]));
*/
$v1 = $a[5];
$v2 = $b[5];
if ('Undefined' === $v1) {
$v1 = PHP_INT_MIN;
}
if ('Undefined' === $v2) {
$v2 = PHP_INT_MIN;
}
return floatval($v1) <=> floatval($v2);
}
usort($unresponsives, 'compa');
I recommend two steps.
Populate an array with ready-to-compare numeric values.
Call array_multisort() to compare in a DESC direction.
Code: (Demo)
array_multisort(
array_map(
fn($a) => sscanf($a[5], '%f')[0] ?? PHP_INT_MIN,
$unresponsives
),
SORT_DESC,
$unresponsives
);
var_export($unresponsives);
This can also be performed with absolutely no iterated function calls for better efficiency.
The first rule checks for Undefined values and orders true evaluations before false evaluations. The second rule is only applied when a tiebreaker is required -- in which case the float values are compared for a DESC sort direction.
Code: (Demo)
usort(
$unresponsives,
fn($a, $b) =>
[$b[5] === 'Undefined', (float) $b[5]]
<=>
[$a[5] === 'Undefined', (float) $a[5]]
);
var_export($unresponsives);
A similar approach can be found here.
Please have a look at bellow snippet
echo '<pre>';
function ShortFunction($new,$old){
return floatval($old[5]) <=> floatval($new[5]);
}
$new_array = usort($unresponsives,'ShortFunction');
print_r($unresponsives);

Restructuring a PHP multidimensional array that uses array_count_values and array_map

Using Laravel 5.6, I am creating a multidimensional array that looks like this:
array:8 [
0 => array:2 [
0 => "ELA-2"
1 => 7
]
1 => array:2 [
0 => "Science-3"
1 => 9
]
2 => array:2 [
0 => "ELA-1"
1 => 5
]
3 => array:2 [
0 => "Science-2"
1 => 9
]
4 => array:2 [
0 => "ELA-4"
1 => 2
]
5 => array:2 [
0 => "ELA-3"
1 => 7
]
6 => array:2 [
0 => "Science-4"
1 => 2
]
7 => array:2 [
0 => "Science-1"
1 => 1
]
]
This was created using array_count_values and array_map (if that matters).
What I want is the data to look like this:
array:8 [
0 => array:3 [
"Subject" => "ELA"
"Level" => 2
"Count" => 7
]
1 => array:3 [
"Subject" => "Science"
"Level" => 3
"Count" => 9
]
2 => array:3 [
"Subject" => "ELA"
"Level" => 1
"Count" => 5
]
3 => array:3 [
"Subject" => "Science"
"Level" => 2
"Count" => 9
]
4 => array:3 [
"Subject" => "ELA"
"Level" => 4
"Count" => 2
]
5 => array:3 [
"Subject" => "ELA"
"Level" => 3
"Count" => 7
]
6 => array:3 [
"Subject" => "Science"
"Level" => 4
"Count" => 2
]
7 => array:3 [
"Subject" => "Science"
"Level" => 1
"Count" => 1
]
]
I am not sure how to:
break apart the values from key[0] within each array and separate them into 2 parts—the first portion for the 'Subject' and the second portion for the 'Level'
relabel the numeric incrementing keys (there will be 3 within each array) to the words "Subject", "Level", and "Count", respectively.
My current code looks like this:
// create an empty array
$array_holder = [];
// loop through each student
foreach($class_students as $student) {
// loop through each subject
foreach($class_subjects as $subject) {
// count each child at his/her level (rounded average)
$childs_level = AssessmentData::where('student_id', $student->id)->where('subject_id', $subject->subject_id)->avg('assessed_level');
// get the subject name
$current_subject = Subject::where('id', $subject->subject_id)->first();
// round the average
$childs_level = round($childs_level);
// convert the childs_level into a whole number
$childs_level = number_format($childs_level, 0);
// add each child by appending to an array
if ($childs_level != 0) {
$compiled_array[] = array_push($array_holder, $current_subject->short_name."-".$childs_level);
}
}
}
// count each assessed_level from the resultant array
$counted_array = array_count_values($array_holder);
// remap the array
$counted_array = array_map(null, array_keys($counted_array), $counted_array);
Additional request - sorting
I would like the results to be sorted by count so that Level 1 is first, Level 2 is second, Level 3 is third, and Level 4 is fourth. (Currently, it is sorted in the opposite order - Level 4 is first, Level 3 is second, etc.)
You have to loop the original array and create a new one:
<?php
$original_array = [
['ELA-2', 7],
['Science-3', 9],
['ELA-1', 5]
// you can fill in the rest
];
$new_array = [];
foreach($original_array as $v)
{
$new_array[] = [
'Subject' => explode('-', $v[0])[0], // split the subject on "-" and retrieve the first item
'Level' => explode('-', $v[0])[1], // split the subject on "-" and retrieve the second item
'Count' => $v[1] // this is given to us so just use it
];
}
// Sort the finished product in ascending order based on the "count" of the sub-arrays
//
// You can also use this earlier on $original_array if you want
// but you would just change the arithmetic to $a[ 1 ] - $b [ 1 ]
usort( $new_array, function( $a, $b ){
return $a[ 'Count' ] - $b[ 'Count' ];
} );
print_r($new_array);
Output:
Array
(
[0] => Array
(
[Subject] => ELA
[Level] => 1
[Count] => 5
)
[1] => Array
(
[Subject] => ELA
[Level] => 2
[Count] => 7
)
[2] => Array
(
[Subject] => Science
[Level] => 3
[Count] => 9
)
)
If you wanted to get fancy and array_map() it then this is achieves the same result as foreach(){}:
$new_array = array_map(
function($v){
return [
'Subject' => explode('-', $v[0])[0], // split the subject on "-" and retrieve the first item
'Level' => explode('-', $v[0])[1], // split the subject on "-" and retrieve the second item
'Count' => $v[1] // this is given to us so just use it
];
},
$original_array
);
I want to add an alternative approach.
Important notes:
don't ask PHP to perform the same explode() call on the same string twice -- cache it as a temporary variable, or as shown in my snippet use all generated elements at once.
limit your explode() call by declaring the limit parameter (3rd param) so that the output is consistent and the script is clear about the intended action (seeing the input data is not required to understand the intention).
the spaceship operator in usort() is very versatile and will allow you to make a variety of comparisons without changing syntax.
Code: (Demo)
$counted_array = [
["ELA-2", 7],
["Science-3", 9],
["ELA-1", 5],
["Science-2", 9],
["ELA-4", 2],
["ELA-3", 7],
["Science-4", 2],
["Science-1", 1]
];
$assoc_array = array_map(
function($subarray){
return array_combine(
['Subject', 'Level'], explode('-', $subarray[0], 2)
)
+ ['Count' => $subarray[1]];
},
$counted_array
);
usort($assoc_array, function($a, $b) {
return [$a['Subject'], $a['Level']]
<=>
[$b['Subject'], $b['Level']];
});
var_export($assoc_array);
I noticed in your question, you asked for the data to be sorted on Count, but then proceeded to explain how you expected the sorting by Level -- this is ambiguous.
If you want to sort on Count, just use:
usort($assoc_array, function($a, $b) {
return $a['Count'] <=> $b['Count'];
});

How to sort by primary date key and secondary alphanumeric in PHP

So I have a 2D array that looks a bit like this:
[0] => Array(
[date] => 23-01-2017
[name] => bbb
[othertext] => text2
)
[1] => Array(
[date] => 23-01-2017
[name] => aaa
[othertext] => text3
)
[2] => Array(
[date] => 24-01-2017
[name] => aaa
[othertext] => text1
)
NOTE: This question is not tagged as MySQL, the database used is MongoDB with the sort type of 'date' => 'asc'.
Currently this is returned from my database sorted by date, but does not take into account the name property. I'd like to now sort this by date, and for entries with the same date to then sort by name.
My current approach is to run an array_multisort on the data:
array_multisort(
$array['date'], SORT_STRING,
$array['name'], SORT_STRING,
$arrayCopy //<--This copy of the array has the full datetime object
);
But this then sorts the date as a string, so in certain scenarios it sorts it incorrectly; e.g. 1st March would go before the 2nd Feb. If putting the month first then again it sorts incorrectly when Dec/Jan dates are sorted.
What's the correct approach to this? I've seen mention of usort(), but I'm unsure how to implement it in this use case. Does array_multisort have functionality for dates?
In your database query:
SELECT * from `your_table_name` order by date asc, name asc;
Maybe this in mongodb:
$cursor->sort(array('date' => 1, 'name' => 1));
See: http://php.net/manual/en/mongocursor.sort.php
No need to do it in php afterwards.
I would strongly suggest doing this via the database.
But if you must use usort or are trying to understand how it works:
$arr = [
[
'date' => '23-01-2017',
'name' => 'bbb',
],
[
'date' => '23-01-2017',
'name' => 'aaa',
],
[
'date' => '24-01-2017',
'name' => 'aaa',
],
];
function cmp($a, $b)
{
$aDate = DateTime::createFromFormat('d-m-Y', $a['date']);
$bDate = DateTime::createFromFormat('d-m-Y', $b['date']);
if ($aDate == $bDate) {
if ($a['name'] == $b['name']) {
return 0;
}
return ($a['name'] < $b['name']) ? -1 : 1;
}
return ($aDate < $bDate) ? -1 : 1;
}
usort($arr, "cmp");
print_r($arr);
http://php.net/manual/en/function.usort.php
Outputs:
[0] => Array
(
[date] => 23-01-2017
[name] => aaa
)
[1] => Array
(
[date] => 23-01-2017
[name] => bbb
)
[2] => Array
(
[date] => 24-01-2017
[name] => aaa
)
With usort-function you could do:
$foo = array(
0 => array(
"date" => "23-01-2017",
"name" => "bbb",
"othertext" => "text2"
),
1 => array(
"date" => "23-01-2017",
"name" => "aaa",
"othertext" => "text3"
),
2 => array(
"date" => "24-01-2017",
"name" => "aaa",
"othertext" => "text1"
)
);
usort($foo, function($a, $b)
{
return $a["date"] === $b["date"] ? strcmp($a["name"], $b["name"]) : strcmp(strtotime($a["date"]), strtotime($b["date"]));
});
var_dump($foo);

Append two arrays having same key inside one array differently

I have two array in php likes this :
$a = [ 1 => [ 0 => 10, 1 => 1 ] ] and $b = [ 1 => [ 0 => 15, 1 => 3 ] ]
I have to make a union of these two arrays that resultant array should be like this :
$r = [ 1 => [ 0 => 10, 1 => 1 ], 1 => [ 0 => 15, 1 => 3 ] ]
Please give me idea how can i achieve this ..
Thanks in advance !!
You can't give same array index....index is unique
$r[] = $a;
$r[] = $b;
so your array will be
$r = [ [0]=>[1 => [ 0 => 10, 1 => 1 ]], [1] =>[1 => [ 0 => 15, 1 => 3 ] ]]
You can't have an array with the same key.
If you want to merge values of two arrays, use array_merge:
$a = array(
0 => 10,
1 => 1 ) ;
print_r($a);
echo "<br>";
$b = array(0 => 15,
1 => 3);
print_r($b);
echo "<br>";
$result = array_merge($a, $b);
print_r($result);

Categories