Ordering an array by specific value - php

I have original array like this:
$arr=array(10,8,13,8,5,10,10,12);
and want to sort so finally become:
$arr=array(10,10,10,8,8,13,12,5);
To get final result i using this way:
$arr=array(10,8,13,8,5,10,10,12);
// Remove array that contain array(1)
$arr_diff=array_diff(array_count_values($arr), array(1));
// Remove array that contain keys of $arr_diff
$arr=array_diff($arr, array_keys($arr_diff));
// Sort
rsort($arr);
ksort($arr_diff);
// unshift
foreach ($arr_diff as $k=>$v)
{
while ($v > 0)
{
array_unshift($arr,$k);
$v--;
}
}
// print_r($arr);
My question is there an another more simple way?
Thanks.

$occurrences = array_count_values($arr);
usort($arr, function ($a, $b) use ($occurrences) {
// if the occurrence is equal (== 0), return value difference instead
return ($occurrences[$b] - $occurrences[$a]) ?: ($b - $a);
});
See Reference: all basic ways to sort arrays and data in PHP.

Related

PHP - Elegantly extract the numeric indices in array a that are not in array b (not array_diff_key)

Suppose you have two arrays $a=array('apple','banana','canaple'); and $b=array('apple');, how do you (elegantly) extract the numeric indices of elements in array a that aren't in array b? (in this case, indices: 1 and 2).
In this case, array a will always have more elements than b.
Note, this is not asking for array_diff_key, but rather the numeric indices in the array with more elements that don't exist in the array with fewer elements.
array_diff gets you half way there. Using array_keys on the diff gets you the rest of what you want.
$a = ['apple','banana','canaple'];
$b = ['apple'];
$diff = array_diff($a, $b);
$keys = array_keys($diff);
var_dump($keys); // [1, 2]
This is because array_diff returns both the element and it's key from the first array. If you wanted to write a PHP implementation of array_diff it might look something like this...
function array_diff(Array ... $arrays) {
$return = [];
$cmp = array_shift($arrays);
foreach ($cmp as $key => $value) {
foreach($arrays as $array) {
if (!in_array($value, $array)) {
$return[$key] = $value;
}
}
}
return $return;
}
This gives you an idea how you might achieve the result, but internally php implements this as a sort, because it's much faster than the aforementioned implementation.

Detect the difference between two arrays using laravel

I have two arrays and I need to verify what is different between them, like:
Something exists in array A but not in B,
One argument is diferent in array A when comparing to B...
$temp = import_temp::select('cod_disciplina', 'cod_turma', 'hr_inicio', 'hr_fim', 'dia_semana')->get();
$turmas;
foreach($temp as $t)
{
$turmas = Horario::select('cod_disciplina', 'cod_turma', 'hr_inicio', 'hr_fim', 'dia_semana')
->whereIn('cod_disciplina', $temp->lists('cod_disciplina'))
->whereIn('cod_turma', $temp->lists('cod_turma'))
->where('ano_semestre', $ano_semestre)->get();
}
When I do:
print_r($turmas->toArray());
print_r($temp->toArray());
I get:
How may I compare these keys and identify when something changed is new or is missing.
Tried working with array_diff_assoc but I get the following error:
Array to string conversion
array_diff_assoc($temp->toArray(), $turmas->toArray());
Also tried This that I found in another answer in a similar question but didn't work.
First define your own function to compare arrays:
function arrayCmp($a, $b) {
if ($a < $b) {
return -1;
} elseif ($a > $b) {
return 1;
} else {
return 0;
}
}
Then if you want elements that are present in $a bot not in $b then you call:
$diff = array_udiff($a, $b, 'arrayCmp');

PHP Array Filter on KEYS that pass a certain test

