I am working on gaming project, which needs sort and shuffle multidimensional array. First i need to sort based on bid. If multiple bids are same i need to sort based on priority. If bid & priority both are same means I need to shuffle those elements. For example we have 3 array elements in bid=0.4 & priority=4. Id's are 102,103 & 104. These array element position should be shuffled.
array(
array('id' => 101, 'bid' => 0.5, 'priority' => 5),
array('id' => 102, 'bid' => 0.4, 'priority' => 4),
array('id' => 103, 'bid' => 0.4, 'priority' => 4),
array('id' => 104, 'bid' => 0.4, 'priority' => 4),
array('id' => 105, 'bid' => 0.3, 'priority' => 5),
array('id' => 106, 'bid' => 0.3, 'priority' => 5),
array('id' => 107, 'bid' => 0.2, 'priority' => 5),
array('id' => 108, 'bid' => 0.7, 'priority' => 5),
array('id' => 108, 'bid' => 0.1, 'priority' => 4)
);
You should take a look at array_multisort to do that. There are examples on that page on how to sort a multi-dimensional array
<?php
// Obtain a list of columns
foreach ($data as $key => $row) {
$bid[$key] = $row['bid'];
$prio[$key] = $row['priority'];
}
array_multisort($bid, SORT_ASC, $prio, SORT_ASC, $data);
?>
To shuffle I would add an extra column to the multidimensional array called "rand" and then fill it with a random number before you use multisort. Then you can add a 3th sort order on that column for shuffling.
Based on idea #Hugo's answer about using random weight:
$array = array(
array('id' => 101, 'bid' => 0.5, 'priority' => 5),
array('id' => 102, 'bid' => 0.4, 'priority' => 4),
array('id' => 103, 'bid' => 0.4, 'priority' => 4),
array('id' => 104, 'bid' => 0.4, 'priority' => 4),
array('id' => 105, 'bid' => 0.3, 'priority' => 5),
array('id' => 106, 'bid' => 0.3, 'priority' => 5),
array('id' => 107, 'bid' => 0.2, 'priority' => 5),
array('id' => 108, 'bid' => 0.7, 'priority' => 5),
array('id' => 108, 'bid' => 0.1, 'priority' => 4)
);
function cmp(&$a, &$b) { # notice the use of & in function signature
if ($a['bid'] - $b['bid']) {
return $a['bid'] - $b['bid'] > 0 ? 1 : -1; # bid is different, sort using bid
} else if ($a['priority'] - $b['priority']) {
return $a['priority'] - $b['priority'] > 0 ? 1 : -1; # priority is different, sort using priority
} else {
if (isset($a['rw']) == false) {
$a['rw'] = rand(1, 100); # assign random tie breaker
}
if (isset($b['rw']) == false) {
$b['rw'] = rand(1, 100); # assign random tie breaker
}
if ($a['rw'] - $b['rw']) {
return $a['rw'] - $b['rw'] > 0 ? 1 : -1; # sort using random weight
} else {
return 0;
}
}
}
usort($array, 'cmp');
var_dump($array);
Output
array(9) {
[0]=>array(3) {["id"]=>int(108) ["bid"]=>float(0.1) ["priority"]=>int(4)}
[1]=>array(3) {["id"]=>int(107) ["bid"]=>float(0.2) ["priority"]=>int(5)}
[2]=>array(4) {["id"]=>int(106) ["bid"]=>float(0.3) ["priority"]=>int(5) ["rw"]=>int(70)}
[3]=>array(4) {["id"]=>int(105) ["bid"]=>float(0.3) ["priority"]=>int(5) ["rw"]=>int(73)}
[4]=>array(4) {["id"]=>int(103) ["bid"]=>float(0.4) ["priority"]=>int(4) ["rw"]=>int(29)}
[5]=>array(4) {["id"]=>int(104) ["bid"]=>float(0.4) ["priority"]=>int(4) ["rw"]=>int(67)}
[6]=>array(4) {["id"]=>int(102) ["bid"]=>float(0.4) ["priority"]=>int(4) ["rw"]=>int(80)}
[7]=>array(3) {["id"]=>int(101) ["bid"]=>float(0.5) ["priority"]=>int(5)}
[8]=>array(3) {["id"]=>int(108) ["bid"]=>float(0.7) ["priority"]=>int(5)}
}
Usort would be ideal for this situation http://www.php.net/manual/en/function.usort.php
You define the function that does the comparisons
<?php
function cmp($a, $b)
{
if ($a['bid'] == $b['bid']) {
if ($a['priority'] == $b['priority']) return 0;
return ($a['priority'] < $b['priority']) ? -1 : 1;
}
return ($a['bid'] < $b['bid']) ? -1 : 1;
}
usort($data, "cmp");
?>
Also based on #Hugos answer, adding shuffle using PHP built in shuffle() and range() functions:
<?php
$data=array(
array('id'=>101,'bid'=>0.5,'priority'=>5),
array('id'=>102,'bid'=>0.4,'priority'=>4),
array('id'=>103,'bid'=>0.4,'priority'=>4),
array('id'=>104,'bid'=>0.4,'priority'=>4),
array('id'=>105,'bid'=>0.3,'priority'=>5),
array('id'=>106,'bid'=>0.3,'priority'=>5),
array('id'=>107,'bid'=>0.2,'priority'=>5),
array('id'=>108,'bid'=>0.7,'priority'=>5),
array('id'=>108,'bid'=>0.1,'priority'=>4)
);
$rand=range(0,count($data)-1);
shuffle($rand);
foreach ($data as $key => $row) {
$bid[$key] = $row['bid'];
$prio[$key] = $row['priority'];
}
array_multisort($bid, SORT_ASC, $prio, SORT_ASC, $rand, SORT_ASC, $data);
I first tried simply shuffling the array before sorting, but for some unknown reason, sorting seems to sort on id as well. Probably some effect of algorithm used.
You can use usort
usort($array,function ($a, $b) {
if ($a['id'] == $b['id']) {
if ($a['priority'] == $b['priority'] && $a['bid'] == $b['bid']) {
$pos = array(-1,0,1);
return $pos[mt_rand(0, 2)]; // shurffle
}
$a = $a['priority'];
$b = $b['priority'];
return ($a == $b) ? 0 : (($a > $b) ? - 1 : 1); // sorty by priority
}
// First i need to sort based on bid
$a = $a['id'];
$b = $b['id'];
return ($a == $b) ? 0 : (($a < $b) ? - 1 : 1); //sort by bid id
});
<?php
foreach ($data as $key => $row) {
//just create a random field
$data[$key]['randSort']= rand(1,999999);
$sameRand[$key] = $data[$key]['randSort'];
$bid[$key] = $row['bid'];
$prio[$key] = $row['priority'];
}
array_multisort($bid, SORT_ASC, $prio, SORT_ASC, $sameRand, SORT_ASC, $data);
?>
put elements to be shuffled into an array and remove them from the initial array until the initial array is empty.
call shuffle on the created array
put the shuffled array into a new array with the results.
Related
I want to first get the keys with the same values and then keep the keys with the longest string. Here is the code compiled so far:
<?php
$data = array('Anna' => 1, 'Ann' => 1, 'Tommy' => 100, 'Tom' => 100);
$total = array_count_values($data);
$filtered = array_filter($data, function ($value) use ($total) {
return $total[$value] > 1;
});
print_r($filtered);
?>
The current output:
Array ( [Anna] => 1 [Ann] => 1 [Tommy] => 100 [Tom] => 100 )
My expected output:
Array ( [Anna] => 1 [Tommy] => 100)
Many thanks for help.
Not necessarily the most optimized solution, but you could write it up using simple check:
$data = array('Anna' => 1, 'Ann' => 1, 'Tommy' => 100, 'Tom' => 100, 'Dan' => 200, 'Danny' => 200);
$total = array_count_values($data);
$filtered = array_filter($data, function ($value) use ($total) {
return $total[$value] > 1;
});
foreach($data as $key => $value) {
$foundKey = array_search($value,$filtered);
if($foundKey){
if(strlen($foundKey) < strlen($key)){
unset($filtered[$foundKey]);
} elseif(strlen($foundKey) > strlen($key)) {
unset($filtered[$key]);
}
}
}
print_r($filtered);
Not sure to post it after even answer is accepted but I have simple hack for this using ksort() and array_flip():
$data = array('Anna' => 1, 'Ann' => 1, 'Tommy' => 100, 'Tom' => 100, 'Dan' => 200, 'Danny' => 200);
ksort($data);
$result=array_flip(array_flip($data));
print_r($result);
ksort(): Sort an array by key
array_flip(): Flip all keys with their associated values in an array
Demo
I need to sort my multidimensional array using two rules.
My array looks like this:
$array = [
['label' => 12, 'countValue' => 5],
['label' => 4, 'countValue' => 78],
['label' => 9, 'countValue' => 5],
['label' => 64, 'countValue' => 0],
['label' => 3, 'countValue' => 60],
['label' => 19, 'countValue' => 0],
['label' => 7, 'countValue' => 5],
];
I need rows with ['countValue'] = 0 to be move to the back and
Sort the rows based on their label value in an ascending direction.
Desired result:
$array = [
['label' => 3, 'countValue' => 60],
['label' => 4, 'countValue' => 78],
['label' => 7, 'countValue' => 5],
['label' => 9, 'countValue' => 5],
['label' => 12, 'countValue' => 5],
['label' => 19, 'countValue' => 0],
['label' => 64, 'countValue' => 0],
];
I have the following code:
public function sortOptionsByName($a, $b)
{
$x = trim($a['label']);
$y = trim($b['label']);
if ($x == '') return 1;
if ($y == '') return -1;
if (is_numeric($x) && is_numeric($y)){
if ($x == $y)
return 0;
return ($x > $y ? 1 : -1);
}
else {
return strcasecmp($x, $y);
}
}
public function sortOptionsByCounts($a, $b)
{
if ($a['countValue'] == $b['countValue']) {
return 0;
}
return ($a['countValue'] < $b['countValue'] ? 1 : -1);
}
Something like...
public function sortOptionsByCountsAndByName($a, $b)
{
if ($a['countValue'] == 0 && $b['countValue'] == 0) {
return -2
}
else {
$this->sortOptionsByName($a, $b)
}
}
First compare values with zero. PHP casts boolean to integer, so you can just subtract to get -1, 0 , 1. And then compare another value when thw 1st comparing returns 0
public function sortOptionsByCountsAndByName($a, $b)
{
$res = ($a['countValue'] == 0) - ($b['countValue'] == 0);
return ($res ? $res : $this->sortOptionsByName($a, $b));
}
You need to sort your data twice, once by name and once by count value. I would suggest to use usort() and implement two custom compare methods:
<?php
function cmpName($a, $b) {
return strnatcmp($a['Name'], $b['Name']);
}
function cmpCountValue($a, $b) {
if ($a['CountValue'] == 0)
return 1;
if ($b['CountValue'] == 0)
return -1;
return cmpName($a, $b);
}
$a[0]['Name'] = 'Bob';
$a[0]['CountValue'] = 0;
$a[1]['Name'] = 'Christal';
$a[1]['CountValue'] = 42;
$a[2]['Name'] = 'Alice';
$a[2]['CountValue'] = 23;
$a[3]['Name'] = 'Jack';
$a[3]['CountValue'] = 1;
$a[4]['Name'] = 'Alex';
$a[4]['CountValue'] = 58;
usort($a, "cmpName");
usort($a, "cmpCountValue");
foreach ($a as $item) {
echo $item['Name'] . ": " . $item['CountValue'] . "<br />";
}
?>
The output is:
Alex: 58
Alice: 23
Christal: 42
Jack: 1
Bob: 0
Online demo: See here
Just bake your sorting rules into two arrays with symmetric elements and separate them with a spaceship operator.
When sorting ASC, false comes before true, so by checking if the countValue is "falsey", true evaluations will be pushed to the back of the pack.
The order of the elements in the sorting callback says:
Prioritize non-zero countValue rows.
When there is a tie on the first rule, break the tie by sorting on label ASC.
Code: (Demo)
usort(
$array,
fn($a, $b) =>
[!$a['countValue'], $a['label']]
<=>
[!$b['countValue'], $b['label']]
);
var_export($array);
Output:
array (
0 =>
array (
'label' => 3,
'countValue' => 60,
),
1 =>
array (
'label' => 4,
'countValue' => 78,
),
2 =>
array (
'label' => 7,
'countValue' => 5,
),
3 =>
array (
'label' => 9,
'countValue' => 5,
),
4 =>
array (
'label' => 12,
'countValue' => 5,
),
5 =>
array (
'label' => 19,
'countValue' => 0,
),
6 =>
array (
'label' => 64,
'countValue' => 0,
),
)
am trying to get output of following array in one format. its not getting
<?php
$distance_covered = array( '1_JAN_2017' => array('DRIVER_1' => array(2, 5, 3),'DRIVER_2' => array(3, 2, 6, 9)),
'2_JAN_2017' => array('DRIVER_1' => array(3, 9), 'DRIVER_3' => array(1, 4, 8)),
'3_JAN_2017' => array('DRIVER_4' => array(9), 'DRIVER_1' => array(2, 7, 5, 2)),
'4_JAN_2017' => array('DRIVER_1' => array(5, 3, 3, 2), 'DRIVER_4' => array(4, 9, 8, 5)),
'5_JAN_2017' => array('DRIVER_2' => array(8, 5), 'DRIVER_5' => array(3, 9, 7)),
'6_JAN_2017' => array('DRIVER_5' => array(2, 1, 7, 5), 'DRIVER_4' => array(1, 9, 6)),
'7_JAN_2017' => array('DRIVER_4' => array(5, 2, 9), 'DRIVER_3' => array(4, 1, 6)), );
The above is my array
i want output in the following format
Output: Array ( [DRIVER_1] => 51, [DRIVER_2] => 33, [DRIVER_3] => 24, [DRIVER_4] => 67, [DRIVER_5] => 34 )
this is the sum of distance travelled by each driver in all trips
i tried code like this,anybody knows please help
$res = array();
foreach($distance_covered as $value) {
foreach($value as $key => $number) {
(!isset($res[$key])) ?
$res[$key] = $number :
$res[$key] += $number;
}
}
print_r($res);
?>
This one works for me
$res = array();
foreach($distance_covered as $value)//the array which you have given us
{
foreach($value as $key => $number) //loop over array of date
{
if(!isset($res[$key]))//check if the key exist in over defined array if no then run this
{
$res[$key] = array_sum($number);// Sum all distances of that driver
continue;//set the key and continue the foreach...
}
$res[$key] += array_sum($number);// Sum all distances of that driver
}
}
print_r($res);
die;
And the Output is
Array
(
[DRIVER_1] => 51
[DRIVER_2] => 33
[DRIVER_3] => 24
[DRIVER_4] => 67
[DRIVER_5] => 34
)
This should work:
$res = array();
foreach($distance_covered as $value) {
foreach($value as $key => $number) {
foreach ($number as $n) {
if (isset($res[$key])) {
$res[$key] += $n;
} else {
$res[$key] = $n;
}
}
}
}
print_r($res);
Just traverse through array of arrays.
$distance_covered = array(
'1_JAN_2017' => array('DRIVER_1' => array(2, 5, 3),'DRIVER_2' => array(3, 2, 6, 9)),
'2_JAN_2017' => array('DRIVER_1' => array(3, 9), 'DRIVER_3' => array(1, 4, 8)),
'3_JAN_2017' => array('DRIVER_4' => array(9), 'DRIVER_1' => array(2, 7, 5, 2)),
'4_JAN_2017' => array('DRIVER_1' => array(5, 3, 3, 2), 'DRIVER_4' => array(4, 9, 8, 5)),
'5_JAN_2017' => array('DRIVER_2' => array(8, 5), 'DRIVER_5' => array(3, 9, 7)),
'6_JAN_2017' => array('DRIVER_5' => array(2, 1, 7, 5), 'DRIVER_4' => array(1, 9, 6)),
'7_JAN_2017' => array('DRIVER_4' => array(5, 2, 9), 'DRIVER_3' => array(4, 1, 6)), );
// Counting.
$merged = [];
foreach ($distance_covered as $day => $stats) {
foreach ($stats as $driver => $distances) {
if (!isset($merged[$driver])) {
$merged[$driver] = 0;
}
$merged[$driver] += array_sum($distances);
}
}
// Display.
echo "<pre>";
print_r($merged);
echo "</pre>";
You are close, but...
$res = array ();
foreach ( $distance_covered as $value ) {
foreach ( $value as $key=> $driver ) {
if ( isset($res[$key]) == false ){
$res[$key]=0;
}
$res[$key] += array_sum($driver);
}
}
print_r($res);
The first foreach simply splits the data down to the days.
The second one returns elements like $key = 'DRIVER_1' and $driver = array(3, 9).
If this is the first time you've encountered this driver, then you need to ensure that the element in $res exists, so set it to 0.
Once you know there is an element there, you can add in the sum of the values ( 3 & 9 in this case ) using the += array_sum($driver) bit. The += is simply adding to rather than having to say a=a+b, you can say a+=b.
[sarcastic voice] I can't believe everybody overlooked this convoluted function-based one-liner...
Code: (Demo)
var_export(array_map('array_sum', array_merge_recursive(...array_values($distance_covered))));
Output:
array (
'DRIVER_1' => 51,
'DRIVER_2' => 33,
'DRIVER_3' => 24,
'DRIVER_4' => 67,
'DRIVER_5' => 34,
)
*this is virtually guaranteed to process slower than any other posted answer.
Remove the first level associative keys (date strings) with array_values()
Unpack the array of arrays with the "splat operator" (...) and feed to array_merge_recursive() to group values
Sum the subarray values by calling array_sum() on each subarray with array_map()
(This is merely an exercise of thinking outside the box.)
Beyond that no one suggested using a null coalescing operator, so I'll post what that can look like:
$driver_totals = [];
foreach ($distance_covered as $daily_log) {
foreach ($daily_log as $driver => $distances) {
$driver_totals[$driver] = ($driver_totals[$driver] ?? 0) + array_sum($distances);
}
}
var_export($driver_totals);
And if you have a special scenario where you only need to know the distance for a single specific driver, you can call upon array_column() like this:
$target_driver = 'DRIVER_4';
$total_distance = 0;
foreach (array_column($distance_covered, $target_driver) as $distances) {
$total_distance += array_sum($distances);
}
echo "{$target_driver} drove for a distance of {$total_distance}";
*Notice that the order of the drivers within each date array is inconsequential because array_column() is smart enough to find the desired distance subarray.
Finally, if you declare a whitelist array of all possible drivers, you can:
control the order of the drivers in the output
avoid the iterated isset() conditions
ensure that drivers without any distance records are included in the output
Code:
$roster = ['DRIVER_6', 'DRIVER_5', 'DRIVER_4', 'DRIVER_3', 'DRIVER_2', 'DRIVER_1'];
$driver_totals = array_fill_keys($roster, 0);
foreach ($distance_covered as $daily_log) {
foreach ($daily_log as $driver => $distances) {
$driver_totals[$driver] += array_sum($distances);
}
}
var_export($driver_totals);
What I'm trying to do is sort a multidimensional array by a number but have any values equal to 0, to be at the end of the array.
This code works for sorting with 0's and single digits, but it seems to break when different numbers are added to it.
The output needs to be:
41,42,43,44,45,46,0,0,0
<?php
$array = array(
array(
"position" => 41,
),
array(
"position" => 43,
),
array(
"position" => 42,
),
array(
"position" => 44,
),
array(
"position" => 45,
),
array(
"position" => 0,
),
array(
"position" => 0,
),
array(
"position" => 0,
),
array(
"position" => 46,
),
);
// Sort ascending
usort($array, 'sortByPosition');
// Show result
echo '<pre>';
print_r($array);
function sortByPosition($a, $b) {
return $a['position'] != 0 ? $a['position'] - $b['position'] : $b['position'] - $a['position'];
}
Your comparison function is a bit off. I would do it like this:
function sortByPosition($a, $b) {
if ($a['position'] == $b['position']) return 0;
if ($a['position'] == 0) return 1;
if ($b['position'] == 0) return -1;
return $a['position'] > $b['position'] ? 1 : -1;
}
First, if both positions are the same, return 0, regardless of
whether either of them are zero.
Second and third, if either of them are zero, that one should sort
after the other (since it already would have returned if they were
both zero).
Fourth, neither are zero, so just compare the values normally.
This way:
usort($array, function ($a, $b) {
if (!$a['position']) return 1;
if (!$b['position']) return -1;
return $a['position']-$b['position'];
});
NOTE: I edited the example and replaced all values with 1. The values do not matter, only the keys do. The previous values I had written led to a misunderstanding. Sorry.
NOTE: a/b blocks are always continuous. I'm adding this because it wasn't clear. If there is 3a/3b and 5a/5b there will always be 4a/4b and not only 4.
I have arrays that contain numbered keys with leading zeros. Sometimes, these numbered keys have two variations that I distinguish by using the suffixes a and b. The number of keys with or without variations is unknown, however there is never more than 2 digits; i.e. the highest numerical key is '09'.
The problem is that these array keys need to be sorted numerically, but when suffixes are present, those should take precedence. Using ksort() alone does not achieve this.
For example, ksort() gives me this:
$arr = array(
'01' => 1,
'02' => 1,
'03a' => 1,
'03b' => 1,
'04a' => 1,
'04b' => 1,
'05a' => 1,
'05b' => 1,
'06' => 1,
);
But, I need this:
$arr = array(
'01' => 1,
'02' => 1,
'03a' => 1,
'04a' => 1,
'05a' => 1,
'03b' => 1,
'04b' => 1,
'05b' => 1,
'06' => 1,
);
I use some fancy coding gymnastics to get what I want, but it isn't pretty. I'm wondering if there's a better, cleaner way?
Here is what I do.
1) I use ksort() which gives me the first of the two arrays above. (The one which isn't yet what I want.)
2) I create two arrays. One for the 'a' suffixes, the other for the 'b' suffixes.
$arr_a = array();
$arr_b = array();
foreach ($arr as $k => $v) {
if (substr($k, 2) == 'a') {
$arr_a[$k] = $v;
} else if (substr($k, 2) == 'b') {
$arr_b[$k] = $v;
}
}
3) I merge the two suffix arrays.
$arr_suffixes = array_merge($arr_a, $arr_b);
4) I slice up my original array so that I get the part before the suffixes, and the part after the suffixes.
$i = array_search(key($arr_suffixes), array_keys($arr));
$length = count($arr_suffixes);
$arr_before_suffixes = array_slice($arr, 0, $i);
$arr_after_suffixes = array_slice($arr, $i + $length);
5) Using array_merge, I recombine the sliced arrays to create the array I need.
$arr = array_merge($arr_before_suffixes, $arr_suffixes);
$arr = array_merge($arr, $arr_after_suffixes);
Finally, we have the correct $arr. Isn't there a better way to do this? It feels really ugly.
You have no formalized rule. I'll try to guess.
$arr = array(
'01' => 1,
'02' => 1,
'03a' => 1,
'03b' => 1,
'04a' => 1,
'04b' => 1,
'05a' => 1,
'05b' => 1,
'06' => 1,
);
uksort($arr, function($item1, $item2)
{
$last1 = substr($item1, -1);
$last2 = substr($item2, -1);
// one of the items is a number or last letters matches
if (is_numeric($last1) || is_numeric($last2) || $last1 == $last2)
// simple number comparison
return $item1 - $item2;
else
// natural order comparison
return $last1 > $last2 ? 1 : -1;
});
var_dump($arr);
natsort() function will helps you:
$arr = array(
'01' => 1,
'02' => 1,
'03a' => 1,
'03b' => 1,
'04a' => 1,
'04b' => 1,
'05a' => 1,
'05b' => 1,
'06' => 1,
);
$keys = array_keys($arr);
natsort($keys);
$result = array();
foreach ($keys as $key) {
$result[$key] = $arr[$key];
}
print_r($result); // Your expected result
$arr = array(
'01' => 1,
'02' => 2,
'03a' => 3,
'03b' => 6,
'04a' => 4,
'04b' => 7,
'05a' => 5,
'05b' => 8,
'06' => 9,
);
uksort(
$arr,
function($a, $b) {
sscanf($a, '%d%s', $an, $as);
sscanf($b, '%d%s', $bn, $bs);
if ($as === null || $bs === null || $as === $bs) {
return $an - $bn;
}
return strcmp($as, $bs);
}
);
var_dump($arr);