dynamic count of shorthand comparisons in PHP - php

I have the following code:
<?php
$a = [
[
'id' => 20,
'created_at' => '2020-11-22',
'updated_at' => '2020-11-22 11:16:22',
'name' => 'AA',
],
[
'id' => 19,
'created_at' => '2020-11-27 11:16:22',
'updated_at' => null,
'name' => 'BB',
]
];
$b = [
[
'id' => 20,
'created_at' => '2020-11-22 11:16:11',
'updated_at' => '2020-11-22 11:16:22',
'name' => 'AA',
],
[
'id' => 19,
'created_at' => '2020-11-27 11:16:22',
'updated_at' => null,
'name' => 'BB',
]
];
function array_diff_by_keys(array $a, array $b)
{
$compare = function ($x, $y) {
return $x['id'] <=> $y['id'] ?:
$x['created_at'] <=> $y['created_at'] ?:
$x['updated_at'] <=> $y['updated_at']; // how to build dynamic?
};
return array_udiff($a, $b, $compare);
}
array_diff_by_keys($a, $b); // what i've got
array_diff_by_keys($a, $b, ['id', 'created_at', ...]); // what i want
I would like in the $compare function create dynamic condition based on arguments passed to the function. Currently I have predefined keys like (id, created_at etc.), and I would like to be able to decide what arguments are to be included in the Elvis operator

You can iterate over the keys you want to compare on, taking the diff of the corresponding values in $x and $y, and returning that diff if it's non-zero; otherwise moving on to the next key. This function allows you to not specify the desired keys to compare on, if you don't they default to all the keys in each element:
function array_diff_by_keys(array $a, array $b, array $keys = null)
{
if (empty($keys)) $keys = array_keys(reset($a));
$compare = function ($x, $y) use ($keys) {
foreach ($keys as $key) {
$diff = $x[$key] <=> $y[$key];
if ($diff) return $diff;
}
return $diff;
};
return array_udiff($a, $b, $compare);
}
Output (for your sample data):
Array
(
[0] => Array
(
[id] => 20
[created_at] => 2020-11-22
[updated_at] => 2020-11-22 11:16:22
[name] => AA
)
)
Demo on 3v4l.org

Related

Sort an Array alphabeticaly but move those with 0 values to the end

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,
),
)

How to check if a single multi dimensional array contails same array as value in php? [duplicate]

