How to order “zebra” array so each key has an alternate value (either 1, 0)

邮差的信 提交于 2020-02-08 11:31:04

问题


If you have this PHP array:

$args = array(
    'a' => array(
        'zebra' => 1,
    ),
    'b' => array(
        'zebra' => 0,
    ),
    'c' => array(
        'zebra' => 0,
    ),
    'd' => array(
        'zebra' => 0,
    ),
    'e' => array(
        'zebra' => 1,
    ),
);

Is there a way to algorithmically change the order of this array by the "zebra" key value, but instead of being "incremental" (0,0,0,1,1), they would alternate (0,1,0,1,0).

So using the example above, the desired finished array will look like this:

$ordered_args = array(
    'b' => array(
        'zebra' => 0,
    ),
    'a' => array(
        'zebra' => 1,
    ),
    'c' => array(
        'zebra' => 0,
    ),
    'e' => array(
        'zebra' => 1,
    ),
    'd' => array(
        'zebra' => 0,
    ),

);

Any extra duplicates should be appended to the end, so the solution should allow for other arrays such as ones with zebra values like (1,1,1,1,0,0), which would result in (0,1,0,1,1,1)

I simply can't figure this one out!

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).


回答1:


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];
    }
}



回答2:


Well, you can collect all 0s'in one array and all 1s' in another array and simply add them to the new array alternately with boolean flag check.

Pseudocode:

ones = []
zeroes = []

for($args as key => value)
    value['key'] = key // to preserve the key as well for later restoration
    if(value['zebra'] == 1)
       ones.push(value)
    else 
       zeroes.push(value)


result = []
flag = true // to decide to pop from ones or zeroes


while(sizeof(ones) > 0 || sizeof(zeroes) > 0){
    if(sizeof(ones) == 0 || flag === false){
        element = zeroes.pop()
        result[element['key']] = ['zebra' => element['zebra']]
    }else if(sizeof(zeroes) == 0 || flag){
        element = ones.pop()
        result[element['key']] = ['zebra' => element['zebra']]
    }   

    flag = !flag // to alternately add from either arrays
}



回答3:


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




回答4:


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.



来源:https://stackoverflow.com/questions/59862704/how-to-order-zebra-array-so-each-key-has-an-alternate-value-either-1-0

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!