How to sort multidimensional array by value? - php

Original array:
$resolutions = array (
"Desktop monitor" => array (
//Standard 4:3
"800x600" => "800x600",
"1024x768" => "1024x768",
"1600x1200" => "1600x1200",
//Wide 16:10
"960x600" => "960x600",
"1280x800" => "1280x800",
"1440x900" => "1440x900",
"1680x1050" => "1680x1050",
"1920x1200" => "1920x1200",
),
"Apple" => array (
"DeviceX" => "2048x1536",
"DeviceY" => "1024x768",
),
);
Wanted array:
$resolutions = array (
"Desktop monitor" => array (
"800x600" => "800x600",//Standard 4:3
"960x600" => "960x600",//Wide 16:10
"1024x768" => "1024x768",//Standard 4:3
"1280x800" => "1280x800",//Wide 16:10
"1440x900" => "1440x900",//Wide 16:10
"1600x1200" => "1600x1200",//Standard 4:3
"1680x1050" => "1680x1050",//Wide 16:10
"1920x1200" => "1920x1200",//Wide 16:10
),
"Apple" => array (
"DeviceY" => "1024x768",
"DeviceX" => "2048x1536",
),
);
What I have tried:
foreach ($resolutions as $screen => $resolution) {
foreach($resolution as $key => $val) {
$newarray[$key] = $row[$val];
}
array_multisort($newarray, SORT_DESC, $resolution);
}
I thought that I'm half way to end, but the code above gave me some non sense (for me), ie: first goes resolution 1024x768, then 1280x800, followed by 1440x900 and in the end is 800x600.

The problem is that you are sorting alphabetically. You will need to calculate the size of each resolution to sort them correctly. This will require writing a custom comparison function that calculate the resolution sizes and compare them.
foreach ($resolutions as &$resolution)
uasort($resolution, function ($a, $b) { return array_product(explode('x', $a)) - array_product(explode('x', $b)); });

This is a quick hack looking specifically for the Desktop monitor array key. ksort will sort by array key, but it doesn't sort well when you have an x in the middle of some numbers. Here's my solution:
Loop through array
Build new array with only first half of resolution as array key
Use ksort to sort new array
array_walk new array to create output array which looks like original
output original:
https://eval.in/71433
You could do the same thing for other arrays inside your original array e.g. Apple by using the value instead of the key as the resolution, and put the array_walk etc inside a loop.

Just looking at what you've put there, two things stand out:
You're looping through the outer array and sorting a single-dimensional array using multisort; I would recommend just using asort().
Not quite sure what's going on there with $resolutions being passed in as the thrid paramter. It wants an array of sort flags.
try:
foreach ($resolutions as $resolution) {
asort($resolution);
}

Related

sorting array based on child array[0] (unix) value

I need an array sorted by Unix timestamp values. I attempted to use both ksort and krsort before realising that occasionally the timestamp values might be the same (and you cannot have duplicate keys in arrays).
Here's an example array I may be faced with:
$array = array(
[
"unix" => 1556547761, // notice the two duplicate unix values
"random" => 4
],
[
"unix" => 1556547761,
"random" => 2
],
[
"unix" => 1556547769,
"random" => 5
],
[
"unix" => 1556547765, // this should be in the 3rd position
"random" => 9
]
);
So what I'm trying to do is sort them all based on each child arrays unix value, however I cannot figure out how to do so. I have tried countless insane ways (including all other sort functions and many, many for loops) to figure it out - but to no avail.
All help is appreciated.
You can use usort which sort your array by given function
Define function as:
function cmpByUnix($a, $b) {
return $a["unix"] - $b["unix"];
}
And use with: usort($array, "cmpByUnix");
Live example: 3v4l
Notice you can also use asort($array); but this will compare also the "random" field and keep the key - if this what you need then look at Mangesh answer
array_multisort() — Sort multiple or multi-dimensional arrays
array_columns() — Return the values from a single column in the input array
You can use array_multisort() and array_column(), then provide your desired sort order (SORT_ASC or SORT_DESC).
array_multisort(array_column($array, "unix"), SORT_ASC, $array);
Explanation:
In array_multisort(), arrays are sorted by the first array given. You can see we are using array_column($array, "unix"), which means that the second parameter is the order of sorting (ascending or descending) and the third parameter is the original array.
This is the result of array_column($array, "unix"):
Array(
[0] => 1556547761
[1] => 1556547761
[2] => 1556547765
[3] => 1556547769
)
This function sorts an array such that array indices maintain their correlation with the array elements they are associated with. This is used mainly when sorting associative arrays where the actual element order is significant.
Note:If two members compare as equal, their relative order in the sorted array is undefined.
Refer : https://www.php.net/manual/en/function.asort.php
asort($array);
echo "<pre>";
print_r($array);
echo "</pre>";
It will give you the output as
Array
(
[1] => Array
(
[unix] => 1556547761
[random] => 2
)
[0] => Array
(
[unix] => 1556547761
[random] => 4
)
[3] => Array
(
[unix] => 1556547765
[random] => 9
)
[2] => Array
(
[unix] => 1556547769
[random] => 5
)
)
You can keep the array key [1],[0],[3],[2]) as it is Or you can keep it as sequential as per your requirement.

