Sort a multi-dimensional Array in PHP - php

i do not unstand the way to sort a multi-dimensional array in php. I have a structure like this:
Array (
[0] (
['vname'] => "Bernt"
['nname'] => "Mayer"
['kl_name'] => "ZR4"
)
[1] (
['vname'] => "Albert"
['nname'] => "Mayer"
['kl_name'] => "TR4"
)
)
My goal is now to first sort by kl_name, and then by nname and then vname. Most important is kl_name.
First i tought i should make objects and store them in the array, but i think sorting them is even more complex. php.net has a nice article about array_multisort, but year, i dont understand it :/

You'll want to sort the array with a user defined function (with usort).
You can then manually specify how you'd like the items to be ordered using the kl_name, nname and vname attributes.
Something like this:
usort($arr, function($a, $b) {
if ($a['kl_name'] !== $b['kl_name']) {
return strcmp($a['kl_name'], $b['kl_name']);
} else if ($a['nname'] !== $b['nname']) {
return strcmp($a['nname'], $b['nname']);
} else {
return strcmp($a['vname'], $b['vname']);
}
});
The function will first attempt to sort by kl_name, then by nname and finally by vname if all previous values are equal.

You can use array_multisort for this.
// Obtain a list of columns
foreach ($data as $key => $row) {
$kl_name[$key] = $row['kl_name'];
$nname[$key] = $row['nname'];
$vname[$key] = $row['vname'];
}
// Sort
array_multisort($kl_name, SORT_ASC, $nname, SORT_ASC, $vname, SORT_ASC, $data);
See http://php.net/array_multisort for more details.

I've never understood array_multisort either. I just use usort for things like this. What you do is, you compare the 1st field, and if it's the same, then you compare the 2nd... and so on.
So, it would look something like this (assuming $data is your array).
usort($data, function($a, $b){
$sort_kl_name = strnatcasecmp($a['kl_name'], $b['kl_name']);
if($sort_kl_name === 0){
$sort_nname = strnatcasecmp($a['nname'], $b['nname']);
if($sort_kl_name === 0){
return strnatcasecmp($a['vname'], $b['vname']);
}
return $sort_nname;
}
return $sort_kl_name;
});
This looks a little messy, with lots of ifs. Let's simplify that with a foreach.
$sortLevels = array('kl_name', 'nname', 'vname');
usort($data, function($a, $b) use($sortLevels){
foreach($sortLevels as $field){
$sort = strnatcasecmp($a[$field], $a[$field]);
if($sort !== 0){
break;
}
}
return $sort;
});
DEMO: https://eval.in/173774

Related

usort encrypted data by selectable columns (table sorting)

Struggling a little to deal with sorting a table by column name. The issues are the data is encrypted so cant just do a sort by the column in the direction needed (ascending descending). So i thought I can use usort but although i can call a function with it i.e. usort( $data, "sortMyData" ); I can't pass the field to sort or it's direction into it. So to do this I would need to write a function to sort for each possible column which is not ideal at all, does anyone know a way i could for instance add another parameter to usort which would contain its property to sort by and it's direction.
Perhaps an alternative would be to decrypt the entire set of data into some memory table but as the data is encrypted and secure i wonder if that would open the way for a vulnerability!
My last option would be to build this into a foreach loop which i could see would make sense but is this the only way?
thanks
Craig
I can't pass the field to sort or it's direction into it.
Actually, you can. There is an example:
<?php
// Example data
$data = array(
array('name' => 'Gamma', 'val' => 25),
array('name' => 'Beta', 'val' => 5),
array('name' => 'Alpha', 'val' => 10)
);
function sortme(&$array, $onfield, $isdesc) {
usort($array,
function($a, $b) use ($onfield, $isdesc) { // 'use' keyword allows to reference external variables from the inside
// custom method to obtain and comapre data;
$v1 = isset($a[$onfield]) ? $a[$onfield] : NULL;
$v2 = isset($b[$onfield]) ? $b[$onfield] : NULL;
if ($v1 < $v2) return ($isdesc ? 1 : -1);
elseif ($v1 > $v2) return ($isdesc ? -1 : 1);
else return 0;
// Note: the conditions above can be replaced by spaceship operator in PHP 7+:
// return $isdesc ? ($v2 <=> $v1) : ($v1 <=> $v2) ;
}
);
}
sortme($data, 'name', false); // sort by `name` ascending
print_r($data); // Alpha -> Beta -> Gamma
sortme($data, 'val', true); // sort by `val` descending
print_r($data); // 25 -> 10 -> 5
Refer this stackoverflow question, Pass extra parameters to usort callback
it provides an example of passing extra parameters to usrot function.
function sort_by_term_meta( $terms, $meta )
{
usort($terms, array(new TermMetaCmpClosure($meta), "call"));
}
function term_meta_cmp( $a, $b, $meta )
{
$name_a = get_term_meta($a->term_id, $meta, true);
$name_b = get_term_meta($b->term_id, $meta, true);
return strcmp($name_a, $name_b);
}
class TermMetaCmpClosure
{
private $meta;
function __construct( $meta ) {
$this->meta = $meta;
}
function call( $a, $b ) {
return term_meta_cmp($a, $b, $this->meta);
}
}
basically you need to create a class function to do the sorting and you can pass additional parameters (column, direction) when constructing the class.