I'd like to check if two arrays are equal. I mean: same size, same index, same values. How can I do that?
Using !== as suggested by a user, I expect that the following would print enter if at least one element in the array(s) are different, but in fact it does not.
if (($_POST['atlOriginal'] !== $oldAtlPosition)
or ($_POST['atl'] !== $aext)
or ($_POST['sidesOriginal'] !== $oldSidePosition)
or ($_POST['sidesOriginal'] !== $sideext)) {
echo "enter";
}
$arraysAreEqual = ($a == $b); // TRUE if $a and $b have the same key/value pairs.
$arraysAreEqual = ($a === $b); // TRUE if $a and $b have the same key/value pairs in the same order and of the same types.
See Array Operators.
EDIT
The inequality operator is != while the non-identity operator is !== to match the equality
operator == and the identity operator ===.
According to this page.
NOTE: The accepted answer works for associative arrays, but it will not work as expected with indexed arrays (explained below). If you want to compare either of them, then use this solution. Also, this function may not works with multidimensional arrays (due to the nature of array_diff function).
Testing two indexed arrays, which elements are in different order, using $a == $b or $a === $b fails, for example:
<?php
(array("x","y") == array("y","x")) === false;
?>
That is because the above means:
array(0 => "x", 1 => "y") vs. array(0 => "y", 1 => "x").
To solve that issue, use:
<?php
function array_equal($a, $b) {
return (
is_array($a)
&& is_array($b)
&& count($a) == count($b)
&& array_diff($a, $b) === array_diff($b, $a)
);
}
?>
Comparing array sizes was added (suggested by super_ton) as it may improve speed.
Try serialize. This will check nested subarrays as well.
$foo =serialize($array_foo);
$bar =serialize($array_bar);
if ($foo == $bar) echo "Foo and bar are equal";
Short solution that works even with arrays which keys are given in different order:
public static function arrays_are_equal($array1, $array2)
{
array_multisort($array1);
array_multisort($array2);
return ( serialize($array1) === serialize($array2) );
}
function compareIsEqualArray(array $array1,array $array2):bool
{
return (array_diff($array1,$array2)==[] && array_diff($array2,$array1)==[]);
}
Compare them as other values:
if($array_a == $array_b) {
//they are the same
}
You can read about all array operators here:
http://php.net/manual/en/language.operators.array.php
Note for example that === also checks that the types and order of the elements in the arrays are the same.
if (array_diff($a,$b) == array_diff($b,$a)) {
// Equals
}
if (array_diff($a,$b) != array_diff($b,$a)) {
// Not Equals
}
From my pov it's better to use array_diff than array_intersect because with checks of this nature the differences returned commonly are less than the similarities, this way the bool conversion is less memory hungry.
Edit Note that this solution is for plain arrays and complements the == and === one posted above that is only valid for dictionaries.
Another method for checking equality regardless of value order works by using http://php.net/manual/en/function.array-intersect.php, like so:
$array1 = array(2,5,3);
$array2 = array(5,2,3);
if($array1 === array_intersect($array1, $array2) && $array2 === array_intersect($array2, $array1)) {
echo 'Equal';
} else {
echo 'Not equal';
}
Here's a version that works also with multidimensional arrays using http://php.net/manual/en/function.array-uintersect.php:
$array1 = array(
array(5, 2),
array(3, 6),
array(2, 9, 4)
);
$array2 = array(
array(3, 6),
array(2, 9, 4),
array(5, 2)
);
if($array1 === array_uintersect($array1, $array2, 'compare') && $array2 === array_uintersect($array2, $array1, 'compare')) {
echo 'Equal';
} else {
echo 'Not equal';
}
function compare($v1, $v2) {
if ($v1===$v2) {
return 0;
}
if ($v1 > $v2) return 1;
return -1;
}
One way: (implementing 'considered equal' for https://www.rfc-editor.org/rfc/rfc6902#section-4.6)
This way allows associative arrays whose members are ordered differently - e.g. they'd be considered equal in every language but php :)
// recursive ksort
function rksort($a) {
if (!is_array($a)) {
return $a;
}
foreach (array_keys($a) as $key) {
$a[$key] = ksort($a[$key]);
}
// SORT_STRING seems required, as otherwise
// numeric indices (e.g. "0") aren't sorted.
ksort($a, SORT_STRING);
return $a;
}
// Per https://www.rfc-editor.org/rfc/rfc6902#section-4.6
function considered_equal($a1, $a2) {
return json_encode(rksort($a1)) === json_encode(rksort($a2));
}
Syntax problem on your arrays
$array1 = array(
'a' => 'value1',
'b' => 'value2',
'c' => 'value3',
);
$array2 = array(
'a' => 'value1',
'b' => 'value2',
'c' => 'value3',
);
$diff = array_diff($array1, $array2);
var_dump($diff);
Here is the example how to compare to arrays and get what is different between them.
$array1 = ['1' => 'XXX', 'second' => [
'a' => ['test' => '2'],
'b' => 'test'
], 'b' => ['no test']];
$array2 = [
'1' => 'XX',
'second' => [
'a' => ['test' => '5', 'z' => 5],
'b' => 'test'
],
'test'
];
function compareArrayValues($arrayOne, $arrayTwo, &$diff = [], $reversed = false)
{
foreach ($arrayOne as $key => $val) {
if (!isset($arrayTwo[$key])) {
$diff[$key] = 'MISSING IN ' . ($reversed ? 'FIRST' : 'SECOND');
} else if (is_array($val) && (json_encode($arrayOne[$key]) !== json_encode($arrayTwo[$key]))) {
compareArrayValues($arrayOne[$key], $arrayTwo[$key], $diff[$key], $reversed);
} else if ($arrayOne[$key] !== $arrayTwo[$key]) {
$diff[$key] = 'DIFFERENT';
}
}
}
$diff = [];
$diffSecond = [];
compareArrayValues($array1, $array2, $diff);
compareArrayValues($array2, $array1, $diffSecond, true);
print_r($diff);
print_r($diffSecond);
print_r(array_merge($diff, $diffSecond));
Result:
Array
(
[0] => DIFFERENT
[second] => Array
(
[a] => Array
(
[test] => DIFFERENT
[z] => MISSING IN FIRST
)
)
[b] => MISSING IN SECOND
[1] => DIFFERENT
[2] => MISSING IN FIRST
)
array_diff — Computes the difference of arrays
http://php.net/manual/en/function.array-diff.php
array array_diff ( array $array1 , array $array2 [, array $... ] )
Compares array1 against one or more other arrays and returns the values in array1 that are not present in any of the other arrays.
If you want to check non associative arrays, here is the solution:
$a = ['blog', 'company'];
$b = ['company', 'blog'];
(count(array_unique(array_merge($a, $b))) === count($a)) ? 'Equals' : 'Not Equals';
// Equals
The following solution works with custom equality functions that you can pass as a callback. Note that it doesn't check arrays order.
trait AssertTrait
{
/**
* Determine if two arrays have the same elements, possibly in different orders. Elements comparison function must be passed as argument.
*
* #param array<mixed> $expected
* #param array<mixed> $actual
*
* #throws InvalidArgumentException
*/
public static function assertArraysContainSameElements(array $expected, array $actual, callable $comparisonFunction): void
{
Assert::assertEquals(\count($expected), \count($actual));
self::assertEveryElementOfArrayIsInAnotherArrayTheSameAmountOfTimes($expected, $actual, $comparisonFunction);
self::assertEveryElementOfArrayIsInAnotherArrayTheSameAmountOfTimes($actual, $expected, $comparisonFunction);
}
/**
* #param array<mixed> $needles
* #param array<mixed> $haystack
*
* #throws InvalidArgumentException
*/
private static function assertEveryElementOfArrayIsInAnotherArrayTheSameAmountOfTimes(
array $needles,
array $haystack,
callable $comparisonFunction
): void {
Assert::assertLessThanOrEqual(\count($needles), \count($haystack));
foreach ($needles as $expectedElement) {
$matchesOfExpectedElementInExpected = \array_filter(
$needles,
static fn($element): bool => $comparisonFunction($expectedElement, $element),
);
$matchesOfExpectedElementInActual = \array_filter(
$haystack,
static fn($element): bool => $comparisonFunction($expectedElement, $element),
);
Assert::assertEquals(\count($matchesOfExpectedElementInExpected), \count($matchesOfExpectedElementInActual));
}
}
}
I usually use it in database integrations tests when I want to ensure that the expected elements are returned but I don't care about the sorting.
The proper way to compare whether two arrays are equal is to use strict equality (===), which compares recursively. Existing answers are unable to recursively sort an arbitrary array (array of arbitrary depth and order, containing a mixture of sequential and associative arrays) and hence cannot handle comparisons of arbitrary arrays. Sequential arrays are associative arrays with a sequential key (0,1,2,3...) whereas associative arrays do not have a sequential key.
To sort these arbitrary arrays, we have to:
Traverse downwards towards leaf nodes with no more sub-arrays
Sort sequential arrays by serializing then sorting them (to remove the need of having to use custom comparators)
Sort associative arrays by key
The following code implements the solution described above. Improvements to the code are welcome.
function recur_sort( &$array ) {
foreach ( $array as &$value ) {
if ( is_array( $value ) ) recur_sort( $value );
}
if ( is_sequential_array( $array ) ) {
$array = array_map( function( $el ) { return json_encode( $el ); }, $array );
sort( $array, SORT_STRING );
$array = array_map( function( $el ) { return json_decode( $el, true ); }, $array );
return;
} else {
return ksort( $array );
}
}
function is_sequential_array(Array &$a) {
$n = count($a);
for($i=0; $i<$n; $i++) {
if(!array_key_exists($i, $a)) {
return false;
}
}
return true;
}
Example (in PHPUnit):
//A stricter and recursive assertEqualsCanonicalizing
public function assertSameCanonicalizing( $expected, $actual ) {
recur_sort( $expected );
recur_sort( $actual );
$this->assertSame( $expected, $actual );
}
If you want to check that your arrays have the strictly equal (===) associations of keys and values, you can use the following function:
function array_eq($a, $b) {
// If the objects are not arrays or differ in their size, they cannot be equal
if (!is_array($a) || !is_array($b) || count($a) !== count($b)) {
return false;
}
// If the arrays of keys are not strictly equal (after sorting),
// the original arrays are not strictly equal either
$a_keys = array_keys($a);
$b_keys = array_keys($b);
array_multisort($a_keys);
array_multisort($b_keys);
if ($a_keys !== $b_keys) {
return false;
}
// Comparing values
foreach ($a_keys as $key) {
$a_value = $a[$key];
$b_value = $b[$key];
// Either the objects are strictly equal or they are arrays
// which are equal according to our definition. Otherwise they
// are different.
if ($a_value !== $b_value && !array_eq($a_value, $b_value)) {
return false;
}
}
return true;
}
To compare the values of your arrays, also multidimensional, associative and in any combination:
/**
* #see PHPUnit Assert::assertEqualsCanonicalizing()
* #return true if all keys and values are equal and of the same type,
* irregardless of items or keys order
*/
function array_vals_equal(array $a, array $b): bool {
// sort multi-dimensional recursive
$_deep_sort = function (array $a) use (&$_deep_sort): array{
// sort discarding index association or sort keys, depending on array type
array_is_list($a) ? sort($a) : ksort($a);
return array_map(fn($v) => is_array($v) ? $_deep_sort($v) : $v, $a);
};
// operator === checks that the count, types and order of the elements are the same
return $_deep_sort($a) === $_deep_sort($b);
}
// Test cases
assertEquals(array_vals_equal([1], [1]), true, 'simple eq');
assertEquals(array_vals_equal([0], [false]), false, 'simple eq');
assertEquals(array_vals_equal([0], [null]), false, 'simple eq');
assertEquals(array_vals_equal([0, 1], [1, 0]), true, 'simple eq, diff order');
assertEquals(array_vals_equal([0, 1, 2], [1, 0]), false, 'diff count');
assertEquals(array_vals_equal([0, 1], [0, 1, 2]), false, 'diff count 2');
assertEquals(array_vals_equal([1, 2], [1, 2, 'hello']), false, 'diff count 3');
//
assertEquals(array_vals_equal([1, 2, 2], [2, 1, 1]), false, 'same vals repeated');
assertEquals(array_vals_equal([1, 2, 2], [2, 2, 1]), true, 'same vals, different order');
//
assertEquals(array_vals_equal([1, 2, 3], ['1', '2', '3']), false, 'int should not be eq string');
assertEquals(array_vals_equal([0 => 'a', 1 => 'b'], [0 => 'b', 1 => 'a']), true, 'same vals, diff order');
assertEquals(array_vals_equal(['a', 'b'], [3 => 'b', 5 => 'a']), true, 'same vals, diff indexes');
// associative arrays whose members are ordered differently
assertEquals(array_vals_equal(['aa' => 'a', 'bb' => 'b'], ['bb' => 'b', 'aa' => 'a']), true, 'dict with different order');
assertEquals(array_vals_equal(['aa' => 'a', 'bb' => 'b'], ['aa' => 'a']), false, 'a key is missing');
assertEquals(array_vals_equal(['aa' => 'a', 'bb' => 'b'], ['aa' => 'a', 'zz' => 'b']), false, 'dict same vals diff key');
// nested arrays with keys in different order
assertEquals(array_vals_equal(
['aa' => 'a', 'bb' => ['bb' => 'b', 'aa' => 'a']],
['aa' => 'a', 'bb' => ['aa' => 'a', 'bb' => 'b']]
), true, 'dict multi 2 level, keys in different order');
assertEquals(array_vals_equal(
['aa' => 'a', 'bb' => ['aa2' => 'a', 'bb2' => ['aa3' => 'a', 'bb3' => 'b']]],
['aa' => 'a', 'bb' => ['aa2' => 'a', 'bb2' => ['aa3' => 'a', 'bb3' => 'b']]]
), true, 'dict multi 3 level');
assertEquals(array_vals_equal(
['aa' => 'a', 'bb' => [0, 1]],
['aa' => 'a', 'bb' => [1, 0]]
), true, 'dict multi level, 2^ level sequential in different order');
assertEquals(array_vals_equal([[0, 1], ['a', 'b']], [['b', 'a'], [1, 0]]), true, 'multi level sequential');
If you'd like to generate a detailed report, you could use something like this:
function deepCompare(Array $a, Array $b, string $parentAKey, string $parentBKey, bool $compareInverted = true, bool $compareValues = true, string $log = '')
{
foreach ($a as $aKey => $aValue) {
$fullAKey = implode('.', [$parentAKey, $aKey]);
$fullBKey = implode('.', [$parentBKey, $aKey]);
if (! isset($b[$aKey])) {
$log .= "⍰ {$fullAKey} has no equivalent {$fullBKey}\n";
} else {
$bValue = $b[$aKey];
if (is_array($aValue)) {
$log = deepCompare($aValue, $bValue, $fullAKey, $fullBKey, false, $compareValues, $log);
} else {
if ($compareValues) {
if ($aValue != $bValue) {
$log .= "≠ {$fullAKey} value differs from {$fullBKey}\n";
}
}
}
}
}
if ($compareInverted) {
$log = deepCompare($b, $a, $parentBKey, $parentAKey, false, false, $log);
}
return $log;
}
Here is an example for it:
$november = [
'site1' => [
'id' => 15,
'name' => 'Brazil',
'extendedHours' => 454,
],
'site2' => [
'id' => 43,
'name' => 'Portugal',
'extendedHours' => 448,
],
'site3' => [
'id' => 49,
'name' => 'Spain',
'extendedHours' => 0,
],
'totalExtendedHours' => 902,
];
$december = [
'site1' => [
'id' => 15,
'name' => 'Brazil',
'extendedHours' => 498,
],
'site2' => [
'id' => 43,
'name' => 'Portugal',
'extendedHours' => 409,
'extraRequests' => 6,
],
'totalExtendedHours' => 907,
'totalExtraRequests' => 6,
];
echo deepCompare(
$november, -- origin array
$december, -- target array
'Nov2022', -- descriptive name of origin array
'Dec2022', -- descriptive name of target array
true, -- should also compare arrays in reverse order?
true -- should care about array values? (false = names only)
);
This example will output:
≠ Nov2022.site1.extendedHours value differs from Dec2022.site1.extendedHours
≠ Nov2022.site2.extendedHours value differs from Dec2022.site2.extendedHours
⍰ Nov2022.site3 has no equivalent Dec2022.site3
≠ Nov2022.totalExtendedHours value differs from Dec2022.totalExtendedHours
⍰ Dec2022.site2.extraRequests has no equivalent Nov2022.site2.extraRequests
⍰ Dec2022.totalExtraRequests has no equivalent Nov2022.totalExtraRequests
I hope that helps someone.
Use php function array_diff(array1, array2);
It will return a the difference between arrays. If its empty then they're equal.
example:
$array1 = array(
'a' => 'value1',
'b' => 'value2',
'c' => 'value3'
);
$array2 = array(
'a' => 'value1',
'b' => 'value2',
'c' => 'value4'
);
$diff = array_diff(array1, array2);
var_dump($diff);
//it will print array = (0 => ['c'] => 'value4' )
Example 2:
$array1 = array(
'a' => 'value1',
'b' => 'value2',
'c' => 'value3',
);
$array2 = array(
'a' => 'value1',
'b' => 'value2',
'c' => 'value3',
);
$diff = array_diff(array1, array2);
var_dump($diff);
//it will print empty;

Move items of an array into the start of a multidimensional array

I have an array that contains some other arrays, I will make a sample of the original structure below in order to understand my issue.
$foo = array(
[0] => array('name' => foo, 'offered' => 1)
[1] => array('name' => foo)
[2] => array('name' => foo, 'offered' => 1)
[3] => array('name' => foo, 'offered' => 1)
);
What i want to do is, to sort my array in order to get first every array that contains the key 'offered'. In the example above i need do get this type of order. [0],[2],[3],[1].
You could use uasort() with a custom comparison function.
$foo = array(
array('name' => 'mark', 'offered' => 1),
array('name' => 'joe'),
array('name' => 'bill', 'offered' => 1),
array('name' => 'hugo', 'offered' => 1)
);
uasort($foo, 'customSort');
function customSort($a, $b) {
$a = isset($a['offered']);
$b = isset($b['offered']);
if (($a && $b) || (!$a && !$b)) return 0;
else if ($a && !$b) return -1;
else return 1;
}
print_r($foo);
Please check:
http://php.net/manual/it/function.usort.php
http://php.net/manual/it/function.uasort.php
http://php.net/manual/it/function.uksort.php
Explanation: (a bit simplified for newcomers) When you sort an array with these special sorting functions, you can tell PHP to use a custom function to let it decide whether a value is "less than another" (and return 1) or "greater than" another (and return -1) or "equal to another" (and return 0).
This functions has as parameters ($a, $b) the two array items to compare.
In this case we decide that the ones where "offered" exists are "less than" the others, so they will be sorted first.
I would iterate and check if the key exists, depending on that, rebuild the array.
$final = [];
foreach ($foo as $r) {
if (isset($r['offered']))
array_unshift($final, $r);
else
array_push($final, $r);
}
var_dump($final);
I find the spaceship operator (php7+) to provide a very clean syntax.
Evaluate the offered column from both $a and $b to check if the column is set. If not the isset() false will be treated as 0, otherwise true will be treated as 1.
By writing $b on the left of the operator and $a on the right, DESC sorting is performed -- this positions all 1 (true) evaluations before 0 (false) evaluations.
Code: (Demo)
$array = [
['name' => 'a', 'offered' => 1],
['name' => 'b'],
['name' => 'c', 'offered' => 1],
['name' => 'd', 'offered' => 1],
['name' => 'e']
];
usort($array, function($a, $b) {
return isset($b['offered']) <=> isset($a['offered']);
});
var_export($array);
Output:
array (
0 =>
array (
'name' => 'a',
'offered' => 1,
),
1 =>
array (
'name' => 'c',
'offered' => 1,
),
2 =>
array (
'name' => 'd',
'offered' => 1,
),
3 =>
array (
'name' => 'b',
),
4 =>
array (
'name' => 'e',
),
)

php - sort array by custom criteria

I have an array of objects:
object1->
name="Name1"
key="key1"
object2->
name="Name2"
key="key2"
object3->
name="Name3"
key="key3"
and an array of priority keys:
$keys = ["key3", "key1"];
I need to sort the array of objects based on priority keys, so the result should be:
object3:
name="Name3"
key="key3"
object1->
name="Name1"
key="key1"
object2:
name="Name2"
key="key2"
What is the best way to do it?
The idea is to add a priority as integer, and sort the array from the highest integer to the lowest using usort()
for example you have this data
<?php
$data = [];
$data[0] = new stdClass;
$data[0]->name = "name1";
$data[0]->key = 'key1';
$data[1] = new stdClass;
$data[1]->name = "name2";
$data[1]->key = 'key2';
$data[2] = new stdClass;
$data[2]->name = "name3";
$data[2]->key = 'key3';
$keys = ["key3", "key1"];
you can sort it this way
function sortByPriority($data , $keys){
$priority = array();
$i = count($keys);
foreach ($keys as $key => $value) {
$i--;
$priority[$value] = $i;
}
usort($data, function($a, $b) use($priority){
$a = isset($priority[$a->key]) ? $priority[$a->key] : -1;
$b = isset($priority[$b->key]) ? $priority[$b->key] : -1;
return $b - $a;
});
return $data;
}
var_dump(sortByPriority($data, $keys));
sample output
array (size=3)
0 =>
object(stdClass)[3]
public 'name' => string 'name3' (length=5)
public 'key' => string 'key3' (length=4)
1 =>
object(stdClass)[1]
public 'name' => string 'name1' (length=5)
public 'key' => string 'key1' (length=4)
2 =>
object(stdClass)[2]
public 'name' => string 'name2' (length=5)
public 'key' => string 'key2' (length=4)
Once you prepare your priority lookup array it is merely a matter of passing it into usort()'s scope and either using the related priority value or if the key property's value is not in the priority array, then use the fallback value. Readable, direct, and concise.
Code: (Demo)
$objects = [
(object)["name" => "Name1", "key" => "key1"],
(object)["name" => "Name2", "key" => "key2"],
(object)["name" => "Name3", "key" => "key3"],
];
$keys = ["key3", "key1"];
$lookup = array_flip($keys);
$fallback = count($keys);
usort($objects, function($a, $b) use ($lookup, $fallback) {
return ($lookup[$a->key] ?? $fallback) <=> ($lookup[$b->key] ?? $fallback);
});
var_export($objects);
Output:
array (
0 =>
(object) array(
'name' => 'Name3',
'key' => 'key3',
),
1 =>
(object) array(
'name' => 'Name1',
'key' => 'key1',
),
2 =>
(object) array(
'name' => 'Name2',
'key' => 'key2',
),
)
From PHP7.4, the syntax can be further condensed and the use() declaration omitted. (Demo)
usort($objects, fn($a, $b) => ($lookup[$a->key] ?? $fallback) <=> ($lookup[$b->key] ?? $fallback));
There is the function usort in PHP do what you need :
usort( $your_array), "sort_function");
sort_function( $a, $b) {
// your sort logic
}

