How to sort an array of associative arrays by "gender" value?

I have an array of arrays:

$students= array(
array("name"=>"...", "gender"=>"male"),
array("name"=>"...", "gender"=>"female"),
array("name"=>"...", "gender"=>"female"),
array("name"=>"...", "gender"=>"female"),
array("name"=>"...", "gender"=>"male"),
array("name"=>"...", "gender"=>"male"),
array("name"=>"...", "gender"=>"male"),
);

I would like to sort $students elements by gender to get:

$students= array(
array("name"=>"...", "gender"=>"male"),
array("name"=>"...", "gender"=>"female"),
array("name"=>"...", "gender"=>"male"),
array("name"=>"...", "gender"=>"female"),
array("name"=>"...", "gender"=>"male"),
array("name"=>"...", "gender"=>"female"),
array("name"=>"...", "gender"=>"male"),
);

How can I do this?

3 answers

  • answered 2019-08-19 03:02 FrankerZ

    I did this by filtering the elements to 2 separate arrays ($males and $females). array_filter preserves keys, so we simply pass it to array_values to get a fresh key list starting from 0. From there, it's a simple for loop, to intertwine them and add them to a final array.

    <?php
    
    $students= [
        ["name"=>"...", "gender"=>"male"],
        ["name"=>"...", "gender"=>"female"],
        ["name"=>"...", "gender"=>"female"],
        ["name"=>"...", "gender"=>"female"],
        ["name"=>"...", "gender"=>"male"],
        ["name"=>"...", "gender"=>"male"],
        ["name"=>"...", "gender"=>"male"],
    ];
    
    $males = array_values(array_filter($students, function($s) { return $s["gender"] === "male"; }));
    $females = array_values(array_filter($students, function($s) { return $s["gender"] === "female"; }));
    
    $final = [];
    $max = max(count($females), count($males));
    
    for ($i=0; $i<$max; $i++) {
        if (isset($males[$i])) {
            $final[] = $males[$i];
        }
    
        if (isset($females[$i])) {
            $final[] = $females[$i];
        }
    }
    
    print_r($final);
    

    See this demo here.

  • answered 2019-08-19 03:08 ggorlen

    You can use array_filter to create two groups according to gender. Then zip the groups into pairs using array_map and run the pairs through array_reduce to flatten the structure:

    <?php
    $students = [
        ["gender" => "male"],
        ["gender" => "female"],
        ["gender" => "female"],
        ["gender" => "female"],
        ["gender" => "male"],
        ["gender" => "female"],
        ["gender" => "female"],
        ["gender" => "male"],
        ["gender" => "male"],
        ["gender" => "female"],
        ["gender" => "female"],
    ];
    
    $males = array_filter($students, function ($e) {
        return $e["gender"] === "male";
    });
    $females = array_filter($students, function ($e) {
        return $e["gender"] === "female";
    });
    $zipped = array_map(null, $males, $females);
    $result = array_reduce($zipped, function ($a, $e) {
        if ($e[0]) $a[] = $e[0];
        if ($e[1]) $a[] = $e[1];
        return $a;  
    }, []);
    
    print_r($result);
    

    Output:

    Array
    (
        [0] => Array
            (
                [gender] => male
            )
    
        [1] => Array
            (
                [gender] => female
            )
    
        [2] => Array
            (
                [gender] => male
            )
    
        [3] => Array
            (
                [gender] => female
            )
    
        [4] => Array
            (
                [gender] => male
            )
    
        [5] => Array
            (
                [gender] => female
            )
    
        [6] => Array
            (
                [gender] => male
            )
    
        [7] => Array
            (
                [gender] => female
            )
    
        [8] => Array
            (
                [gender] => female
            )
    
        [9] => Array
            (
                [gender] => female
            )
    
        [10] => Array
            (
                [gender] => female
            )
    
    )
    

    If you're looking for pure speed, this should do the trick. O(n) with two passes and branch prediction should handle situations with a wide imbalance between the two genders (if not, just separate the last loop into three; should be the same performance):

    foreach ($students as $student) {
        if ($student["gender"] === "male") {
            $males[]= $student;
        }
        else {
            $females[]= $student;
        }
    }
    
    for ($i = 0, $j = 0; $i < count($males) || $j < count($females);) {
        if ($i < count($males)) {
            $result[]= $males[$i++];
        }
    
        if ($j < count($females)) {
            $result[]= $females[$j++];
        }
    }
    

  • answered 2019-08-19 03:41 Andreas

    Array_filter will traverse the array, and doing so twice just to split an array is unnecessary.
    Instead loop it with foreach and split it.
    Then array_combine each part with even or uneven number keys and merge the two.

    foreach($students as $stu){
        if($stu['gender'] == 'male'){
            $male[] = $stu;
        }else{
            $female[] = $stu;
        }
    }
    
    
    $male = array_combine(range(0,(count($male)-1)*2,2),$male); // make keys even starting with 0
    $female = array_combine(range(1,count($female)*2,2),$female); // make keys uneven starting with 1
    $all = array_replace($male, $female); // replace can be used since they keys do not create any problems
    ksort($all); //sort on key
    $all = array_values($all);
    
    var_dump($all);
    

    https://3v4l.org/TZCKN


    Another method is to assign the keys in the foreach then just do the array_replace.
    That should be faster since there is less array functions involved.

    $i = 0;
    $j = 1;
    foreach($students as $stu){
        if($stu['gender'] == 'male'){
            $male[$i] = $stu;
            $i +=2;
        }else{
            $female[$j] = $stu;
            $j +=2;
        }
    }
    
    $all = array_replace($male, $female);
    ksort($all);
    $all = array_values($all);
    
    var_dump($all);
    

    https://3v4l.org/k3MMj