PHP - Compare associative array with custom list and sort to match

I need to sort an array in a particular order, sorting by the keys. I know I need to do something like the below but have tried many different variations of the it and cannot get the result I need.
Can anyone help?
An example of the array is below and th results I am looking for for this would be: Connectivity | Contact Centres | Cloud & Hosting | Business Continuity
$solutions = Array ( [Business Continuity] => business-continuity
[Connectivity] => connectivity [Cloud & Hosting] => cloud-hosting
[Contact Centres] => contact-centres )
function reorder_solutions($a, $b){
$custom = array('Lines & Calls', 'Mobile', 'Connectivity', 'Wifi', 'LAN', 'UC&C', 'Contact Centres', 'Cloud & Hosting', 'Managed Services', 'Security', 'Business Continuity');
foreach ($custom as $k => $v){
if ($k == $b) {
return 0;
}
return ($k < $b) ? -1 : 1;
}
}
uasort($solutions, "reorder_solutions");
Here's one way to do it:
// loop over the $custom array
foreach ($custom as $key) {
// add each key found in $solutions into a $sorted array
// (they'll be added in custom order)
if (isset($solutions[$key])) {
$sorted[$key] = $solutions[$key];
// remove the item from $solutions after adding it to $sorted
unset($solutions[$key]);
}
}
// merge the $sorted array with any remaining items in $solutions
$solutions = array_merge($sorted, $solutions);
Another way:
// create an array using the values of $custom as keys with all empty values
$custom = array_fill_keys($custom, null);
// merge that array with $solutions (same keys will be overwritten with values
// from $solutions, then remove the empty values with array_filter
$solutions = array_filter(array_merge($custom, $solutions));
You can make it a one-liner if you like.
$solutions = array_filter(array_merge(array_fill_keys($custom, null), $solutions));

Using angular to turn a multi-dimensional array into a multi level list using php and usort

I have been working on this a while. I see multi-level arrays in php are not that easy. Here is my code:
Array
(
[0]=array(
"level"=>'Level1',
"id"=>1,
"title"=>"Home",
"order"=>"0"
);
[1]=array(
"level"=>'Level1',
"id"=>"355",
"title"=>"About Us",
"order"=>"21"
);
[2]=array(
"level"=>'Level1',
"id"=>"10",
"title"=>"Test",
"order"=>"58"
);
[3]=array(
"level"=>'Level2',
"id"=>13,
"title"=>"Our Team",
"order"=>"11",
"parent_id"=>"355"
);
[4]=array(
"level"=>'Level2',
"id"=>12,
"title"=>"The In Joke",
"order"=>"12",
"parent_id"=>"355"
);
[5]=array(
"level"=>'Level2',
"id"=>11,
"title"=>"Our History",
"order"=>"13",
"parent_id"=>"355"
));
>
1-Home
2-about us
3-Our Team
4-The In Joke
5-Our History
6-Test
i have multi-level parent child array and need to sort according to about result did not understand how i can use usort().
To use usort() to sort your array, you need to write a custom sort function. Because you want to look at the $array['title'] value for the comparison, you would use this array index in your comparison function:
$array = array(
array(
"level"=>'Level1',
"id"=>1,
"title"=>"Home",
"order"=>"0"
),
// your additional multidimensional array values...
);
// function for `usort()` - $a and $b are both arrays, you can look at their values for sorting
function compare($a, $b){
// If the values are the same, return 0
if ($a['title'] == $b['title']) return 0;
// if the title of $a is less than $b return -1, otherwise 1
return ($a['title'] < $b['title']) ? -1 : 1;
}
usort($array, 'compare');

Sorting multi-dimensional array by weighted value