Spliting .txt file lines in to multi-layered array key value pairs then sorting it

Since this question has a long explanation, I'll ask the question, then have the explanation below -- Can you sort a multidimensional array by their internal array key value, or is there a better way to get around sorting key value pairs that will have inevitable duplicates, than just using an array?
I am mostly unfamiliar with using PHP and want to learn how to store data.
The very simple example I made is just two HTML form inputs for a score and a name and a PHP file to handle the input to be stored in a plain .txt file, which was originally written with the pattern
42|John
32|Jane
25|John
I was able to successfully split the data, sort it, add the new inputted values then store it all back in the text file to be displayed somewhere else, using the name as the key and the score as the value.
I did all this only to realize that it would only store sort and display the last value associated with each name (i.e.)
42|John
32|Jane
25|John
would be sorted to
32|Jane
25|John
because you, obviously, can't have two of the same keys in an array, which is something I completely overlooked.
My solution, currently is to have an extra number that is unique to each name/score pair, which I formatted in the text file as
1|42|John
2|32|Jane
3|25|John
I then split them into a multidimensional array using this foreach loop
foreach($arr as $key => $value) {
$lineData = explode("|", $value);
$scores[$lineData[0]] = array($lineData[1] => $lineData[2]);
}
To get this output
Array
(
[1] => Array
(
[42] => John
)
[2] => Array
(
[32] => Jane
)
[3] => Array
(
[25] => John
)
)
which avoids overwriting any duplicate names or scores, but leaves me in a position where I can't (to my knowledge) use arsort() to sort the array in to highest to lowest.
You can use array_multisort for that, in combination with array_column. Because the key values are strings, you need to also convert them to integers, for which you can use array_map("intval", ...):
foreach($arr as $value) {
$result[] = explode("|", $value);
}
array_multisort(array_map("intval", array_column($result, 0)), $result);
After the above code has run, $result will be sorted by the key values:
[
['25', 'John'],
['32', 'Jane'],
['42', 'John']
]
To reverse the order, apply array_reverse to the result.
Alternative
You could also decide to sort the original array without conversion to a 2D array, and sort it with a custom sort callback, using usort and (again) intval:
usort($arr, function ($a, $b) {
return intval($a) - intval($b);
});
Then $arr will be sorted to:
[
'25|John',
'32|Jane',
'42|John'
]
To reverse the order, switch the position of $a and $b in the sort callback function:
return intval($b) - intval($a);
If we make a small change to your foreach iteration like this:
foreach($arr as $key => $value) {
$lineData = explode("|", $value);
$scores[] = array('score' => $lineData[1], 'name' => $lineData[2]);
}
Your array will have:
Array
(
[0] => Array
(
[score] => 42
[name] => John
)
[1] => Array
(
[score] => 32
[name] => Jane
)
[2] => Array
(
[score] => 25
[name] => John
)
)
You can use the uasort function, which takes the array to sort, and an user-defined function to do the sorting. The code would look like this:
function compare($a, $b)
{
if ($b['score'] == $a['score']) {
if ($a['name'] == $b['name']) {
return 0;
} elseif ($a['name'] < $b['name']) {
return -1;
} else {
return 1;
}
} else {
return ($b['score'] - $a['score']);
}
}
print_r($scores);
uasort($scores, 'compare');
print_r($scores);
Which gives the following result:
Array
(
[1] => Array
(
[score] => 32
[name] => Jane
)
[2] => Array
(
[score] => 25
[name] => John
)
[0] => Array
(
[score] => 42
[name] => John
)
)
When you use a user-defined function for the sorting you need to return one of 3 values (0 of the values as equal, -1 if $a < $b, and 1 if $b > $a. In this case we're sorting first by score (descending), then by name (ascending). Since you need to order from highest to lowest score, the comparison is $b against $a, for ascending order is $a against $b. I didn't consider the extra number necessary. If you need it then change this line:
$scores[] = array('score' => $lineData[1], 'name' => $lineData[2]);
To this:
$scores[$lineData[0]] = array('score' => $lineData[1], 'name' => $lineData[2]);

php array remove key and shift values up

I've researched topics similar to this but not exactly what I'm looking to do.
I have a multidimensional array like the following.
[code] => BILL
[assets] => Array
(
[en] => Array
(
[datatype] => My Assets
[data] => Array
(
[Corporate Equity] => 41
[Global Equity] => 24
[Fixed Income – Government] => 22
[Fixed Income – Corporate] => 8.1
[Other] => 3.57
)
)
)
I'd like to remove the first inner array, but preserve the values. Shift them up one level in the array so that it looks like this.
[code] => BILL
[assets] => Array
(
[datatype] => My Assets
[data] => Array
(
[Corporate Equity] => 41
[Global Equity] => 24
[Fixed Income – Government] => 22
[Fixed Income – Corporate] => 8.1
[Other] => 3.57
)
)
This is just the beginning of the array, there are other instances of the same key [en] at the same level.
I've tried unset, array_shift and others but I need to keep the contents of [en], just shift them up one level in the array.
You can use array_map which returns an array which contains all elements of the previous array after applying the function.
In this case it will simply take the array at index en and add it's contents to the new array.
http://php.net/manual/en/function.array-map.php
$arr = array('assets' => array(
'en' => array(
'datatype' => 'My Assets',
'data' => array(
'Corporate Equity' => 41,
'Global Equity' => 24,
'Fixed Income – Government' => 22,
'Fixed Income – Corporate' => 8.1,
'Other' => 3.57
)
)
));
$new_arr = array_map(function ($e) {
return $e['en'];
}, $arr);
A simple solution that assumes the key to always be en and the subkeys to always be (only) datatype and data:
$assets['datatype'] = $assets['en']['datatype'];
$assets['data'] = $assets['en']['data'];
unset( $assets['en'] );
This code could be problematic for you in the future if that array structure ever changes (it lacks extensibility), but it gets you what you want given the information you have provided.
array_shift is the opposite of array_pop. Used in stack/queue like structures for removing the fist element http://php.net/manual/en/function.array-shift.php
What you want to do is flatten the array. But if you want to keep all the other sub-arrays as you mentioned, you might look up array_merge.
I faced the same scenario after using reader to read xml file, the returned array was having inserted 0 key array in each level like the following one:
'config' =>
0 =>
'products' =>
0 =>
'media' =>
.
.
.
so I built a small function to get rid of a specific key and shift up its child's in a two dimensions array, in my case the key was 0. hopping this would help somebody also.
public function clearMaps(&$maps, $readerMaps, $omittedKey)
{
if (is_array($readerMaps)) {
foreach ($readerMaps as $key => $map) {
if ($key !== $omittedKey) {
$maps[$key] = [];
$this->clearMaps($maps[$key], $readerMaps[$key], $omittedKey);
} else {
$this->clearMaps($maps, $readerMaps[$key], $omittedKey);
}
}
} else {
$maps = $readerMaps;
}
}
// $maps: cleaned array, will start as empty array
// $readerMaps: array needs to be cleaned
// $omittedKey: array key to git rid of.
// first call is clearMaps([], $readerMaps, 0);

Sort 2D Array in PHP

I have an array that looks like this:
Array
(
[90] => Array
(
[1056] => 44.91
[1055] => 53.56
[1054] => 108.88
[1053] => 23.28
),
[63] => Array
(
[1056] => 44.44
[1055] => 53.16
[1054] => 108.05
),
[21] => Array
(
[1056] => 42.83
[1055] => 51.36
[1054] => 108.53
)
);
Both keys ([x] and [y]) refer to IDs in my database, so those need to stay intact. The order of the [x] does not matter, but I need to sort each array by the value of [y].
Edit:
I have tried this loop, but it does not seem to work:
foreach($distance as $key=>$value) {
asort($value,SORT_NUMERIC);
}
Use ksort (or uksort) to sort the arrays by their keys.
UPDATE: Use asort (or uasort) to sort by values, preserving keys.
UPDATE 2: Try this
foreach($distance as &$value) {
asort($value,SORT_NUMERIC);
}
Like this?
array_walk($array, 'asort');
Use asort() for sorting by values. It maintains the index associations.
For the loop, you need to pass $value by reference, so you need to use &$value.
array_multisort($arrindex1, SORT_DESC, $arrindex2, SORT_DESC, $array);

PHP custom ordering array

An array arrives with some or all of the following values, in any order. What's the best way to order them in ascending size order? So starting with small and ending with XXL. I can usort but am a bit lost as to how the elements should be ordered in my user defined function
Small
XXL
Medium
Large
XL
EDIT: left out some info so created new question Custom ordering array with key / value pairs
EDIT2: Full code
print_r($sizes);
$sorted_sizes = $this->sort_sizes(array_unique($sizes));
print_r($sorted_sizes);
function sort_sizes($sizes)
{
return uasort($sizes, array($this, 'cmp'));
}
function cmp($a,$b)
{
$sizeArray = array( 'Small' => 0, 'Medium' => 1, 'Large' => 2, 'XL' => 3, 'XXL' => 4);
return $sizeArray[$a] - $sizeArray[$b];
}
This outputs:
Array
(
[66-507cddcd16d9786abafccfa78b19acf8] => XL
[64-507cddcd16d9786abafccfa78b19acf8] => medium
[65-507cddcd16d9786abafccfa78b19acf8] => large
[63-507cddcd16d9786abafccfa78b19acf8] => small
)
and print_r($sorted_sizes) just gives output "1"
Updated answer according to full code
The first issue here is that you're returning the result of uasort():
function sort_sizes($sizes)
{
return uasort($sizes, array($this, 'cmp'));
}
That's wrong, because uasort() does not return the sorted array. It modifies the same variable that you pass as a parameter, and returns a boolean value. That's why you see 1 as output.
Make the method accept $sizes by reference:
function sort_sizes(array &$sizes)
{
uasort($sizes, array($this, 'cmp'));
}
Then call it like so:
print_r($sizes);
$sorted_sizes = array_unique($sizes);
$this->sort_sizes($sorted_sizes);
print_r($sorted_sizes);
Here's your cmp() method, with added support for case-insensitive sorting:
function cmp($a, $b)
{
$sizes = array('small' => 0, 'medium' => 1, 'large' => 2, 'xl' => 3, 'xxl' => 4);
return $sizes[strtolower($a)] - $sizes[strtolower($b)];
}
Old answer
Try this. Use uasort() instead if you want to maintain key-value pairs:
function sort_sizes($a, $b) {
// Map the sizes to an ordered sequence of ints
static $sizes = array('small' => 0, 'medium' => 1, 'large' => 2, 'xl' => 3, 'xxl' => 4);
// Find the difference, using the sizes as keys to the above array
return $sizes[strtolower($a)] - $sizes[strtolower($b)];
}
$arr = array('Small', 'XXL', 'Medium', 'Large', 'XL');
print_r($arr); // Before sorting
uasort($arr, 'sort_sizes');
print_r($arr); // After sorting
Output:
Array
(
[0] => Small
[1] => XXL
[2] => Medium
[3] => Large
[4] => XL
)
Array
(
[0] => Small
[2] => Medium
[3] => Large
[4] => XL
[1] => XXL
)
You can do this using array_multisort:
$orderIndex = array_flip(array('Small','Medium','Large','XL','XXL'));
$arr = array('Small','XXL','Medium','Large','XL');
array_multisort(array_map(function($val) use ($orderIndex) { return $orderIndex[$val]; }, $arr), $arr);
Here array_map is used with an anonymous function to build an array of the “weight” of each value in $arr. That array is then used to order the values in $arr.
This is basically the same as BoltClock suggested just with doing the comparison on the already calculated “weights”.
Do the sorting in two phases. First, convert text to a convenient numeric value. In this moment you should decide if "L" and "Large" map to the same value or not. Then reorder the array based on those numeric conversions.

Categories