alex
alex

Reputation: 490657

Custom key-sort a flat associative based on another array

Is it possible in PHP to do something like this? How would you go about writing a function? Here is an example. The order is the most important thing.

$customer['address'] = '123 fake st';
$customer['name'] = 'Tim';
$customer['dob'] = '12/08/1986';
$customer['dontSortMe'] = "this value doesn't need to be sorted";

And I'd like to do something like

$properOrderedArray = sortArrayByArray($customer, array('name', 'dob', 'address'));

Because at the end I use a foreach() and they're not in the right order (because I append the values to a string which needs to be in the correct order and I don't know in advance all of the array keys/values).

I've looked through PHP's internal array functions but it seems you can only sort alphabetically or numerically.

Upvotes: 192

Views: 199126

Answers (16)

hakre
hakre

Reputation: 198217

Take one array as your order:

$order = array('north', 'east', 'south', 'west');

You can sort another array based on values using array_intersect­Docs:

/* sort by value: */
$array = array('south', 'west', 'north');
$sorted = array_intersect($order, $array);
print_r($sorted);

Or in your case, to sort by keys, use array_intersect_key­Docs:

/* sort by key: */
$array = array_flip($array);
$sorted = array_intersect_key(array_flip($order), $array);
print_r($sorted);

Both functions will keep the order of the first parameter and will only return the values (or keys) from the second array.

So for these two standard cases you don't need to write a function on your own to perform the sorting/re-arranging.


To apply this on an associative array (like the customer in the question) , we can intersect on the keys and replace using array_replace­Docs with the original array.

That works, because associative arrays in PHP are ordered (compare: Are PHP Associative Arrays ordered? (Q&A)):

$order = array('name', 'dob', 'address', 'optional');
$sorted = array_replace(array_intersect_key(array_flip($order), $customer), $customer);
print_r($sorted);

This allows keeping the original order of optional keys.

Array
(
    [name] => Tim
    [dob] => 12/08/1986
    [address] => 123 fake st
    [dontSortMe] => this value doesn't need to be sorted
)

And it allows ignoring order values that are optional keys in the array.

If the latter is not a requirement, instead of sorting, the defaults having associative array can be replaced into as it is already an ordered map and customer will follow its order:

$order = array('name', 'dob', 'address', 'optional');
$sorted = array_replace(array_fill_keys($order, null), $customer);
print_r($sorted);
Array
(
    [name] => Tim
    [dob] => 12/08/1986
    [address] => 123 fake st
    [optional] => 
    [dontSortMe] => this value doesn't need to be sorted
)

The original requirement that the dontSortMe key has to be preserved may suggest the former or the latter application.

Non-sorted defaults could otherwise be appended by a union:

$order = array('name', 'dob', 'address', 'optional');
$sorted = array_replace(array_intersect_key(array_flip($order), $customer), $customer);
print_r($sorted + array_fill_keys($order, null));
Array
(
    [name] => Tim
    [dob] => 12/08/1986
    [address] => 123 fake st
    [dontSortMe] => this value doesn't need to be sorted
    [optional] => 
)

Upvotes: 17

Jenovai Matyas
Jenovai Matyas

Reputation: 69

Without magic...

$items=array(28=>c,4=>b,5=>a);
$seq=array(5,4,28);    
SortByKeyList($items,$seq) result: array(5=>a,4=>b,28=>c);

function sortByKeyList($items,$seq){
    $ret=array();
    if(empty($items) || empty($seq)) return false;
    foreach($seq as $key){$ret[$key]=$items[$key];}
    return $ret;
}

Upvotes: 6

DJules
DJules

Reputation: 31

A bit late, but I couldn't find the way I implemented it, this version needs closure, php>=5.3, but could be altered not to:

$customer['address'] = '123 fake st';
$customer['name'] = 'Tim';
$customer['dob'] = '12/08/1986';
$customer['dontSortMe'] = 'this value doesnt need to be sorted';

$order = array('name', 'dob', 'address');

$keys= array_flip($order);
uksort($customer, function($a, $b)use($keys){
    return $keys[$a] - $keys[$b];
});
print_r($customer);

Of course 'dontSortMe' needs to be sorted out, and may appear first in the example

Output from above snippet: (Demo)

Warning: Undefined array key "dontSortMe" in /in/Q4osh on line 12

Warning: Undefined array key "dontSortMe" in /in/Q4osh on line 12