There are numerous questions here asking how to sort a multi-dimensional array in PHP. The answer is usort(). I know that. But I have a question that takes it a bit further, and I couldn't see a similar answer here.
I have an array of records, each of which includes a country ID (or a country name if you prefer; it's not relevant).
My task is to sort the array in such a way as to favour certain countries. This is dynamic -- ie the choice of countries to favour is determined by the user's config. I have a separate array which specifies the required sort order for the first few countries; results from other countries would just be left unsorted at the end of the list.
So the question is: how do I get the this sort criteria into usort() without resorting to using a global variable. And preferably without injecting the criteria array into every element of the main array ('coz if I'm going to loop it anyway, what's the point in using usort() at all?)
Please note: Since it's going to be relevant to the answers here, I'm stuck on PHP 5.2 for the time being, so I can't use an anonymous function. We are in the process of upgrading, but for now I need answers that will work for 5.2. (answers for 5.3/5.4 will be welcome too, especially if they make it significantly easier, but I won't be able to use them)
You explicitly write that you do not want to have global variables, so I do not make you a suggestion with static variables as well because those are actually global variables - and those are not needed at all.
In PHP 5.2 (and earlier) if you need call context within the callback, you can create your context by making use of a class of it's own that carries it:
class CallContext
{
}
For example you have the compare function for sort:
class CallContext
{
...
public function compare($a, $b)
{
return $this->weight($a) - $this->weight($b);
}
public function getCallback()
{
return array($this, 'compare');
}
...
}
That function can be used as the following as a callback with usort then:
$context = new CallContext();
usort($array, $context->getCallback());
Pretty straight forward. The private implementation of CallContext::weight is still missing, and from your question we know it needs some sort data and information. For example the name of the key of the country id in each record. Lets assume records are Stdclass objects so to get the weight of one record the context class needs to know the name of the property, the sort-order you define your own and a sort-value for those country-ids that are not defined in the custom sort order (the others, the rest).
These configuration values are given with the constructor function (ctor in short) and are stored as private members. The missing weight function then converts a record into the sort-value based on that information:
class CallContext
{
private $property, $sortOrder, $sortOther;
public function __construct($property, $sortOrder, $sortOther = 9999)
{
$this->property = $property;
$this->sortOrder = $sortOrder;
$this->sortOther = $sortOther;
}
private function weight($object) {
if (!is_object($object)) {
throw new InvalidArgumentException(sprintf('Not an object: %s.', print_r($object, 1)));
}
if (!isset($object->{$this->property})) {
throw new InvalidArgumentException(sprintf('Property "%s" not found in object: %s.', $this->property, print_r($object, 1)));
}
$value = $object->{$this->property};
return isset($this->sortOrder[$value])
? $this->sortOrder[$value]
: $this->sortOther;
}
...
The usage now extends to the following:
$property = 'country';
$order = array(
# country ID => sort key (lower is first)
46 => 1,
45 => 2
);
$context = new CallContext('country', $order);
usort($array, $context->getCallback());
With the same principle you can very often convert any PHP 5.3 closure with use clauses to PHP 5.2. The variables from the use clause become private members injected with construction.
This variant does not only prevent the usage of static, it also makes visible that you have got some mapping per each element and as both elements are treated equal, it makes use of a private implementation of some weight function which works very well with usort.
I hope this is helpful.
You might not want a global variable, but you need something that behaves like one. You could use a class with static methods and parameters. It won't pollute the global scope that much and it would still function the way you need it.
class CountryCompare {
public static $country_priorities;
public static function compare( $a, $b ) {
// Some custom sorting criteria
// Work with self::country_priorities
}
public static function sort( $countries ) {
return usort( $countries, array( 'CountryCompare', 'compare' ) );
}
}
Then just call it like this:
CountryCompare::country_priorities = loadFromConfig();
CountryCompare::sort( $countries );
You can use closures (PHP >= 5.3):
$weights = array( ... );
usort($records, function($a, $b) use ($weights) {
// use $weights in here as usual and perform your sort logic
});
See Demo : http://codepad.org/vDI2k4n6
$arrayMonths = array(
'jan' => array(1, 8, 5,4),
'feb' => array(10,12,15,11),
'mar' => array(12, 7, 4, 3),
'apr' => array(10,16,7,17),
);
$position = array("Foo1","Foo2","Foo3","FooN");
$set = array();
foreach($arrayMonths as $key => $value)
{
$max = max($value);
$pos = array_search($max, $value);
$set[$key][$position[$pos]] = $max ;
}
function cmp($a, $b)
{
foreach($a as $key => $value )
{
foreach ($b as $bKey => $bValue)
{
return $bValue - $value ;
}
}
}
uasort($set,"cmp");
var_dump($set);
output
array
'apr' =>
array
'FooN' => int 17
'feb' =>
array
'Foo3' => int 15
'mar' =>
array
'Foo1' => int 12
'jan' =>
array
'Foo2' => int 8
another example:-
Sorting a Multi-Dimensional Array with PHP
http://www.firsttube.com/read/sorting-a-multi-dimensional-array-with-php/
Every so often I find myself with a multidimensional array that I want to sort by a value in a sub-array. I have an array that might look like this:
//an array of some songs I like
$songs = array(
'1' => array('artist'=>'The Smashing Pumpkins', 'songname'=>'Soma'),
'2' => array('artist'=>'The Decemberists', 'songname'=>'The Island'),
'3' => array('artist'=>'Fleetwood Mac', 'songname' =>'Second-hand News')
);
The problem is thus: I’d like to echo out the songs I like in the format “Songname (Artist),” and I’d like to do it alphabetically by artist. PHP provides many functions for sorting arrays, but none will work here. ksort() will allow me to sort by key, but the keys in the $songs array are irrelevant. asort() allows me to sort and preserves keys, but it will sort $songs by the value of each element, which is also useless, since the value of each is “array()”. usort() is another possible candidate and can do multi-dimensional sorting, but it involves building a callback function and is often pretty long-winded. Even the examples in the PHP docs references specific keys.
So I developed a quick function to sort by the value of a key in a sub-array. Please note this version does a case-insensitive sort. See subval_sort() below.
function subval_sort($a,$subkey) {
foreach($a as $k=>$v) {
$b[$k] = strtolower($v[$subkey]);
}
asort($b);
foreach($b as $key=>$val) {
$c[] = $a[$key];
}
return $c;
}
To use it on the above, I would simply type:
$songs = subval_sort($songs,'artist');
print_r($songs);
This is what you should expect see:
Array
(
[0] => Array
(
[artist] => Fleetwood Mac
[song] => Second-hand News
)
[1] => Array
(
[artist] => The Decemberists
[song] => The Island
)
[2] => Array
(
[artist] => The Smashing Pumpkins
[song] => Cherub Rock
)
)
The songs, sorted by artist.
The answer to your question is indeed in the usort() function. However, what you need to do is write the function that you pass to it is doing the weighting for you properly.
Most of the time, you have something like
if($a>$b)
{
return $a;
}
But what you need to do is something along the lines of
if($a>$b || $someCountryID != 36)
{
return $a;
}
else
{
return $b;
}
You need to use ksort to sort by weight, not usort. That will be much cleaner.
Arrange your data in an associative array $weighted_data in the format weight => country_data_struct. This is a very intuitive form of presentation for weighted data. Then run
krsort($weighted_data)

PHP remove arrays by nested key value

I have some arrays that I'd like to unset based on a key.
For example, let's say I have this array:
$array = array(
'one' => array('item' => '1'),
'two' => array('item' => '2')
);
If I want to unset the nested array with key 'two', I could do:
unset($array['two'])
or if I wanted to unset just the item array for key 'two', I could do:
unset($array['two']['item'])
I want to dynamically delete array items based on known keys. So for example, I know I want to delete ['two']['item'].
How do I pass those two arguments to a method, that can then be appended to an array?
Example:
//This works fine if it's only the first item in the array
function deleteArray($keys)
{
unset($this->array[$keys]);
}
But when we want to delete nested items, this would not work. I could pass in the keys as an array such as array('two', 'item') and build the index off of this, but not sure how....
Any help would be great! thank you!
You can use this function:
function delete(&$array, $keys)
{
$key = array_shift($keys);
if (count($keys) == 0)
unset($array[$key]);
else
delete($array[$key], $keys);
}
Try with a recursive function:
function deleteArray(&$array, $keys) {
if ( count($keys) == 1 )
unset( $array[$keys[0]] );
else
{
$k = array_shift($keys);
deleteArray($array[$k],$keys);
}
}
deleteArray($this->arr, array("three","item","blabla")); // This erase $this->array["three"]["item"]["blabla"]
function deleteArray($keys)
{
$keyarray = explode($keys, " ");
unset($this->array[$keyarray[0]][$keyarray[1]]);
}
I edited it a bit (won't work!) maybe somebody could continue that. Maybe it is possible with a while() ...

Categories