I have an associative PHP array and I would like to generate a list of keys that pass a certain test. For example
$myArray = ('28'=>0.01,'51'=>-0.1,'48'=>0.4,'53'=>-0.3);
And I'd like to filter the keys in the same way I can simply filter the values. So if I filter the values on "return the elements that are bigger than 0.2" would be
print_r(array_filter($myArray,"biggerThanFilter");
with
function biggerThanFilter($v){
return $v>0.2;
}
But how would I apply a filter to the keys which say the "keyValueIsBiggerThan50"
i.e something like this
print_r(array_KEY_filter($myArray,"keyValueIsBiggerThan50");
function keyValueIsBiggerThan50($key){
return $key*1>50;
}
I would loop through the array_keys and unset, personally:
function array_filter_keys($array, callable $fn)
foreach (array_keys($array) as $key) {
if (!$fn($key)) unset($array[$key])
}
return $array;
}
$filtered_array = array_filter_keys($array, function($key) { return $key > 50 });
This assumes PHP >= 5.4
function keyValueIsBiggerThan50 ($myArray) {
$newArray = array();
foreach($myArray as $key => $value){
if($key * 1 > 50){
$newArray[$key] = $value
}
}
return $newArray;
}
to be used like
print_r(keyValueIsBiggerThan50 ($myArray));
Are you looking for this specific case, or a generic?
For PHP 5.6+ my answer to a similar question also applies to this one: use ARRAY_FILTER_USE_KEY
<?php
$myArray = ['28' => 0.01, '51' => -0.1, '48' => 0.4, '53' => -0.3];
$filtered = array_filter(
$myArray,
function ($key) {
return $key > 50;
},
ARRAY_FILTER_USE_KEY
);
?>
For PHP < 5.6, array_diff_ukey performs a custom comparison of two arrays by key and appears to do a full N x M comparison so you can filter a single array by using a dummy as the second array.
Using PHP 5.5.9, I used the following to remove the numeric key elements from an array:
$res = array_diff_ukey($res, array(0), function ($a,$b){ return is_string($a); });

Using array_multisort() and putting empty values last, not first

I have 4 arrays and use array_multisort to sort them all at the same time relative to one another.
Problem is, in the first array, there can be empty values and I want to put them at the end, not the beginning.
Example : http://codepad.org/V6TjCsS5
Is there a way to:
Pass a custom function to array_multisort
or
Sort the first array with a custom function then use the result order to sort the other arrays
or
Use a certain argument with array_multisort to achieve what I want
Thank you very much
Unfortunately none of the approaches your propose is possible, which means you have to take another step back and look for alternatives. I am assuming you want a normal ascending sort, with the explicit exception that empty elements (which you be "smallest") need to be considered as "largest".
Option 1: Manually rearrange elements after sorting
Do your array_multisort as usual, and then make the modifications you require:
// $arr1, $arr2 etc have been sorted with array_multisort
while(reset($arr1) == '') {
$k = key($arr1);
unset($arr1[$k]); // remove empty element from beginning of array
$arr1[$k] = ''; // add it to end of array
// and now do the same for $arr2
$v = reset($arr2);
$k = key($arr2);
unset($arr2[$k]);
$arr2[$k] = $v;
// the same for $arr3, etc
}
You can pull out part of the code in a function to make this prettier:
function shift_and_push(&$arr) {
$v = reset($arr);
$k = key($arr);
unset($arr[$k]);
$arr[$k] = $v;
}
Option 2: Condense everything inside one array so you can use usort
The idea here is to pull all your arrays into one so you can specify the comparison function by using usort:
$allArrays = array_map(function() { return func_get_args(); },
$array1, $array2 /* , as many arrays as you want */);
You can now sort:
// writing this as a free function so that it looks presentable
function cmp($row1, $row2) {
// $row1[0] is the item in your first array, etc
if($row1[0] == $row2[0]) {
return 0;
}
else if($row1[0] == '') {
return 1;
}
else if($row2[0] == '') {
return -1;
}
return $row1[0] < $row2[0] ? -1 : 1;
}
usort($allArrays, "cmp");
At this point you are left with an array, each element (row) of which is an array. The first elements of each are what was originally inside $array1, second elements are what was in $array2, etc. By placing those elements inside "rows", we have managed to keep the sort order among all your original arrays synchronized.
The second argument to array_multisort can be the options to the sort. So you could pass SORT_DESC if SORT_ASC is doing the opposite of what you want.
array_multisort($arr, SORT_DESC);
For completeness sake, here are the other options SORT_ASC, SORT_DESC, SORT_REGULAR, SORT_NUMERIC, SORT_STRING. SORT_STRING may be helpful.
Do you actually want the empty elements? Just remove them before sorting:
foreach($array1 as &$v) {
if($v==='')
{
unset($v);
}
}

Preserve key order (stable sort) when sorting with PHP's uasort

This question is actually inspired from another one here on SO and I wanted to expand it a bit.
Having an associative array in PHP is it possible to sort its values, but where the values are equal to preserve the original key order, using one (or more) of PHP's built in sort function?
Here is a script I used to test possible solutions (haven't found any):
<?php
header('Content-type: text/plain');
for($i=0;$i<10;$i++){
$arr['key-'.$i] = rand(1,5)*10;
}
uasort($arr, function($a, $b){
// sort condition may go here //
// Tried: return ($a == $b)?1:($a - $b); //
// Tried: return $a >= $b; //
});
print_r($arr);
?>
Pitfall: Because the keys are ordered in the original array, please don't be tempted to suggest any sorting by key to restore to the original order. I made the example with them ordered to be easier to visually check their order in the output.
Since PHP does not support stable sort after PHP 4.1.0, you need to write your own function.
This seems to do what you're asking: http://www.php.net/manual/en/function.usort.php#38827
As the manual says, "If two members compare as equal, their order in the sorted array is undefined." This means that the sort used is not "stable" and may change the order of elements that compare equal.
Sometimes you really do need a stable sort. For example, if you sort a list by one field, then sort it again by another field, but don't want to lose the ordering from the previous field. In that case it is better to use usort with a comparison function that takes both fields into account, but if you can't do that then use the function below. It is a merge sort, which is guaranteed O(n*log(n)) complexity, which means it stays reasonably fast even when you use larger lists (unlike bubblesort and insertion sort, which are O(n^2)).
<?php
function mergesort(&$array, $cmp_function = 'strcmp') {
// Arrays of size < 2 require no action.
if (count($array) < 2) return;
// Split the array in half
$halfway = count($array) / 2;
$array1 = array_slice($array, 0, $halfway);
$array2 = array_slice($array, $halfway);
// Recurse to sort the two halves
mergesort($array1, $cmp_function);
mergesort($array2, $cmp_function);
// If all of $array1 is <= all of $array2, just append them.
if (call_user_func($cmp_function, end($array1), $array2[0]) < 1) {
$array = array_merge($array1, $array2);
return;
}
// Merge the two sorted arrays into a single sorted array
$array = array();
$ptr1 = $ptr2 = 0;
while ($ptr1 < count($array1) && $ptr2 < count($array2)) {
if (call_user_func($cmp_function, $array1[$ptr1], $array2[$ptr2]) < 1) {
$array[] = $array1[$ptr1++];
}
else {
$array[] = $array2[$ptr2++];
}
}
// Merge the remainder
while ($ptr1 < count($array1)) $array[] = $array1[$ptr1++];
while ($ptr2 < count($array2)) $array[] = $array2[$ptr2++];
return;
}
?>
Also, you may find this forum thread interesting.
array_multisort comes in handy, just use an ordered range as second array ($order is just temporary, it serves to order the equivalent items of the first array in its original order):
$a = [
"key-0" => 5,
"key-99" => 3,
"key-2" => 3,
"key-3" => 7
];
$order = range(1,count($a));
array_multisort($a, SORT_ASC, $order, SORT_ASC);
var_dump($a);
Output
array(4) {
["key-99"]=>
int(3)
["key-2"]=>
int(3)
["key-0"]=>
int(5)
["key-3"]=>
int(7)
}
I used test data with not-ordered keys to demonstrate that it works correctly. Nonetheless, here is the output your test script:
Array
(
[key-1] => 10
[key-4] => 10
[key-5] => 20
[key-8] => 20
[key-6] => 30
[key-9] => 30
[key-2] => 40
[key-0] => 50
[key-3] => 50
[key-7] => 50
)
Downside
It only works with predefined comparisons, you cannot use your own comparison function. The possible values (second parameter of array_multisort()) are:
Sorting type flags:
SORT_ASC - sort items ascendingly.
SORT_DESC - sort items descendingly.
SORT_REGULAR - compare items normally (don't change types)
SORT_NUMERIC - compare items numerically
SORT_STRING - compare items as strings
SORT_LOCALE_STRING - compare items as strings, based on the current locale. It uses the locale, which can be changed using
setlocale()
SORT_NATURAL - compare items as strings using "natural ordering" like natsort()
SORT_FLAG_CASE - can be combined (bitwise OR) with SORT_STRING or SORT_NATURAL to sort strings case-insensitively
For completeness sake, you should also check out the Schwartzian transform:
// decorate step
$key = 0;
foreach ($arr as &$item) {
$item = array($item, $key++); // add array index as secondary sort key
}
// sort step
asort($arr); // sort it
// undecorate step
foreach ($arr as &$item) {
$item = $item[0]; // remove decoration from previous step
}
The default sort algorithm of PHP works fine with arrays, because of this:
array(1, 0) < array(2, 0); // true
array(1, 1) < array(1, 2); // true
If you want to use your own sorting criteria you can use uasort() as well:
// each parameter is an array with two elements
// [0] - the original item
// [1] - the array key
function mysort($a, $b)
{
if ($a[0] != $b[0]) {
return $a[0] < $b[0] ? -1 : 1;
} else {
// $a[0] == $b[0], sort on key
return $a[1] < $b[1] ? -1 : 1; // ASC
}
}
This is a solution using which you can achieve stable sort in usort function
public function sortBy(array &$array, $value_compare_func)
{
$index = 0;
foreach ($array as &$item) {
$item = array($index++, $item);
}
$result = usort($array, function($a, $b) use ($value_compare_func) {
$result = call_user_func($value_compare_func, $a[1], $b[1]);
return $result == 0 ? $a[0] - $b[0] : $result;
});
foreach ($array as &$item) {
$item = $item[1];
}
return $result;
}
Just to complete the responses with some very specific case. If the array keys of $array are the default one, then a simple array_values(asort($array)) is sufficient (here for example in ascending order)
As a workaround for stable sort:
<?php
header('Content-type: text/plain');
for ($i = 0;$i < 10;$i++)
{
$arr['key-' . $i] = rand(1, 5) * 10;
}
uksort($arr, function ($a, $b) use ($arr)
{
if ($arr[$a] === $arr[$b]) return array_search($a, array_keys($arr)) - array_search($b, array_keys($arr));
return $arr[$a] - $arr[$b];
});
print_r($arr);

Categories