php usort with multi dimensinal array based on another array

I am following this thread How can I sort arrays and data in PHP?
I want to sort a two dimensinal array based on another single dimensional array
array i want to sort
$main = array(array('name' => 'terry', 'age' => '25', 'sex' => 'm', 'status' => 'active'),
array('name' => 'raul', 'age' => '26', 'sex' => 'm', 'status' => 'active'),
array('name' => 'mata', 'age' => '27', 'sex' => 'm', 'status' => 'active'),
array('name' => 'ronaldo', 'age' => '28', 'sex' => 'm', 'status' => 'active'),
array('name' => 'drogba', 'age' => '29', 'sex' => 'm', 'status' => 'active'),
array('name' => 'messi', 'age' => '30', 'sex' => 'm', 'status' => 'active'));
order array
$order = array('30','25');
function cmp(array $a, array $b) {
global $order;
return array_search($a['age'], $order) - array_search($b['age'], $order);
}
usort($main, 'cmp');
This is the function i am trying out. but it is not sorting the array.
desired output: I want to get the arrays with age value 30 & 25 as the first two arrays in my input two dimensional array
I am able to do it with this code
function cmp(array $a, array $b) {
global $order;
foreach ($order as $ord) {
if (in_array($ord, $a)) {
return true;
}
}
}
But i want to avoid the foreach loop
Try this:
$inArray = array(30, 25);
uasort($main, function($a, $b) use ($inArray){
$aAge = $a['age'];
$bAge = $b['age'];
$aWeight = 0;
$bWeight = 0;
if (in_array($aAge, $inArray))
$aWeight++;
if (in_array($bAge, $inArray))
$bWeight++;
if ($aWeight != $bWeight) {
return $aWeight > $bWeight ? -1 : 1;
} else if ($aWeight > 0) {
// need to sort by order which specified in array
$aIndex = array_search($aAge, $inArray);
$bIndex = array_search($bAge, $inArray);
return ($aIndex == $bIndex ? 0 : ($aIndex > $bIndex ? 1 : -1));
} else {
// just compare age values
return ($aAge == $bAge ? 0 : ($aAge > $bAge ? 1 : -1));
}
});

Categories