问题
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 0
s'in one array and all 1
s' 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