Sometip
Sometip

Reputation: 352

Order rows by their column value to create repeating sequences of ascending values

I want to order my associative rows by their column value as sequences of ascending values.

Sample array1:

$args = [
    'a' => ['zebra' => 1],
    'b' => ['zebra' => 0],
    'c' => ['zebra' => 0],
    'd' => ['zebra' => 0],
    'e' => ['zebra' => 1],
];

Desired result:

[
    'b' => ['zebra' => 0],
    'a' => ['zebra' => 1],
    'c' => ['zebra' => 0],
    'e' => ['zebra' => 1],
    'd' => ['zebra' => 0],
]

Notice that duplicate values are not consecutive while sorting ascending. Instead, all unique, first-encountered values come first, then second encountered values, etc.

Sample array2:

$args = [
    'a' => ['zebra' => 1],
    'b' => ['zebra' => 1],
    'c' => ['zebra' => 1],
    'd' => ['zebra' => 1],
    'e' => ['zebra' => 0],
    'f' => ['zebra' => 0],
];

Desired result:

[
    'e' => ['zebra' => 0],
    'a' => ['zebra' => 1],
    'f' => ['zebra' => 0],
    'b' => ['zebra' => 1],
    'c' => ['zebra' => 1],
    'd' => ['zebra' => 1],
]

Edit: I tried to do this with usort, via this similar, but different, question, and the answer was no, so I am looking for a programatic solution (without usort).

Upvotes: -2

Views: 165

Answers (3)

Jeto
Jeto

Reputation: 14927

Here's a solution using array_map after grabbing 1s and 0s in separate arrays:

$args0 = array_filter($args, function ($arg) {
  return $arg['zebra'] === 0;
});
$args1 = array_filter($args, function ($arg) {
  return $arg['zebra'] === 1;
});

$result = array_merge(...array_map(static function ($arg0Key, $arg1Key) use ($args0, $args1) {
  if ($arg0Key !== null) {
    $result[$arg0Key] = $args0[$arg0Key];
  }
  if ($arg1Key !== null) {
    $result[$arg1Key] = $args1[$arg1Key];
  }
  return $result;
}, array_keys($args0), array_keys($args1)));

print_r($result);

Demo: https://3v4l.org/sfqeq

Note: using two array_filter to separate values looks nice but loops over $args twice; prefer a simple loop if the initial array can be somewhat big. This is not the relevant part of the answer, though.

Upvotes: 0

Nigel Ren
Nigel Ren

Reputation: 57131

Same idea to split the input into the 1's and 0's, then output a 0 and a 1 as long as there is something left to output. As each time you output a value, the array is reduced, this just continues till both lists are empty so should cope with unbalanced lists...

$temp = [ 0 => [], 1 => []];

foreach($args as $key=>$value){
    $temp[$value['zebra']][] = $key;
}

$output = [];
while ( !empty($temp[0]) || !empty($temp[1]) )   {
    if ( !empty($temp[0]) )   {
        $next = array_shift($temp[0]);
        $output [$next] = $args[$next];
    }
    if ( !empty($temp[1]) )   {
        $next = array_shift($temp[1]);
        $output [$next] = $args[$next];
    }
}

Upvotes: 0

Aksen P
Aksen P

Reputation: 4599

I can suggest you to use deconstruction with count of comparison.

At first step you can collect all indexes with zebra = 1 and with zebra = 0:

$zeros = [];
$ones = [];

foreach($args as $let=>$arg){
    if ($arg['zebra'] === 1) {
        $ones[] = $let;
    } else if ($arg['zebra'] === 0) {
        $zeros[] = $let;
    }
}

And now you can construct resultant array like:

if(abs(count($zeros) - count($ones)) === 1) {    // if their difference equal to 1
    if (count($ones) > count($zeros)){           // if $ones is bigger
        foreach($zeros as $ind=>$let){ 
            $res[$ones[$ind]] = ['zebra' => 1];
            $res[$let]        = ['zebra' => 0];  
            $tmp = $ind;
        } 
        $res[$ones[$tmp+1]] = ['zebra' => 1];
    } else if (count($ones) < count($zeros)){      // if $zeros is bigger
        foreach($ones as $ind=>$let){ 
            $res[$zeros[$ind]] = ['zebra' => 0];
            $res[$let]        = ['zebra' => 1];  
            $tmp = $ind;
        } 
        $res[$zeros[$tmp+1]] = ['zebra' => 0];
    }
}

Output:

Array
(
    [b] => Array
        (
            [zebra] => 0
        )

    [a] => Array
        (
            [zebra] => 1
        )

    [c] => Array
        (
            [zebra] => 0
        )

    [e] => Array
        (
            [zebra] => 1
        )

    [d] => Array
        (
            [zebra] => 0
        )

)

Demo

If you need result in case of (1,0,1,0,0) use next constructor:

    if (count($ones) > count($zeros)){
        foreach($ones as $ind=>$let){ 
            if (isset($zeros[$ind])) $res[$zeros[$ind]] = ['zebra' => 0]; 
            $res[$let]        = ['zebra' => 1];  
        }  
    } else if (count($zeros) > count($ones)){
        foreach($zeros as $ind=>$let){ 
            $res[$let]        = ['zebra' => 0];  
            if (isset($ones[$ind])) $res[$ones[$ind]] = ['zebra' => 1]; 
        }  
    }

Output:

Array
(
    [b] => Array
        (
            [zebra] => 0
        )

    [a] => Array
        (
            [zebra] => 1
        )

    [d] => Array
        (
            [zebra] => 0
        )

    [c] => Array
        (
            [zebra] => 1
        )

    [e] => Array
        (
            [zebra] => 0
        )

)

Demo

Upvotes: 0

Related Questions