Warning: Undefined array key "dontSortMe" in /in/Q4osh on line 12
Array
(
    [name] => Tim
    [dontSortMe] => this value doesnt need to be sorted
    [dob] => 12/08/1986
    [address] => 123 fake st
)

Upvotes: 2

Farid shahidi
Farid shahidi

Reputation: 352

If you have arrays like this, and you need to sort your array based on order, you can easily use this code:

$order = ['a', 'b', 'c', 'd', 'e'];
$needToSortArray = ['d', 'c', 'e'];

uksort($needToSortArray, function($key1, $key2) use ($order, $needToSortArray) {
    return (array_search($needToSortArray[$key1], $order) > array_search($needToSortArray[$key2], $order));
});

Upvotes: 0

Darkwaltz4
Darkwaltz4

Reputation: 4334

Just use array_merge or array_replace. array_merge works by starting with the array you give it (in the proper order) and overwriting/adding the keys with data from your actual array:

$customer['address']    = '123 fake st';
$customer['name']       = 'Tim';
$customer['dob']        = '12/08/1986';
$customer['dontSortMe'] = 'this value doesnt need to be sorted';

$properOrderedArray = array_merge(array_flip(array('name', 'dob', 'address')), $customer);
// or
$properOrderedArray = array_replace(array_flip(array('name', 'dob', 'address')), $customer);

// $properOrderedArray: array(
//   'name'       => 'Tim',
//   'dob'        => '12/08/1986',
//   'address'    => '123 fake st',
//   'dontSortMe' => 'this value doesnt need to be sorted')

PS: I'm answering this 'stale' question, because I think all the loops given as previous answers are overkill.

Upvotes: 431

Pageii Studio
Pageii Studio

Reputation: 2096

I adopted the answer from @Darkwaltz4 for its brevity and would like to share how I adapted the solution to situations where the array may contain different keys for each iteration like so:

Array[0] ...
['dob'] = '12/08/1986';
['some_key'] = 'some value';

Array[1] ...
['dob'] = '12/08/1986';

Array[2] ...
['dob'] = '12/08/1986';
['some_key'] = 'some other value';

and maintained a "master key" like so:

$master_key = array( 'dob' => ' ' ,  'some_key' => ' ' );

array_merge would have executed the merge in the Array[1] iteration based on $master_key and produced ['some_key'] = '', an empty value, for that iteration. Hence, array_intersect_key was used to modify $master_key in each iterations like so:

foreach ($customer as $customer) {
  $modified_key = array_intersect_key($master_key, $unordered_array);
  $properOrderedArray = array_merge($modified_key, $customer);
}

Upvotes: 1

user1653711
user1653711

Reputation: 1177

First Suggestion

function sortArrayByArray($array,$orderArray) {
    $ordered = array();
    foreach($orderArray as $key) {
        if(array_key_exists($key,$array)) {
            $ordered[$key] = $array[$key];
            unset($array[$key]);
        }
    }
    return $ordered + $array;
}

Second Suggestion

$properOrderedArray = array_merge(array_flip(array('name', 'dob', 'address')), $customer);

I wanted to point out that both of these suggestions are awesome. However, they are apples and oranges. The difference? One is non-associative friendly and the other is associative friendly. If you are using 2 fully associative arrays then the array merge/flip will actually merge and overwrite the other associative array. In my case that is not the results I was looking for. I used a settings.ini file to create my sort order array. The data array I was sorting did not need to written over by my associative sorting counterpart. Thus array merge would destroy my data array. Both are great methods, both need to be archived in any developers toolbox. Based on your needs you may find you actually need both concepts in your archives.

Upvotes: 1

Baptiste Bernard
Baptiste Bernard

Reputation: 181

I used the Darkwaltz4's solution but used array_fill_keys instead of array_flip, to fill with NULL if a key is not set in $array.

$properOrderedArray = array_replace(array_fill_keys($keys, null), $array);

Upvotes: 18

Doglas
Doglas

Reputation: 670

This function return a sub and sorted array based in second parameter $keys

function array_sub_sort(array $values, array $keys){
    $keys = array_flip($keys);
    return array_merge(array_intersect_key($keys, $values), array_intersect_key($values, $keys));
}

Example:

$array_complete = [
    'a' => 1,
    'c' => 3,
    'd' => 4,
    'e' => 5,
    'b' => 2
];

$array_sub_sorted = array_sub_sort($array_complete, ['a', 'b', 'c']);//return ['a' => 1, 'b' => 2, 'c' => 3];

