sorting part of a multidimensional array - php

I have an multi dimensional array:
$array[0] = array ( name => "Bob",
position => "Chair",
email => "bob#foo.bar"
);
$array[1] = array ( name => "Al",
position => "",
email => "al#foo.bar"
);
//etc..
I want to sort it so that those whose position != "" are first, then the rest alphabetically by name... I'm not very familiar w/ sorting multidimensional arrays, could someone help me?
Thanks!

<?php
$array[0] = array ( name => "Bob", position => "Chair", email => "bob#foo.bar");
$array[1] = array ( name => "Al", position => "",email => "al#foo.bar");
print_r($array);
$idxPos = array();
for ($i=0;$i<count($array);$i++)
{
$idxPos[$i] = $array[$i]["position"];
}
arsort($idxPos);
$arrayNew = array();
$sortedKeys = array_keys($idxPos);
for($p=0;$p<count($idxPos);$p++)
{
$arrayNew[] = $array[$sortedKeys[$p]];
}
$array = $arrayNew;
print_r("<br />");
print_r($array);
?>

The usort function is probably your best bet for this:
usort($array, function($a, $b) {
if (empty($a['position']) && !empty($b['position'])) {
return 1;
}
else if (!empty($a['position']) && empty($b['position'])) {
return -1;
}
else {
return strcmp($a['name'], $b['name']);
}
});
Anonymous functions were introduced in PHP 5.3.0. If you're using an earlier version you can just define a regular function instead:
function cmp($a, $b) {
if (empty($a['position']) && !empty($b['position'])) {
return 1;
}
else if (!empty($a['position']) && empty($b['position'])) {
return -1;
}
else {
return strcmp($a['name'], $b['name']);
}
}
usort($array, 'cmp');

You'll probably want to try usort so you can use a custom sorting function. In the sorting function you can check a["position"] vs b["position"] in the way you describe.

Related

How to filter an array based on last name?

I have an array as follows:
Array(
[0] => 'Sarah, Green',
[1] => 'Adam, Brown',
[2] => 'Fred, Able'
);
From this array I need to sort based on the last name.
I have tried following code:
$this->sortByLastName($data);
private function sortByLastName($data)
{
$result = uasort($data, function($a, $b) {
$splitFirstItem = explode(',', $a);
$spliteSecondItem = explode(',', $b);
$firstItemElement = trim(end($splitFirstItem));
$secondItemElement = trim(end($spliteSecondItem));
return strcasecmp($firstItemElement, $secondItemElement);
});
return $result;
}
But I am just getting true as the result.
What mistake I have done here.
uasort simply returns true/false dependent on whether it completed successfully or not. The array is sorted in place, so your code should look like:
private function sortByLastName($data)
{
uasort($data, function($a, $b) {
$splitFirstItem = explode(',', $a);
$spliteSecondItem = explode(',', $b);
$firstItemElement = trim(end($splitFirstItem));
$secondItemElement = trim(end($spliteSecondItem));
return strcasecmp($firstItemElement, $secondItemElement);
});
return $data;
}
Demo on 3v4l.org
Change your function as below.
private function sortByLastName($data)
{
uasort($data, function($a, $b) {
$splitFirstItem = explode(',', $a);
$spliteSecondItem = explode(',', $b);
$firstItemElement = trim(end($splitFirstItem));
$secondItemElement = trim(end($spliteSecondItem));
return strcasecmp($firstItemElement, $secondItemElement);
});
return $data;
}
This is because uasort does not return the array but changes the array passed to it. It only returns boolean true or false depending upon if the process was succesful

Finding the higest value in a loop

I am trying to return the highest percentage value along with all the other information like name,average, percentage, color etc in the array.
foreach ($array as $value) {
$name = getName($value);
$average = getAverage($value);
$percentage = getPercentage($value);
$color = getColor($value);
return $percentage;
}
How can i implement the solution to find the desired value (highest percentage) and return it If an array. I am thinking sorting can be one way but i am still not clear how should i do it.
Try this
$percentage1=0;
$datas=array();
foreach ($array as $value) {
$name = getName($value);
$average = getAverage($value);
$percentage = getPercentage($value);
$color = getColor($value);
if($percentage>$percentage1)
{
$percentage1=$percentage;
$datas['name']=$name;
$datas['average']=$average;
$datas['percentage']=$percentage;
$datas['color']=$color;
}
return $datas;
}
Though the code in your question doesn't make any sense, you can use php max function to get highest value if all value in array are numbers.
For example:
<?php $highest_value = max($array); ?>
If you want to use the buit-in PHP sorting, you can use usort(). It use a custom function to sort your array.
usort($array, function($a, $b){
$percentage_a = getPercentage($a);
$percentage_b = getPercentage($b);
if($percentage_a === $percentage_b) {
return 0;
}
return ($percentage_a > $percentage_b)? -1 : 1;
});
return array(
"name" => getName($array[0]),
"average" => getAverage($array[0]),
"percentage" => getPercentage($array[0]),
"color" = getColor($array[0]));
Then, your array is sorted with the highest percentage first.
If you don't want anonymous function, you can juste define it and call usort with its name :
function getMaxPercentage($a, $b){
$percentage_a = getPercentage($a);
$percentage_b = getPercentage($b);
if($percentage_a === $percentage_b) {
return 0;
}
return ($percentage_a > $percentage_b)? -1 : 1;
}
usort($array, 'getMaxPercentage');
return return array(
"name" => getName($array[0]),
"average" => getAverage($array[0]),
"percentage" => getPercentage($array[0]),
"color" = getColor($array[0]));