Upvotes: 2

Grain
Grain

Reputation: 554

  • sort as requested
  • save for int-keys (because of array_replace)
  • don't return keys are not existing in inputArray
  • (optionally) filter keys no existing in given keyList

Code:

 /**
 * sort keys like in key list
 * filter: remove keys are not listed in keyList
 * ['c'=>'red', 'd'=>'2016-12-29'] = sortAndFilterKeys(['d'=>'2016-12-29', 'c'=>'red', 'a'=>3 ]], ['c', 'd', 'z']){
 *
 * @param array $inputArray
 * @param string[]|int[] $keyList
 * @param bool $removeUnknownKeys
 * @return array
 */
static public function sortAndFilterKeys($inputArray, $keyList, $removeUnknownKeys=true){
    $keysAsKeys = array_flip($keyList);
    $result = array_replace($keysAsKeys, $inputArray); // result = sorted keys + values from input + 
    $result = array_intersect_key($result, $inputArray); // remove keys are not existing in inputArray 
    if( $removeUnknownKeys ){
        $result = array_intersect_key($result, $keysAsKeys); // remove keys are not existing in keyList 
    }
    return $result;
}

Upvotes: 2

Eran Galperin
Eran Galperin

Reputation: 86805

There you go:

function sortArrayByArray(array $array, array $orderArray) {
    $ordered = array();
    foreach ($orderArray as $key) {
        if (array_key_exists($key, $array)) {
            $ordered[$key] = $array[$key];
            unset($array[$key]);
        }
    }
    return $ordered + $array;
}

Upvotes: 122

Peter de Groot
Peter de Groot

Reputation: 829

How about this solution

$order = array(1,5,2,4,3,6);

$array = array(
    1 => 'one',
    2 => 'two',
    3 => 'three',
    4 => 'four',
    5 => 'five',
    6 => 'six'
);

uksort($array, function($key1, $key2) use ($order) {
    return (array_search($key1, $order) > array_search($key2, $order));
});

Upvotes: 59

danielcraigie
danielcraigie

Reputation: 222

PHP has functions to help you with this:

$arrayToBeSorted = array('west', 'east', 'south', 'north');
$order = array('north', 'south', 'east', 'west');

// sort array
usort($arrayToBeSorted, function($a, $b) use ($order){
    // sort using the numeric index of the second array
    $valA = array_search($a, $order);
    $valB = array_search($b, $order);

    // move items that don't match to end
    if ($valA === false)
        return -1;
    if ($valB === false)
        return 0;

    if ($valA > $valB)
        return 1;
    if ($valA < $valB)
        return -1;
    return 0;
});

Usort does all the work for you and array_search provides the keys. array_search() returns false when it can't find a match so items that are not in the sort array naturally move to the bottom of the array.

Note: uasort() will order the array without affecting the key => value relationships.

Upvotes: 3

abyrvalg
abyrvalg

Reputation: 567

Another way for PHP >= 5.3.0:

$customer['address'] = '123 fake st';
$customer['name'] = 'Tim';
$customer['dob'] = '12/08/1986';
$customer['dontSortMe'] = 'this value doesnt need to be sorted';

$customerSorted = array_replace(array_flip(array('name', 'dob', 'address')), $customer);

Result:

Array (
  [name] => Tim
  [dob] => 12/08/1986
  [address] => 123 fake st
  [dontSortMe] => this value doesnt need to be sorted
)

Works fine with string and numeric keys.

Upvotes: 45

Boombastic
Boombastic

Reputation: 47

IF you have array in your array, you'll have to adapt the function by Eran a little bit...

function sortArrayByArray($array,$orderArray) {
    $ordered = array();
    foreach($orderArray as $key => $value) {
        if(array_key_exists($key,$array)) {
                $ordered[$key] = $array[$key];
                unset($array[$key]);
        }
    }
    return $ordered + $array;
}

Upvotes: 3

OIS
OIS

Reputation: 10033

function sortArrayByArray(array $toSort, array $sortByValuesAsKeys)
{
    $commonKeysInOrder = array_intersect_key(array_flip($sortByValuesAsKeys), $toSort);
    $commonKeysWithValue = array_intersect_key($toSort, $commonKeysInOrder);
    $sorted = array_merge($commonKeysInOrder, $commonKeysWithValue);
    return $sorted;
}

Upvotes: 29

Related Questions