In Array with regex

I`m using $_POST array with results of checkbox's selected from a form.
I'm thinking of using php in_array function but how could I extract only values that start with chk Given the following array:
Array (
[chk0] => 23934567622639616
[chk3] => 23934567622639618
[chk4] => 23934567622639619
[select-all] => on
[process] => Process
)
Thanks!
Simple and fast
$result=array();
foreach($_POST as $key => $value){
if(substr($key, 0, 2) == 'chk'){
$result[$key] = $value;
}
}
Lots of ways to do this, I like array_filter.
Example:
$result = array_filter(
$_POST,
function ($key) {
return strpos($key, "chk") === 0;
},
ARRAY_FILTER_USE_KEY
);
Here's a solution from http://php.net/manual/en/function.preg-grep.php
<?php
function preg_grep_keys($pattern, $input, $flags = 0) {
return array_intersect_key($input, array_flip(preg_grep($pattern, array_keys($input), $flags)));
}
?>
I would use array_filter
$ary = array_filter($originalArray,
function($key){ return preg_match('/chk/', $key); },
ARRAY_FILTER_USE_KEY
);

PHP Deep Extend Array

How can I do a deep extension of a multi dimensional associative array (for use with decoded JSON objects).
I need the php equivalent of jQuery's $.extend(true, array1, array2) with arrays instead of JSON and in PHP.
Here's an example of what I need (array_merge_recursive didn't seem to do the same thing)
$array1 = ('1'=> ('a'=>'array1a', 'b'=>'array1b'));
$array2 = ('1'=> ('a'=>'array2a', 'c'=>'array2b'));
$array3 = array_extend($array1, $array2);
//$array3 = ('1'=> ('a'=>'array2a', 'b'=>'array1b', 'c'=>'array2b'))
Notice how array2 overrides array1 if it has same value (like how extension of classes works)
If you have PHP 5.3.0+, you can use array_replace_recursive which does exactly what you need:
array_replace_recursive() replaces the values of array1 with the same
values from all the following arrays. If a key from the first array
exists in the second array, its value will be replaced by the value
from the second array. If the key exists in the second array, and not
the first, it will be created in the first array. If a key only exists
in the first array, it will be left as is. If several arrays are
passed for replacement, they will be processed in order, the later
array overwriting the previous values.
This might be what you're looking for:
function array_extend(&$result) {
if (!is_array($result)) {
$result = array();
}
$args = func_get_args();
for ($i = 1; $i < count($args); $i++) {
// we only work on array parameters:
if (!is_array($args[$i])) continue;
// extend current result:
foreach ($args[$i] as $k => $v) {
if (!isset($result[$k])) {
$result[$k] = $v;
}
else {
if (is_array($result[$k]) && is_array($v)) {
array_extend($result[$k], $v);
}
else {
$result[$k] = $v;
}
}
}
}
return $result;
}
Usage:
$arr1 = array('a' => 1, 'b' => 2, 'c' => 3);
$arr2 = array('b' => 'b', 'd' => 'd');
array_extend($arr1, $arr2);
print_r($arr1); // array('a' => 1, 'b' => 'b', 'c' => 3, 'd' => 'd')
// or, to create a new array and leave $arr1 unchanged use:
array_extend($arr3, $arr1, $arr2);
print_r($arr3); // array('a' => 1, 'b' => 'b', 'c' => 3, 'd' => 'd')
// or, use the return value:
print_r(array_extend($arr1, $arr2)); // but this will also modify $arr1
I use this in the same way I use angular.extend(dst, src) and jQuery.extend().
function extend($base = array(), $replacements = array()) {
$base = ! is_array($base) ? array() : $base;
$replacements = ! is_array($replacements) ? array() : $replacements;
return array_replace_recursive($base, $replacements);
}
Example:
si() is a utility sanitize function that grabs $_POST or $_GET and returns an array.
$s = extend(array(
'page' => 1,
'take' => 100,
'completed' => 1,
'incomplete' => 1,
), si());
Taken from array_merge docs:
function array_extend($a, $b) {
foreach($b as $k=>$v) {
if( is_array($v) ) {
if( !isset($a[$k]) ) {
$a[$k] = $v;
} else {
$a[$k] = array_extend($a[$k], $v);
}
} else {
$a[$k] = $v;
}
}
return $a;
}
You should use: https://github.com/appcia/webwork/blob/master/lib/Appcia/Webwork/Storage/Config.php#L64
/**
* Merge two arrays recursive
*
* Overwrite values with associative keys
* Append values with integer keys
*
* #param array $arr1 First array
* #param array $arr2 Second array
*
* #return array
*/
public static function merge(array $arr1, array $arr2)
{
if (empty($arr1)) {
return $arr2;
} else if (empty($arr2)) {
return $arr1;
}
foreach ($arr2 as $key => $value) {
if (is_int($key)) {
$arr1[] = $value;
} elseif (is_array($arr2[$key])) {
if (!isset($arr1[$key])) {
$arr1[$key] = array();
}
if (is_int($key)) {
$arr1[] = static::merge($arr1[$key], $value);
} else {
$arr1[$key] = static::merge($arr1[$key], $value);
}
} else {
$arr1[$key] = $value;
}
}
return $arr1;
}
With a little googling I found this:
/**
* jquery style extend, merges arrays (without errors if the passed values are not arrays)
*
* #return array $extended
**/
function extend() {
$args = func_get_args();
$extended = array();
if(is_array($args) && count($args)) {
foreach($args as $array) {
if(is_array($array)) {
$extended = array_merge($extended, $array);
}
}
}
return $extended;
}
extend($defaults, $new_options);
I guess here is the correct answer, because:
your answer have a bug with warning:
Warning: Cannot use a scalar value as an array in...
Because $a is not always an array and you use $a[$k].
array_merge_recursive does indeed merge arrays, but it converts values with duplicate keys to arrays rather than overwriting the value in the first array with the duplicate value in the second array, as array_merge does.
other aswers are not recursives or not simple.
So, here is my answer: your answer without bugs:
function array_extend(array $a, array $b) {
foreach($b as $k=>$v) {
if( is_array($v) ) {
if( !isset($a[$k]) ) {
$a[$k] = $v;
} else {
if( !is_array($a[$k]){
$a[$k]=array();
}
$a[$k] = array_extend($a[$k], $v);
}
} else {
$a[$k] = $v;
}
}
return $a;
}
And my answer with ternary operator:
function array_extend(array $a, array $b){
foreach($b as $k=>$v)
$a[$k] = is_array($v)&&isset($a[$k])?
array_extend(is_array($a[$k])?
$a[$k]:array(),$v):
$v;
return $a;
}
Edit: And a bonus one with as many arrays you want:
function array_extend(){
$args = func_get_args();
while($extended = array_shift($args))
if(is_array($extended))
break;
if(!is_array($extended))
return FALSE;
while($array = array_shift($args)){
if(is_array($array))
foreach($array as $k=>$v)
$extended[$k] = is_array($v)&&isset($extended[$k])?
array_extend(is_array($extended[$k])?
$extended[$k]:array(),$v):
$v;
}
return $extended;
}

Sort array by two object properties using anonymous function

I have the following array:
Array
(
[0] => stdClass Object
(
[timestamp] => 1
[id] => 10
)
[1] => stdClass Object
(
[timestamp] => 123
[id] => 1
)
[2] => stdClass Object
(
[timestamp] => 123
[id] => 2
)
)
I'm currently using the following code to sort the array by the timestamp property:
function sort_comments_by_timestamp(&$comments, $prop)
{
usort($comments, function($a, $b) use ($prop) {
return $a->$prop < $b->$prop ? 1 : -1;
});
}
How can I also sort id by id descending when timestamp is the same?
Suggestion is to send in an array with $props
function sort_comments_by_timestamp(&$comments, $props)
{
usort($comments, function($a, $b) use ($props) {
if($a->$props[0] == $b->$props[0])
return $a->$props[1] < $b->$props[1] ? 1 : -1;
return $a->$props[0] < $b->$props[0] ? 1 : -1;
});
}
And then call it with
sort_comments_by_timestamp($unsorted_array,array("timestamp","id"));
If you want it to work with X number of $props you can make a loop inside the usort always comparing a property with its preceding property in the array like this:
function sort_comments_by_timestamp(&$comments, $props)
{
usort($comments, function($a, $b) use ($props) {
for($i = 1; $i < count($props); $i++) {
if($a->$props[$i-1] == $b->$props[$i-1])
return $a->$props[$i] < $b->$props[$i] ? 1 : -1;
}
return $a->$props[0] < $b->$props[0] ? 1 : -1;
});
}
Cheers!
function sort_comments_by_timestamp(&$comments, $prop)
{
usort($comments, function($a, $b) use ($prop) {
if ($a->$prop == $b->$prop)
return $b->id - $a->id;
else
return $a->$prop < $b->$prop ? 1 : -1;
});
}
The above sorts first by the $prop parameter and then secondary by id.
You can do it with ouzo goodies (I know you've heard about it :P).
$result = Arrays::sort($array,
Comparator::compound(
Comparator::compareBy('timestamp'),
Comparator::compareBy('id')
)
);
I know this is fairly old question however didn't find much info for when you want to sort by multiple properties kinda like mySQL ORDERBY does so here's a function from a personal framework
Option 1: order($key, $direction='asc') where $key is a property of the objects and $direction is 'asc' or 'desc'
Option 2: order(array($key => $direction, $key => $direction))
This is similar to option 1 however when 2 objects have the same value for $key, the second sort option from the array passed is used.
public function order($arr, $key=null, $direction='ASC'){
if(!is_string($key) && !is_array($key))
throw new InvalidArgumentException("order() expects the first parameter to be a valid key or array");
$props = array();
if(is_string($key)) {
$props[$key] = strtolower($direction) == 'asc' ? 1 : -1;
}else{
$i = count($key);
foreach($key as $k => $dir){
$props[$k] = strtolower($dir) == 'asc' ? $i : -($i);
$i--;
}
}
usort($arr, function($a, $b) use ($props){
foreach( $props as $key => $val ){
if( $a->$key == $b->$key ) continue;
return $a->$key > $b->$key ? $val : -($val);
}
return 0;
});
return $arr;
}
$result = -1;
if ($a->timestamp < $b->timestamp) {
$result = 1;
} else if ($a->timestamp === $b->timestamp) {
if ($a->id < $b->id) $result = 1;
}
return $result;
Put this within the usort closure. You can also get rid of the $prop argument and the use ($prop) part.
function compare_city($a, $b)
{
// sort by state
$retval = strnatcmp($a->state, $b->state);
// if identical, sort by city
if(!$retval) $retval = strnatcmp($a->city, $b->city);
return $retval;
}
// sort alphabetically by state and city
usort($sortable, __NAMESPACE__ . '\compare_city');
This function worked with me! Answer found at: Sorting SimpleXMLElement Object arrays
The accepted answer has a loop variant that will not always work.
if($a->$props[$i-1] == $b->$props[$i-1])
return $a->$props[$i] < $b->$props[$i] ? 1 : -1;
obj1 prop1:foo, prop2: bar, prop3: test
obj2 prop1:foo, prop2: bar, prop3: apple
At $i = 1, the first comparison is true, and as bar is not less than bar, it will return -1 rather than continue with the loop to evaluate prop3.
Just an FYI.
I have a variant that also takes direction, so each property can also be sorted by ascending or descending order using -1 and 1, respectively.
function sortByProps(&$anArray, $props) {
usort($anArray, function($a, $b) use ($props) {
for($i = 0; $i < count($props); $i++) {
if( $a->{$props[$i][0]} < $b->{$props[$i][0]}) {
return $props[$i][1];
}
else if( $a->{$props[$i][0]} > $b->{$props[$i][0]}) {
return -1* $props[$i][1];
}
}
return 0;
});
}
sortByProps($someArray,(array(['name',-1],['address',-1],['occupation',-1],['favorite_food',-1])));
Here's how to sort by an arbitrary number of criteria, one after the other, breaking ties. It works a lot like an sql order by col1, col2 clause.
I always forget if $a - $b vs $b - $a sorts ascending or descending. Adjust as needed.
$comparatorSequence = array(
function($a, $b) { return $a->timestamp - $b->timestamp; }
, function($a, $b) { return $a->id - $b->id; }
);
usort($theArray, function($a, $b) use ($comparatorSequence) {
foreach ($comparatorSequence as $cmpFn) {
$diff = call_user_func($cmpFn, $a, $b);
if ($diff !== 0) {
return $diff;
}
}
return 0;
});

Categories